Skip to content

Commit

Permalink
Use SAN as cert name if CN isn't pressent
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Jul 17, 2023
1 parent 714f32c commit 0a6e437
Showing 1 changed file with 62 additions and 43 deletions.
105 changes: 62 additions & 43 deletions library/X509/CertificateUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Icinga\Module\X509;

use Exception;
use Generator;
use Icinga\Application\Logger;
use Icinga\File\Storage\TemporaryLocalFileStorage;
use Icinga\Module\X509\Model\X509Certificate;
Expand All @@ -16,6 +17,7 @@
use ipl\Sql\Expression;
use ipl\Sql\Select;
use ipl\Stdlib\Filter;
use Traversable;

class CertificateUtils
{
Expand Down Expand Up @@ -123,15 +125,14 @@ public static function duration($seconds)
*
* If the given DN contains a CN, the CN is returned. Else, the DN is returned as string.
*
* @param array $dn
* @param array $dn
*
* @return string The CN if it exists or the full DN as string
*/
private static function shortNameFromDN(array $dn)
private static function shortNameFromDN(array $dn): string
{
if (isset($dn['CN'])) {
$cn = (array) $dn['CN'];
return $cn[0];
return ((array) $dn['CN'])[0];
} else {
$result = [];
foreach ($dn as $key => $value) {
Expand All @@ -151,16 +152,31 @@ private static function shortNameFromDN(array $dn)
/**
* Split the given Subject Alternative Names into key-value pairs
*
* @param string $sans
* @param string $sans
*
* @return \Generator
* @return Generator
*/
private static function splitSANs($sans)
private static function splitSANs(string $sans): Generator
{
preg_match_all('/(?:^|, )([^:]+):/', $sans, $keys);
$values = preg_split('/(^|, )[^:]+:\s*/', $sans);
for ($i = 0; $i < count($keys[1]); $i++) {
yield [$keys[1][$i], $values[$i + 1]];
$orderedKeys = $keys[1];
usort($orderedKeys, function (string $a, string $b) {
$order = ['DNS' => 0, 'rfc822Name' => 1, 'URI' => 2, 'IP Address' => 3, 'email' => 4];
if (! isset($order[$a])) {
return 1;
}

if (! isset($order[$b])) {
return -1;
}

return $order[$a] <=> $order[$b];
});

$keys = array_flip($keys[1]);
$values = preg_split('/(^|, )[^:]+:\s*/', $sans, -1, PREG_SPLIT_NO_EMPTY);
for ($i = 0; $i < count($keys); $i++) {
yield [$orderedKeys[$i], $values[$keys[$orderedKeys[$i]]]];
}
}

Expand All @@ -169,7 +185,7 @@ private static function splitSANs($sans)
*
* @param string $file Path to the bundle
*
* @return \Generator
* @return Generator
*/
public static function parseBundle($file)
{
Expand Down Expand Up @@ -232,11 +248,18 @@ public static function findOrInsertCert(Connection $db, $cert)
$pubkey = openssl_pkey_get_details(openssl_pkey_get_public($cert));
$signature = explode('-', $certInfo['signatureTypeSN']);

$sans = static::splitSANs($certInfo['extensions']['subjectAltName'] ?? '');
if (! isset($certInfo['subject']['CN']) && $sans->valid()) {
$subject = $sans->current()[1];
} else {
$subject = static::shortNameFromDN($certInfo['subject']);
}

// TODO: https://github.com/Icinga/ipl-orm/pull/78
$db->insert(
'x509_certificate',
[
'subject' => CertificateUtils::shortNameFromDN($certInfo['subject']),
'subject' => $subject,
'subject_hash' => $dbTool->marshalBinary($subjectHash),
'issuer' => CertificateUtils::shortNameFromDN($certInfo['issuer']),
'issuer_hash' => $dbTool->marshalBinary($issuerHash),
Expand All @@ -258,53 +281,49 @@ public static function findOrInsertCert(Connection $db, $cert)

$certId = $db->lastInsertId();

CertificateUtils::insertSANs($db, $certId, $certInfo);
CertificateUtils::insertSANs($db, $certId, $sans);

return [$certId, $issuerHash];
}


private static function insertSANs($db, $certId, array $certInfo)
private static function insertSANs($db, $certId, Traversable $sans): void
{
$dbTool = new DbTool($db);
foreach ($sans as $san) {
list($type, $value) = $san;

if (isset($certInfo['extensions']['subjectAltName'])) {
foreach (CertificateUtils::splitSANs($certInfo['extensions']['subjectAltName']) as $san) {
list($type, $value) = $san;

$hash = hash('sha256', sprintf('%s=%s', $type, $value), true);

$row = X509CertificateSubjectAltName::on($db);
$row->columns([new Expression('1')]);
$hash = hash('sha256', sprintf('%s=%s', $type, $value), true);

$filter = Filter::all(
Filter::equal('certificate_id', $certId),
Filter::equal('hash', $hash)
);
$row = X509CertificateSubjectAltName::on($db);
$row->columns([new Expression('1')]);

$row->filter($filter);
$filter = Filter::all(
Filter::equal('certificate_id', $certId),
Filter::equal('hash', $hash)
);

// Ignore duplicate SANs
if ($row->execute()->hasResult()) {
continue;
}
$row->filter($filter);

// TODO: https://github.com/Icinga/ipl-orm/pull/78
$db->insert(
'x509_certificate_subject_alt_name',
[
'certificate_id' => $certId,
'hash' => $dbTool->marshalBinary($hash),
'type' => $type,
'value' => $value,
'ctime' => new Expression('UNIX_TIMESTAMP() * 1000')
]
);
// Ignore duplicate SANs
if ($row->execute()->hasResult()) {
continue;
}

// TODO: https://github.com/Icinga/ipl-orm/pull/78
$db->insert(
'x509_certificate_subject_alt_name',
[
'certificate_id' => $certId,
'hash' => $dbTool->marshalBinary($hash),
'type' => $type,
'value' => $value,
'ctime' => new Expression('UNIX_TIMESTAMP() * 1000')
]
);
}
}


private static function findOrInsertDn($db, $certInfo, $type)
{
$dbTool = new DbTool($db);
Expand Down

0 comments on commit 0a6e437

Please sign in to comment.