Skip to content

Commit

Permalink
Merge pull request #75 from liamdennehy/new-extensions
Browse files Browse the repository at this point in the history
New extensions: SCTList, fixes
  • Loading branch information
liamdennehy authored Jul 25, 2020
2 parents 66df89e + c8aa755 commit f9930d5
Show file tree
Hide file tree
Showing 18 changed files with 531 additions and 146 deletions.
2 changes: 1 addition & 1 deletion src/Certificate/AuthorityInformationAccess.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function __construct($extensionDER, $isCritical = false)
default:
$this->findings[] = new Finding(
self::type,
'warning',
$isCritical ? 'critical' : 'warning',
"Unrecognised authorityInfoAccess OID $oid ($oidName): ".
base64_encode($extensionDER)
);
Expand Down
2 changes: 1 addition & 1 deletion src/Certificate/AuthorityKeyIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function __construct($extensionDER, $isCritical = false)
default:
$this->findings[] = new Finding(
self::type,
'error',
$isCritical ? 'critical' : 'warning',
"Unrecognised AuthorityKeyIdentifier ".
$akiElement->tag().
" Format: ".
Expand Down
2 changes: 1 addition & 1 deletion src/Certificate/BasicConstraints.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function __construct($extensionDER, $isCritical = false)
// Some CAs incorrectly encode isCA as TRUE as 0x01, parser expects 0xFF
$this->findings[] = new Finding(
self::type,
'warning',
$isCritical ? 'critical' : 'warning',
"isCA not correctly encoded in ASN1, expectedd 0xff found 0x".
bin2hex($extensionDER[4])
);
Expand Down
2 changes: 1 addition & 1 deletion src/Certificate/CRLDistributionPoints.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function __construct($extensionDER, $isCritical = false)
// TODO: Handle DN of CDPs
$this->findings[] = new Finding(
self::type,
'warning',
$isCritical ? 'critical' : 'warning',
"Unrecognised crlDistributionPoints entry: ".
base64_encode($extensionDER)
);
Expand Down
34 changes: 18 additions & 16 deletions src/Certificate/CertificatePolicies.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,23 @@ public function __construct($extensionDER, $isCritical = false)
{
$this->isCritical = $isCritical;
$this->binary = $extensionDER;
if ($isCritical == true) {
$findingLevel = 'critical';
} else {
$findingLevel = 'warning';
}
try {
$seq = UnspecifiedType::fromDER($extensionDER)->asSequence();
$policies = UnspecifiedType::fromDER($extensionDER);
if ($policies->tag() <> 16) {
$this->findings[] = new Finding(
self::type,
$isCritical ? 'critical' : 'warning',
'Malformed certificatePolicies extension, should be a Sequence: '.
base64_encode($extensionDER)
);
return;
} else {
$seq = $policies->asSequence();
}
} catch (\Exception $e) {
$this->findings[] = new Finding(
self::type,
$findingLevel,
$isCritical ? 'critical' : 'warning',
'Malformed certificatePolicies extension \''.$e->getMessage().'\': '.
base64_encode($extensionDER)
);
Expand All @@ -50,16 +56,12 @@ public function __construct($extensionDER, $isCritical = false)
$policy = new CertificatePolicy($certPolicy);
$this->policies[] = $policy;
} catch (ParseException $e) {
if ($e->getMessage() == 'Unrecognised') {
$oid = $certPolicy->at(0)->asObjectIdentifier()->oid();
$oidName = OID::getName($oid);
$this->findings[] = new Finding(
self::type,
$findingLevel,
"Unrecognised certificatePolicy OID $oid ($oidName): ".
$this->findings[] = new Finding(
self::type,
$isCritical ? 'critical' : 'warning',
$e->getMessage() . ': '.
base64_encode($certPolicy->toDER())
);
}
);
}
}
}
Expand Down
27 changes: 17 additions & 10 deletions src/Certificate/CertificatePolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ class CertificatePolicy
private $description;
private $url;
private $name;
private $vendor;
private $oid;
private $binary;

const vendor_ETSI = 'ETSI';
const vendor_CAB = 'CA/Browser Forum';

