Skip to content

Commit

Permalink
Introduce SslCertificate::getPublicKeyAlgorithm() and SslCertificate:…
Browse files Browse the repository at this point in the history
…:getPublicKeySize() (#182)

* Update expected values for unit-testing

* Introduce SslCertificate::getPublicKeyAlgorithm() and SslCertificate::getPublicKeySize()

* Changed SslCertificate::toArray() for publicKeyDetail

* Change arguments of SslCertificate::__construct()

* Replace 'switch...case...' with match expression

* Fix bug

* Use default in match expression
  • Loading branch information
JoeHorn authored Sep 4, 2023
1 parent 2c7c7d5 commit a602234
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 12 deletions.
5 changes: 4 additions & 1 deletion src/Downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,17 @@ public function getCertificates(string $hostName): array
$certificates = array_map(function ($certificate) use ($remoteAddress) {
$certificateFields = openssl_x509_parse($certificate);

$publicKeyDetail = openssl_pkey_get_details(openssl_pkey_get_public($certificate));

$fingerprint = openssl_x509_fingerprint($certificate);
$fingerprintSha256 = openssl_x509_fingerprint($certificate, 'sha256');

return new SslCertificate(
$certificateFields,
$fingerprint,
$fingerprintSha256,
$remoteAddress
$remoteAddress,
$publicKeyDetail,
);
}, $fullCertificateChain);

Expand Down
25 changes: 24 additions & 1 deletion src/SslCertificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ public static function createFromString(string $certificatePem): self
{
$certificateFields = openssl_x509_parse($certificatePem);

$publicKeyDetail = openssl_pkey_get_details(openssl_pkey_get_public($certificatePem));

$fingerprint = openssl_x509_fingerprint($certificatePem);
$fingerprintSha256 = openssl_x509_fingerprint($certificatePem, 'sha256');

return new self(
$certificateFields,
$fingerprint,
$fingerprintSha256
$fingerprintSha256,
'',
$publicKeyDetail,
);
}

Expand All @@ -50,6 +54,7 @@ public static function createFromArray(array $properties): self
$properties['fingerprint'],
$properties['fingerprintSha256'],
$properties['remoteAddress'],
$properties['publicKeyDetail'],
);
}

Expand All @@ -65,6 +70,7 @@ public function __construct(
protected string $fingerprint = '',
private string $fingerprintSha256 = '',
private string $remoteAddress = '',
private array $publicKeyDetail = [],
) {
//
}
Expand Down Expand Up @@ -128,6 +134,22 @@ public function getAdditionalDomains(): array
return array_map(fn (string $domain) => str_replace('DNS:', '', $domain), $additionalDomains);
}

public function getPublicKeyAlgorithm(): string
{
return match($this->publicKeyDetail['type'] ?? -1) {
OPENSSL_KEYTYPE_RSA => 'RSA',
OPENSSL_KEYTYPE_DSA => 'DSA',
OPENSSL_KEYTYPE_DH => 'DH',
OPENSSL_KEYTYPE_EC => 'EC',
default => 'Unknown',
};
}

public function getPublicKeySize(): int
{
return intval($this->publicKeyDetail['bits'] ?? 0);
}

public function validFromDate(): Carbon
{
return Carbon::createFromTimestampUTC($this->rawCertificateFields['validFrom_time_t']);
Expand Down Expand Up @@ -280,6 +302,7 @@ public function toArray(): array
'fingerprint' => $this->fingerprint,
'fingerprintSha256' => $this->fingerprintSha256,
'remoteAddress' => $this->remoteAddress,
'publicKeyDetail' => $this->publicKeyDetail,
];
}

Expand Down
10 changes: 9 additions & 1 deletion tests/SslCertificateFromStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
->expect(fn () => $this->certificate->getSignatureAlgorithm())
->toEqual('RSA-SHA256');

it('can determine the public key algorithm')
->expect(fn () => $this->certificate->getPublicKeyAlgorithm())
->toEqual("RSA");

it('can determine the public key size')
->expect(fn () => $this->certificate->getPublicKeySize())
->toEqual(4096);

it('can determine the additional domains')
->expect(fn () => $this->certificate->getAdditionalDomains())->toHaveCount(1)
->and(fn () => $this->certificate->getAdditionalDomains()[0])->toEqual('analytics.spatie.be');
Expand Down Expand Up @@ -100,7 +108,7 @@
});

it('can get the hash of a certificate', function () {
expect($this->certificate->getHash())->toEqual('0547c1a78dcdbe96f907aaaf42db5b8f');
expect($this->certificate->getHash())->toEqual('025580390a842a6564e9f24b81a5e000');
})->skip(getenv('GITHUB_ACTIONS'), 'Github Actions results in different output');

