DNSOverHTTPS::get()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 17
nc 3
nop 2
dl 0
loc 29
ccs 0
cts 14
cp 0
crap 12
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * DNSOverHTTPS
5
 *
6
 * MIT License
7
 *
8
 * Copyright (c) 2018 wutno (#/g/punk - Rizon)
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
namespace Zwartpet\PHPCertificateToolbox\DNSValidator;
30
31
use GuzzleHttp\Client;
32
use Zwartpet\PHPCertificateToolbox\Exception\RuntimeException;
33
use GuzzleHttp\Exception\BadResponseException;
34
35
/**
36
 * DNSOverHTTPS implements DNSValidatorInterface using Google's DNS-over-HTTPS service
37
 * @package Zwartpet\PHPCertificateToolbox\DNSValidator
38
 */
39
class DNSOverHTTPS implements DNSValidatorInterface
40
{
41
42
    const DNS_GOOGLE     = 'https://dns.google.com/resolve';
43
    const DNS_MOZILLA    = 'https://mozilla.cloudflare-dns.com/dns-query';
44
    const DNS_CLOUDFLARE = 'https://cloudflare-dns.com/dns-query';
45
46
    /**
47
     * What DNS-over-HTTPS service to use
48
     *
49
     * @var null|string
50
     */
51
    private $baseURI;
52
53
    /**
54
     * Guzzle client handler
55
     *
56
     * @var Client object
57
     */
58
    private $client;
59
60
    /**
61
     * DNSOverHTTPS constructor.
62
     * @param string|null $baseURI
63
     */
64 6
    public function __construct(string $baseURI = null)
65
    {
66
        //Default to Google, seems like a safe bet...
67 6
        if ($baseURI === null) {
68 6
            $this->baseURI = self::DNS_GOOGLE;
69
        } else {
70
            $this->baseURI = $baseURI;
71
        }
72
73 6
        $this->client = new Client([
74 6
            'base_uri' => $this->baseURI
75
        ]);
76 6
    }
77
78
    public function checkChallenge($domain, $requiredDigest) : bool
79
    {
80
        $hostname = '_acme-challenge.' . str_replace('*.', '', $domain);
81
82
        $records = $this->get($hostname, 'TXT');
83
        if ($records->Status == 0) {
84
            foreach ($records->Answer as $record) {
85
                if ((rtrim($record->name, ".") == $hostname) &&
86
                    ($record->type == 16) &&
87
                    (trim($record->data, '"') == $requiredDigest)) {
88
                    return true;
89
                }
90
            }
91
        }
92
93
        return false;
94
    }
95
96
    /**
97
     * @param string $name
98
     * @param string $type per experimental spec this can be string OR int, we force string
99
     * @return \stdClass
100
     */
101
    public function get(string $name, string $type) : \stdClass
102
    {
103
        $query = [
104
            'query' => [
105
                'name'               => $name,
106
                'type'               => $type,
107
                'edns_client_subnet' => '0.0.0.0/0',            //disable geotagged dns results
108
                'ct'                 => 'application/dns-json', //cloudflare requires this
109
            ],
110
            'headers' => [
111
                'Accept' => 'application/dns-json'
112
            ]
113
        ];
114
115
        try {
116
            $response = $this->client->get(null, $query);
117
        } catch (BadResponseException $e) {
118
            throw new RuntimeException("GET {$this->baseURI} failed", 0, $e);
119
        }
120
121
        $decode = json_decode($response->getBody());
122
123
        if (json_last_error() !== JSON_ERROR_NONE) {
124
            throw new RuntimeException(
125
                'Attempted to decode expected JSON response, however server returned something unexpected.'
126
            );
127
        }
128
129
        return $decode;
130
    }
131
}
132