public function __construct($policy)
{
$this->oid = $policy->at(0)->asObjectIdentifier()->oid();
Expand All @@ -28,40 +24,51 @@ public function __construct($policy)
$this->description =
'PSD2 qualified website authentication certificate';
$this->url = 'https://www.etsi.org/deliver/etsi_ts/119400_119499/119495/01.03.02_60/ts_119495v010302p.pdf#chapter-6.1';
$this->vendor = self::vendor_ETSI;
break;
case 'EVCP':
$this->description =
'Consistent with EV Certificates Guidelines issued by the CAB Forum';
$this->url = 'https://www.etsi.org/deliver/etsi_ts/102000_102099/102042/02.04.01_60/ts_102042v020401p.pdf#chapter-5.2';
$this->vendor = self::vendor_ETSI;
break;
case 'extended_validation':
$this->description =
'Certificate issued in compliance with the Extended Validation Guidelines';
$this->url = 'https://cabforum.org/object-registry/';
$this->vendor = self::vendor_CAB;
break;
case 'organization_validation':
$this->description =
'Compliant with Baseline Requirements – Organization identity asserted';
$this->url = 'https://cabforum.org/object-registry/';
$this->vendor = self::vendor_CAB;
break;
default:
throw new ParseException("Unrecognised", 1);
$vendor = OID::getVendorFromOID($this->oid);
if ($vendor == 'unknown') {
throw new ParseException("Certificate Policy from unknown vendor as oid '$this->oid'", 1);
} else {
throw new ParseException("Unrecognised '$vendor' Certificate Policy as oid '$this->oid'", 1);
}
break;
}
$this->binary = $policy->toDER();
}

public function getVendor()
{
return OID::getVendorFromOID($this->oid);
}

public function getOID()
{
return $this->oid;
}

