Signer   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 108
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 13
eloc 44
c 2
b 0
f 0
dl 0
loc 108
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A jwk() 0 16 3
A __construct() 0 3 1
A getBase64Encoder() 0 3 1
A jws() 0 15 2
A kty() 0 5 1
A createWithBase64SafeEncoder() 0 3 1
A kid() 0 15 2
A sign() 0 14 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the LetsEncrypt ACME client.
7
 *
8
 * @author    Ivanov Aleksandr <[email protected]>
9
 * @copyright 2019-2020
10
 * @license   https://github.com/misantron/letsencrypt-client/blob/master/LICENSE MIT License
11
 */
12
13
namespace LetsEncrypt\Helper;
14
15
use LetsEncrypt\Exception\KeyPairException;
16
17
class Signer implements SignerInterface
18
{
19
    /**
20
     * @var Base64SafeEncoder
21
     */
22
    private $base64Encoder;
23
24
    public function __construct(Base64SafeEncoder $base64Encoder)
25
    {
26
        $this->base64Encoder = $base64Encoder;
27
    }
28
29
    public static function createWithBase64SafeEncoder(): self
30
    {
31
        return new static(new Base64SafeEncoder());
32
    }
33
34
    public function getBase64Encoder(): Base64SafeEncoder
35
    {
36
        return $this->base64Encoder;
37
    }
38
39
    /**
40
     * @throws KeyPairException
41
     */
42
    public function jws(array $payload, string $url, string $nonce, string $privateKeyPath): array
43
    {
44
        $privateKey = openssl_pkey_get_private('file://' . $privateKeyPath);
45
        if ($privateKey === false) {
46
            throw KeyPairException::privateKeyInvalid();
47
        }
48
49
        $protected = [
50
            'alg' => 'RS256',
51
            'jwk' => $this->jwk($privateKeyPath),
52
            'nonce' => $nonce,
53
            'url' => $url,
54
        ];
55
56
        return $this->sign($protected, $payload, $privateKey);
57
    }
58
59
    /**
60
     * @throws KeyPairException
61
     */
62
    public function kid(array $payload, string $kid, string $url, string $nonce, string $privateKeyPath): array
63
    {
64
        $privateKey = openssl_pkey_get_private('file://' . $privateKeyPath);
65
        if ($privateKey === false) {
66
            throw KeyPairException::privateKeyInvalid();
67
        }
68
69
        $protected = [
70
            'alg' => 'RS256',
71
            'kid' => $kid,
72
            'nonce' => $nonce,
73
            'url' => $url,
74
        ];
75
76
        return $this->sign($protected, $payload, $privateKey);
77
    }
78
79
    /**
80
     * @throws KeyPairException
81
     */
82
    public function kty(string $privateKeyPath): string
83
    {
84
        $header = json_encode($this->jwk($privateKeyPath));
85
86
        return $this->base64Encoder->hashEncode($header);
87
    }
88
89
    /**
90
     * @throws KeyPairException
91
     */
92
    public function jwk(string $privateKeyPath): array
93
    {
94
        $privateKey = openssl_pkey_get_private('file://' . $privateKeyPath);
95
        if ($privateKey === false) {
96
            throw KeyPairException::privateKeyInvalid();
97
        }
98
99
        $details = openssl_pkey_get_details($privateKey);
100
        if ($details === false) {
101
            throw KeyPairException::privateKeyDetailsError();
102
        }
103
104
        return [
105
            'kty' => 'RSA',
106
            'n' => $this->base64Encoder->encode($details['rsa']['n']),
107
            'e' => $this->base64Encoder->encode($details['rsa']['e']),
108
        ];
109
    }
110
111
    private function sign(array $protected, array $payload, $privateKey): array
112
    {
113
        // empty payload array must be encoded to empty string
114
        $payloadEncoded = $payload !== [] ?
115
            $this->base64Encoder->encode(str_replace('\\/', '/', json_encode($payload))) :
116
            '';
117
        $protectedEncoded = $this->base64Encoder->encode(json_encode($protected));
118
119
        openssl_sign($protectedEncoded . '.' . $payloadEncoded, $signature, $privateKey, OPENSSL_ALGO_SHA256);
120
121
        return [
122
            'protected' => $protectedEncoded,
123
            'payload' => $payloadEncoded,
124
            'signature' => $this->base64Encoder->encode($signature),
125
        ];
126
    }
127
}
128