it('can get all domains')
Expand Down
12 changes: 10 additions & 2 deletions tests/SslCertificateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@
->expect(fn () => $this->certificate->getSignatureAlgorithm())
->toEqual('RSA-SHA256');

it('can determine the additional domains', function () {
it('can determine the public key algorithm')
->expect(fn () => $this->certificate->getPublicKeyAlgorithm())
->toEqual("Unknown");

it('can determine the public key size')
->expect(fn () => $this->certificate->getPublicKeySize())
->toEqual(0);

it('can determine the additional domains', function () {
expect($this->certificate->getAdditionalDomains())->toHaveCount(3)
->and($this->certificate->getAdditionalDomains()[0])->toEqual('spatie.be')
->and($this->certificate->getAdditionalDomains()[1])->toEqual('www.spatie.be')
Expand Down Expand Up @@ -156,7 +164,7 @@

it('can get the hash of a certificate')
->expect(fn () => $this->certificate->getHash())
->toEqual('7469a491af5f1a5cc5dc5775608ec0ab');
->toEqual('55353c8a63ab7669bb37a2692d2b0f3d');

it('can get all domains', function () {
expect($this->certificate->getDomains())->toMatchArray([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@
"extendedKeyUsage": "TLS Web Server Authentication, TLS Web Client Authentication",
"basicConstraints": "CA:FALSE",
"subjectKeyIdentifier": "84:93:3A:3C:73:0D:EA:A6:59:3E:79:21:80:96:FF:95:A1:B7:0F:5D",
"authorityKeyIdentifier": "keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1\n",
"authorityInfoAccess": "OCSP - URI:http:\/\/ocsp.int-x3.letsencrypt.org\nCA Issuers - URI:http:\/\/cert.int-x3.letsencrypt.org\/\n",
"authorityKeyIdentifier": "A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1",
"authorityInfoAccess": "OCSP - URI:http:\/\/ocsp.int-x3.letsencrypt.org\nCA Issuers - URI:http:\/\/cert.int-x3.letsencrypt.org\/",
"subjectAltName": "DNS:analytics.spatie.be",
"certificatePolicies": "Policy: 2.23.140.1.2.1\nPolicy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http:\/\/cps.letsencrypt.org\n",
"certificatePolicies": "Policy: 2.23.140.1.2.1\nPolicy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http:\/\/cps.letsencrypt.org",
"ct_precert_scts": "Signed Certificate Timestamp:\n Version : v1 (0x0)\n Log ID : 5E:A7:73:F9:DF:56:C0:E7:B5:36:48:7D:D0:49:E0:32:\n 7A:91:9A:0C:84:A1:12:12:84:18:75:96:81:71:45:58\n Timestamp : Jan 13 04:18:13.691 2020 GMT\n Extensions: none\n Signature : ecdsa-with-SHA256\n 30:46:02:21:00:B8:E0:C8:73:90:2E:59:15:28:D2:7E:\n 5F:63:A7:14:BD:0E:A6:40:42:52:C5:20:B1:30:A1:21:\n 25:1F:C3:CB:D0:02:21:00:DA:4D:D5:EC:E5:28:F2:24:\n 79:1C:F3:02:B0:A0:C1:DD:9B:65:0F:95:19:52:9B:27:\n 68:DA:72:84:83:E9:B4:18\nSigned Certificate Timestamp:\n Version : v1 (0x0)\n Log ID : B2:1E:05:CC:8B:A2:CD:8A:20:4E:87:66:F9:2B:B9:8A:\n 25:20:67:6B:DA:FA:70:E7:B2:49:53:2D:EF:8B:90:5E\n Timestamp : Jan 13 04:18:13.670 2020 GMT\n Extensions: none\n Signature : ecdsa-with-SHA256\n 30:44:02:20:41:9C:70:D9:1F:60:9E:C9:65:53:E0:2F:\n B8:A7:FD:29:D7:33:FF:F8:73:BC:1F:D3:C2:1C:85:80:\n F2:70:10:3B:02:20:28:10:E5:4D:E3:90:C8:04:58:58:\n 51:88:B5:08:34:CF:CA:9D:52:1B:86:56:4C:04:3A:B3:\n 4E:D7:BE:3D:13:2E"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@
"extendedKeyUsage": "TLS Web Server Authentication, TLS Web Client Authentication",
"basicConstraints": "CA:FALSE",
"subjectKeyIdentifier": "2E:D7:F6:B0:5C:89:EB:58:71:F8:B8:6D:02:5C:FE:22:90:C6:65:E0",
"authorityKeyIdentifier": "keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1\n",
"authorityInfoAccess": "OCSP - URI:http:\/\/ocsp.int-x3.letsencrypt.org\/\nCA Issuers - URI:http:\/\/cert.int-x3.letsencrypt.org\/\n",
"authorityKeyIdentifier": "A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1",
"authorityInfoAccess": "OCSP - URI:http:\/\/ocsp.int-x3.letsencrypt.org\/\nCA Issuers - URI:http:\/\/cert.int-x3.letsencrypt.org\/",
"subjectAltName": "DNS:spatie.be, DNS:www.spatie.be, DNS:*.otherdomain.com",
"certificatePolicies": "Policy: 2.23.140.1.2.1\nPolicy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http:\/\/cps.letsencrypt.org\n User Notice:\n Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https:\/\/letsencrypt.org\/repository\/\n"
"certificatePolicies": "Policy: 2.23.140.1.2.1\nPolicy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http:\/\/cps.letsencrypt.org\n User Notice:\n Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https:\/\/letsencrypt.org\/repository\/"
}
}
2 changes: 1 addition & 1 deletion tests/stubs/spatieCertificateFields.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"\/CN=spatie.be","subject":{"CN":"spatie.be"},"hash":"374a1154","issuer":{"C":"US","O":"Let's Encrypt","CN":"Let's Encrypt Authority X3"},"version":2,"serialNumber":"267977138471675133728493439824231787816484","validFrom":"160519165000Z","validTo":"160817165000Z","validFrom_time_t":1463676600,"validTo_time_t":1471452600,"signatureTypeSN":"RSA-SHA256","signatureTypeLN":"sha256WithRSAEncryption","signatureTypeNID":668,"purposes":{"1":[true,false,"sslclient"],"2":[true,false,"sslserver"],"3":[true,false,"nssslserver"],"4":[false,false,"smimesign"],"5":[false,false,"smimeencrypt"],"6":[false,false,"crlsign"],"7":[true,true,"any"],"8":[true,false,"ocsphelper"]},"extensions":{"keyUsage":"Digital Signature, Key Encipherment","extendedKeyUsage":"TLS Web Server Authentication, TLS Web Client Authentication","basicConstraints":"CA:FALSE","subjectKeyIdentifier":"2E:D7:F6:B0:5C:89:EB:58:71:F8:B8:6D:02:5C:FE:22:90:C6:65:E0","authorityKeyIdentifier":"keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1\n","authorityInfoAccess":"OCSP - URI:http:\/\/ocsp.int-x3.letsencrypt.org\/\nCA Issuers - URI:http:\/\/cert.int-x3.letsencrypt.org\/\n","subjectAltName":"DNS:spatie.be, DNS:www.spatie.be, DNS:*.otherdomain.com","certificatePolicies":"Policy: 2.23.140.1.2.1\nPolicy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http:\/\/cps.letsencrypt.org\n User Notice:\n Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https:\/\/letsencrypt.org\/repository\/\n"}}
{"name":"\/CN=spatie.be","subject":{"CN":"spatie.be"},"hash":"374a1154","issuer":{"C":"US","O":"Let's Encrypt","CN":"Let's Encrypt Authority X3"},"version":2,"serialNumber":"267977138471675133728493439824231787816484","validFrom":"160519165000Z","validTo":"160817165000Z","validFrom_time_t":1463676600,"validTo_time_t":1471452600,"signatureTypeSN":"RSA-SHA256","signatureTypeLN":"sha256WithRSAEncryption","signatureTypeNID":668,"purposes":{"1":[true,false,"sslclient"],"2":[true,false,"sslserver"],"3":[true,false,"nssslserver"],"4":[false,false,"smimesign"],"5":[false,false,"smimeencrypt"],"6":[false,false,"crlsign"],"7":[true,true,"any"],"8":[true,false,"ocsphelper"]},"extensions":{"keyUsage":"Digital Signature, Key Encipherment","extendedKeyUsage":"TLS Web Server Authentication, TLS Web Client Authentication","basicConstraints":"CA:FALSE","subjectKeyIdentifier":"2E:D7:F6:B0:5C:89:EB:58:71:F8:B8:6D:02:5C:FE:22:90:C6:65:E0","authorityKeyIdentifier":"A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1","authorityInfoAccess":"OCSP - URI:http:\/\/ocsp.int-x3.letsencrypt.org\/\nCA Issuers - URI:http:\/\/cert.int-x3.letsencrypt.org\/","subjectAltName":"DNS:spatie.be, DNS:www.spatie.be, DNS:*.otherdomain.com","certificatePolicies":"Policy: 2.23.140.1.2.1\nPolicy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http:\/\/cps.letsencrypt.org\n User Notice:\n Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https:\/\/letsencrypt.org\/repository\/"}}

0 comments on commit a602234

Please sign in to comment.