public function getAttributes()
{
return [
'oid' => $this->oid,
'name' => $this->name,
'description' => $this->description,
'vendor' => $this->vendor,
'vendor' => $this->getVendor(),
'url' => $this->url
];
}
Expand Down
2 changes: 1 addition & 1 deletion src/Certificate/ExtendedKeyUsage.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function __construct($extensionDER, $isCritical = false)
if ($ekuName == 'unknown') {
$this->findings[] = new Finding(
self::type,
'critical',
$isCritical ? 'critical' : 'warning',
"Unrecognised ExtendedKeyUsage: ".
base64_encode($extensionDER)
);
Expand Down
5 changes: 5 additions & 0 deletions src/Certificate/OCSPNoCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use eIDASCertificate\ExtensionInterface;
use eIDASCertificate\Certificate\X509Certificate;
use eIDASCertificate\Finding;
use eIDASCertificate\ParseException;
use ASN1\Type\UnspecifiedType;

/**
*
Expand All @@ -21,6 +23,9 @@ class OCSPNoCheck implements ExtensionInterface

public function __construct($extensionDER, $isCritical = false)
{
if (UnspecifiedType::fromDER($extensionDER)->tag() <> 5) {
throw new ParseException("Malformed OCSPNoCheck Extension: ".base64_encode($extensionDER), 1);
}
$this->isCritical = $isCritical;
$this->findings[] = new Finding(
self::type,
Expand Down
2 changes: 1 addition & 1 deletion src/Certificate/PreCertPoison.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function __construct($extensionDER, $isCritical = false)
$this->isCritical = $isCritical;
$this->findings[] = new Finding(
self::type,
'warning',
'critical',
"This is a precert, so should not be seen in production"
);
$this->binary = $extensionDER;
Expand Down
188 changes: 188 additions & 0 deletions src/Certificate/SCTList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php

// With BIG thanks to Let's Encrypt for a detailed walkthrough
// https://letsencrypt.org/2018/04/04/sct-encoding.html

namespace eIDASCertificate\Certificate;

use eIDASCertificate\ExtensionInterface;
use eIDASCertificate\ParseException;
use eIDASCertificate\Certificate\X509Certificate;
use eIDASCertificate\Finding;
use ASN1\Type\UnspecifiedType;

/**
*
*/
class SCTList implements ExtensionInterface
{
private $binary;
private $findings = [];
private $isCritical;
private $list;
private $entries = [];

const type = 'sctList';
const oid = '1.3.6.1.4.1.11129.2.4.2';
const uri = 'https://tools.ietf.org/html/rfc6962#section-3.3';

public function __construct($extensionDER, $isCritical = false)
{
$this->isCritical = $isCritical;
$struct = UnspecifiedType::fromDER($extensionDER)->asOctetString()->string();
$length = unpack('nlength', substr($struct, 0, 2))['length'];
if (strlen($struct) < ($length + 2)) {
$this->findings[] = new Finding(
self::type,
$isCritical ? 'critical' : 'warning',
'Malformed SCT extension (not enough bytes): '.
base64_encode($extensionDER)
);
} elseif (strlen($struct) > ($length + 2)) {
$this->findings[] = new Finding(
self::type,
$isCritical ? 'critical' : 'warning',
'Malformed SCT extension (too many bytes): '.
base64_encode($extensionDER)
);
} else {
$offset = 2;
while ($offset < $length) {
$entryLength = unpack('nlen', substr($struct, $offset, 2))['len'];
$offset = $offset + 2;
$version = unpack('Cver', substr($struct, $offset++, 1))['ver'];
$logId = substr($struct, $offset, 32);
$offset = $offset + 32;
$at = unpack('Jat', substr($struct, $offset, 8))['at'];
$offset = $offset + 8;
// No extensions in v1, always equals '0000' so we skip
$offset = $offset + 2;
$hash = self::getHashAlgorithmFromByte(substr($struct, $offset++, 1));
$cipher = self::getCipherAlgorithmFromByte(substr($struct, $offset++, 1));
if ($cipher !== 'ecdsa') {
$this->findings[] = new Finding(
self::type,
$isCritical ? 'critical' : 'warning',
"Unsupported SCT Signature Algorithm '$cipher-$hash': ".
base64_encode($extensionDER)
);
// Since the remaining structure depends on key format we
// cannot parse, so discard all entries (partials may be
// more harmful)
$this->entries = [];
break;
}
$sigLength = unpack('nlen', substr($struct, $offset, 2))['len'];
$offset = $offset + 2;
$signature = substr($struct, $offset, $sigLength);
$offset = $offset + $sigLength;
$this->entries[] = [
'version' => $version + 1,
'logId' => bin2hex($logId),
'at' => $at / 1000,
'extensions' => [],
'cipherspec' => $cipher.'-'.$hash,
'signature' => base64_encode($signature)
];
}
$this->binary = $extensionDER;
}
}

public function getType()
{
return self::type;
}

public function getURI()
{
return self::uri;
}

public function getBinary()
{
return $this->binary;
}

public function getDescription()
{
return "This is a Signed Certificate Timestamp list extension";
}

public function getFindings()
{
return $this->findings;
}

public function getIsCritical()
{
return $this->isCritical;
}

public function setCertificate(X509Certificate $cert)
{
null;
}

public function getAttributes()
{
if (!empty($this->entries)) {
return [
'issuer' => ['SCTList' => $this->entries]
];
} else {
return [];
}
}

public static function getHashAlgorithmFromByte($byte)
{
switch ($byte) {
case chr(00):
return 'none';
break;
case chr(01):
return 'md5';
break;
case chr(02):
return 'sha1';
break;
case chr(03):
return 'sha224';
break;
case chr(04):
return 'sha256';
break;
case chr(05):
return 'sha384';
break;
case chr(06):
return 'sha512';
break;
default:
return 'unknown';
break;
}
}

public static function getCipherAlgorithmFromByte($byte)
{
switch ($byte) {
case chr(00):
return 'anonymous';
break;
case chr(01):
return 'rsa';
break;
case chr(02):
return 'dsa';
break;
case chr(03):
return 'ecdsa';
break;
default:
return 'unknown';
break;
}
}
}
2 changes: 1 addition & 1 deletion src/Certificate/SubjectAltName.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function __construct($extensionDER, $isCritical = false)
default:
$this->findings[] = new Finding(
self::type,
'warning',
$isCritical ? 'critical' : 'warning',
"Unrecognised subjectAltName extension: ".
base64_encode($extensionDER)
);
Expand Down
Loading

0 comments on commit f9930d5

Please sign in to comment.