Completed
Push — master ( 4acb0e...0c2fae )
by Sam
01:54
created

StreamSocketProvider::getRawCertificate()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
rs 9.2
cc 2
eloc 14
nc 4
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
    /**
43
     * StreamSocketProvider constructor.
44
     *
45
     * @param string  $hostname
46
     * @param int     $port           (optional)
47
     * @param int     $timeout        (optional)
48
     * @param boolean $verifyPeerName (optional)
49
     */
50
    public function __construct(
51
        $hostname,
52
        $port = self::DEFAULT_PORT,
53
        $timeout = self::DEFAULT_TIMEOUT_SECONDS,
54
        $verifyPeerName = true
55
    ) {
56
        $this->hostname       = $hostname;
57
        $this->port           = $port;
58
        $this->timeout        = $timeout;
59
        $this->verifyPeerName = $verifyPeerName;
60
    }
61
62
63
    /**
64
     * @inheritdoc
65
     */
66
    public function getRawCertificate()
67
    {
68
        try {
69
            $client = stream_socket_client(
70
                $this->getRequestUrl(),
71
                $errorNumber,
72
                $errorDescription,
73
                $this->timeout,
74
                STREAM_CLIENT_CONNECT,
75
                $this->getStreamContext()
76
            );
77
78
            $response = stream_context_get_params($client);
79
80
            return $response['options']['ssl']['peer_certificate'];
81
        } catch (\Throwable $e) {
82
            // Parse the error messages and throw appropriate exceptions if possible
83
            $this->handleThrowable($e);
84
85
            throw new ConnectionFailedException($e->getMessage());
86
        }
87
    }
88
89
90
    /**
91
     * @return resource
92
     */
93
    private function getStreamContext()
94
    {
95
        return stream_context_create([
96
            'ssl' => [
97
                'capture_peer_cert' => true,
98
                'verify_peer'       => false,
99
                'verify_peer_name'  => $this->verifyPeerName,
100
            ],
101
        ]);
102
    }
103
104
105
    /**
106
     * @return string
107
     */
108
    private function getRequestUrl()
109
    {
110
        return 'ssl://' . $this->hostname . ':' . $this->port;
111
    }
112
113
114
    /**
115
     * @param \Throwable $e
116
     *
117
     * @throws CertificateNotFoundException
118
     * @throws ConnectionTimeoutException
119
     * @throws DomainMismatchException
120
     * @throws NameResolutionException
121
     */
122
    private function handleThrowable(\Throwable $e)
123
    {
124
        $errorMessage = $e->getMessage();
125
126
        // Check for name resolution errors
127
        if (strpos($errorMessage, 'getaddrinfo failed') !== false) {
128
            throw new NameResolutionException();
129
        }
130
131
        // Check for unknown SSL protocol (usually means no SSL is configured on the endpoint)
132
        if (strpos($errorMessage, 'GET_SERVER_HELLO:unknown protocol') !== false) {
133
            throw new CertificateNotFoundException();
134
        }
135
136
        // Check for domain mismatches
137
        if (strpos($errorMessage, 'did not match expected') !== false) {
138
            throw new DomainMismatchException();
139
        }
140
141
        // Check for connection timeouts
142
        if (strpos($errorMessage, 'timed out') !== false) {
143
            throw new ConnectionTimeoutException();
144
        }
145
    }
146
147
}
148