Completed
Push — master ( 0c2fae...16e774 )
by Sam
01:52
created

StreamSocketProvider::getRawCertificate()   B

Complexity

Conditions 4
Paths 10

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 28
rs 8.5806
cc 4
eloc 17
nc 10
nop 0
1
<?php
2
3
namespace Jalle19\CertificateParser\Provider;
4
5
use Jalle19\CertificateParser\Exception\ConnectionTimeoutException;
6
use Jalle19\CertificateParser\Exception\DomainMismatchException;
7
use Jalle19\CertificateParser\Exception\NameResolutionException;
8
use Jalle19\CertificateParser\Exception\CertificateNotFoundException;
9
use Jalle19\CertificateParser\Exception\ConnectionFailedException;
10
11
/**
12
 * Class StreamSocketProvider
13
 * @package Jalle19\CertificateParser\Provider
14
 */
15
class StreamSocketProvider implements ProviderInterface
16
{
17
18
    const DEFAULT_TIMEOUT_SECONDS = 15;
19
    const DEFAULT_PORT            = 443;
20
21
    /**
22
     * @var string
23
     */
24
    private $hostname;
25
26
    /**
27
     * @var int
28
     */
29
    private $port;
30
31
    /**
32
     * @var int
33
     */
34
    private $timeout;
35
36
    /**
37
     * @var boolean
38
     */
39
    private $verifyPeerName;
40
41
    /**
42
     * @var array maps partial error messages to exception types
43
     */
44
    private static $errorExceptionMap = [
45
        // Check for name resolution errors
46
        'getaddrinfo failed'                => NameResolutionException::class,
47
        // Check for unknown SSL protocol (usually means no SSL is configured on the endpoint)
48
        'GET_SERVER_HELLO:unknown protocol' => CertificateNotFoundException::class,
49
        // Check for domain mismatches
50
        'did not match expected'            => DomainMismatchException::class,
51
        // Check for connection timeouts
52
        'timed out'                         => ConnectionTimeoutException::class,
53
    ];
54
55
56
    /**
57
     * StreamSocketProvider constructor.
58
     *
59
     * @param string  $hostname
60
     * @param int     $port           (optional)
61
     * @param int     $timeout        (optional)
62
     * @param boolean $verifyPeerName (optional)
63
     */
64
    public function __construct(
65
        $hostname,
66
        $port = self::DEFAULT_PORT,
67
        $timeout = self::DEFAULT_TIMEOUT_SECONDS,
68
        $verifyPeerName = true
69
    ) {
70
        $this->hostname       = $hostname;
71
        $this->port           = $port;
72
        $this->timeout        = $timeout;
73
        $this->verifyPeerName = $verifyPeerName;
74
    }
75
76
77
    /**
78
     * @inheritdoc
79
     */
80
    public function getRawCertificate()
81
    {
82
        try {
83
            $client = stream_socket_client(
84
                $this->getRequestUrl(),
85
                $errorNumber,
86
                $errorDescription,
87
                $this->timeout,
88
                STREAM_CLIENT_CONNECT,
89
                $this->getStreamContext()
90
            );
91
92
            $response = stream_context_get_params($client);
93
94
            return $response['options']['ssl']['peer_certificate'];
95
        } catch (\Throwable $e) {
96
            $errorMessage = $e->getMessage();
97
98
            // Throw mapped exceptions
99
            foreach (self::$errorExceptionMap as $needle => $exceptionClass) {
100
                if (strpos($errorMessage, $needle) !== false) {
101
                    throw new $exceptionClass();
102
                }
103
            }
104
105
            throw new ConnectionFailedException($e->getMessage());
106
        }
107
    }
108
109
110
    /**
111
     * @return resource
112
     */
113
    private function getStreamContext()
114
    {
115
        return stream_context_create([
116
            'ssl' => [
117
                'capture_peer_cert' => true,
118
                'verify_peer'       => false,
119
                'verify_peer_name'  => $this->verifyPeerName,
120
            ],
121
        ]);
122
    }
123
124
125
    /**
126
     * @return string
127
     */
128
    private function getRequestUrl()
129
    {
130
        return 'ssl://' . $this->hostname . ':' . $this->port;
131
    }
132
133
}
134