Skip to content

Commit

Permalink
Get details from a certificate from string (#123)
Browse files Browse the repository at this point in the history
* createFromString method

* string annotation

* getOrganization method

* fix typo

* tests

* SslCertificate → self

* Apply fixes from StyleCI

* Delete .phpunit.result.cache

* add getOrganization method in readme

* added createFromFile method and descriptive variable names

* fully qualified class name in tests

* added createFromString and createFromFile to readme

* readme code block fix
  • Loading branch information
markvaneijk authored Mar 18, 2020
1 parent 900ec42 commit d96f343
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 6 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@
[![StyleCI](https://styleci.io/repos/64165510/shield)](https://styleci.io/repos/64165510)
[![Total Downloads](https://img.shields.io/packagist/dt/spatie/ssl-certificate.svg?style=flat-square)](https://packagist.org/packages/spatie/ssl-certificate)

The class provided by this package makes it incredibly easy to query the properties on an ssl certificate. Here's an example:

The class provided by this package makes it incredibly easy to query the properties on an ssl certificate. We have three options for fetching a certficate. Here's an example:
```php
use Spatie\SslCertificate\SslCertificate;

// fetch the certificate using an url
$certificate = SslCertificate::createForHostName('spatie.be');

// or from a certificate file
$certificate = SslCertificate::createFromFile($pathToCertificateFile);

// or from a string
$certificate = SslCertificate::createFromString($certificateData);

$certificate->getIssuer(); // returns "Let's Encrypt Authority X3"
$certificate->isValid(); // returns true if the certificate is currently valid
$certificate->expirationDate(); // returns an instance of Carbon
$certificate->expirationDate()->diffInDays(); // returns an int
$certificate->getSignatureAlgorithm(); // returns a string
$certificate->getOrganization(); // returns the organization name when available
```

Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource).
Expand Down Expand Up @@ -86,6 +93,14 @@ Returns the algorithm used for signing the certificate
$certificate->getSignatureAlgorithm(); // returns "RSA-SHA256"
```

### Getting the certificate's organization

Returns the organization belonging to the certificate

```php
$certificate->getOrganization(); // returns "Spatie BVBA"
```

### Getting the additional domain names

A certificate can cover multiple (sub)domains. Here's how to get them.
Expand Down Expand Up @@ -120,8 +135,6 @@ $certificate->validFromDate(); // returns an instance of Carbon
$certificate->expirationDate(); // returns an instance of Carbon
```



### Determining if the certificate is still valid

Returns true if the current Date and time is between `validFromDate` and `expirationDate`.
Expand Down
24 changes: 24 additions & 0 deletions src/SslCertificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ public static function createForHostName(string $url, int $timeout = 30): self
return Downloader::downloadCertificateFromUrl($url, $timeout);
}

public static function createFromFile(string $pathToCertificate): self
{
return $this->createFromString(file_get_contents($pathToCertificate));
}

public static function createFromString(string $certificatePem): self
{
$certificateFields = openssl_x509_parse($certificatePem);

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

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

public function __construct(
array $rawCertificateFields,
string $fingerprint = '',
Expand Down Expand Up @@ -78,6 +97,11 @@ public function getSignatureAlgorithm(): string
return $this->rawCertificateFields['signatureTypeSN'] ?? '';
}

public function getOrganization(): string
{
return $this->rawCertificateFields['subject']['O'] ?? '';
}

public function getFingerprint(): string
{
return $this->fingerprint;
Expand Down
216 changes: 216 additions & 0 deletions tests/SslCertificateFromStringTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<?php

namespace Spatie\SslCertificate\Test;

use Carbon\Carbon;
use PHPUnit\Framework\TestCase;
use Spatie\Snapshots\MatchesSnapshots;
use Spatie\SslCertificate\SslCertificate;

class SslCertificateFromStringTest extends TestCase
{
use MatchesSnapshots;

/** @var Spatie\SslCertificate\SslCertificate */
protected $certificate;

public function setUp(): void
{
parent::setUp();

Carbon::setTestNow(Carbon::create('2020', '01', '13', '03', '18', '13', 'utc'));

$certificate = file_get_contents(__DIR__.'/stubs/spatieCertificate.pem');

$this->certificate = SslCertificate::createFromString($certificate);
}

/** @test */
public function it_can_determine_the_issuer()
{
$this->assertSame("Let's Encrypt Authority X3", $this->certificate->getIssuer());
}

/** @test */
public function it_can_determine_the_domain()
{
$this->assertSame('analytics.spatie.be', $this->certificate->getDomain());
}

/** @test */
public function it_can_determine_the_signature_algorithm()
{
$this->assertSame('RSA-SHA256', $this->certificate->getSignatureAlgorithm());
}

/** @test */
public function it_can_determine_the_additional_domains()
{
$this->assertCount(1, $this->certificate->getAdditionalDomains());

$this->assertSame('analytics.spatie.be', $this->certificate->getAdditionalDomains()[0]);
}

/** @test */
public function it_can_determine_the_valid_from_date()
{
$this->assertInstanceOf(Carbon::class, $this->certificate->validFromDate());

$this->assertSame('2020-01-13 03:18:13', $this->certificate->validFromDate()->format('Y-m-d H:i:s'));
}

/** @test */
public function it_can_determine_the_expiration_date()
{
$this->assertInstanceOf(Carbon::class, $this->certificate->expirationDate());

$this->assertSame('2020-04-12 03:18:13', $this->certificate->expirationDate()->format('Y-m-d H:i:s'));
}

/** @test */
public function it_can_determine_if_the_certificate_is_valid()
{
Carbon::setTestNow(Carbon::create('2016', '05', '19', '16', '45', '00', 'utc'));
$this->assertFalse($this->certificate->isValid());

Carbon::setTestNow(Carbon::create('2020', '02', '13', '16', '51', '00', 'utc'));
$this->assertTrue($this->certificate->isValid());

Carbon::setTestNow(Carbon::create('2020', '03', '17', '16', '49', '00', 'utc'));
$this->assertTrue($this->certificate->isValid());

Carbon::setTestNow(Carbon::create('2016', '08', '17', '16', '51', '00', 'utc'));
$this->assertFalse($this->certificate->isValid());
}

/** @test */
public function it_can_determine_if_the_certificate_is_expired()
{
Carbon::setTestNow(Carbon::create('2020', '02', '13', '16', '45', '00', 'utc'));
$this->assertFalse($this->certificate->isExpired());

Carbon::setTestNow(Carbon::create('2020', '02', '13', '16', '51', '00', 'utc'));
$this->assertFalse($this->certificate->isExpired());

Carbon::setTestNow(Carbon::create('2020', '02', '17', '16', '49', '00', 'utc'));
$this->assertFalse($this->certificate->isExpired());

Carbon::setTestNow(Carbon::create('2020', '08', '17', '16', '51', '00', 'utc'));
$this->assertTrue($this->certificate->isExpired());
}

/** @test */
public function it_provides_a_fluent_interface_to_set_all_options()
{
$downloadedCertificate = SslCertificate::download()
->usingPort(443)
->setTimeout(30)
->forHost('spatie.be');

$this->assertSame('spatie.be', $downloadedCertificate->getDomain());
}

/** @test */
public function it_can_convert_the_certificate_to_json()
{
$this->assertMatchesJsonSnapshot($this->certificate->getRawCertificateFieldsJson());
}

/** @test */
public function it_can_convert_the_certificate_to_a_string()
{
$this->assertEquals(
$this->certificate->getRawCertificateFieldsJson(),
(string) $this->certificate
);
}

/** @test */
public function it_can_get_the_hash_of_a_certificate()
{
$this->assertEquals('0547c1a78dcdbe96f907aaaf42db5b8f', $this->certificate->getHash());
}

/** @test */
public function it_can_get_all_domains()
{
$this->assertEquals([
0 => 'analytics.spatie.be',
], $this->certificate->getDomains());
}

/** @test */
public function it_can_get_the_days_until_the_expiration_date()
{
$this->assertEquals(90, $this->certificate->daysUntilExpirationDate());
}

/** @test */
public function it_can_determine_if_it_is_self_signed()
{
$this->assertFalse($this->certificate->isSelfSigned());
}

/** @test */
public function it_can_determine_if_it_uses_sha1_hasing()
{
$this->assertFalse($this->certificate->usesSha1Hash());
}

/** @test */
public function it_can_determine_if_the_certificate_has_a_certain_domain()
{
$this->assertTrue($this->certificate->containsDomain('analytics.spatie.be'));

$this->assertFalse($this->certificate->containsDomain('www.example.com'));
$this->assertFalse($this->certificate->containsDomain('notreallyspatie.be'));
$this->assertFalse($this->certificate->containsDomain('spatie.be.example.com'));
}

/** @test */
public function does_not_notify_on_wrong_domains()
{
$rawCertificateFields = json_decode(
file_get_contents(__DIR__.'/stubs/certificateWithRandomWildcardDomains.json'),
true
);

$this->certificate = new SslCertificate($rawCertificateFields);

$this->assertFalse($this->certificate->appliesToUrl('https://coinmarketcap.com'));
}

/** @test */
public function it_correctly_compares_uppercase_domain_names()
{
$rawCertificateFields = json_decode(
file_get_contents(__DIR__.'/stubs/certificateWithUppercaseDomains.json'),
true
);

$this->certificate = new SslCertificate($rawCertificateFields);

$this->assertTrue($this->certificate->appliesToUrl('spatie.be'));
$this->assertTrue($this->certificate->appliesToUrl('www.spatie.be'));
}

/** @test */
public function it_correctly_identifies_pre_certificates()
{
$rawCertificateFieldsNormalCertificate = json_decode(
file_get_contents(__DIR__.'/stubs/spatieCertificateFields.json'),
true
);

$rawCertificateFieldsPreCertificate = json_decode(
file_get_contents(__DIR__.'/stubs/preCertificate.json'),
true
);

$certificateNormal = new SslCertificate($rawCertificateFieldsNormalCertificate);
$certificatePreCertificate = new SslCertificate($rawCertificateFieldsPreCertificate);

$this->assertFalse($certificateNormal->isPreCertificate());
$this->assertTrue($certificatePreCertificate->isPreCertificate());
}
}
4 changes: 2 additions & 2 deletions tests/SslCertificateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class SslCertificateTest extends TestCase
{
use MatchesSnapshots;

/** @var SslCertificate */
/** @var Spatie\SslCertificate\SslCertificate */
protected $certificate;

public function setUp(): void
Expand All @@ -26,7 +26,7 @@ public function setUp(): void
$this->certificate = new SslCertificate($rawCertificateFields);
}

public function it_can_get_the_raw_certificate_fiels()
public function it_can_get_the_raw_certificate_fields()
{
$rawCertificateFields = $this->certificate->getRawCertificateFields();

Expand Down
Loading

0 comments on commit d96f343

Please sign in to comment.