Passed
Push — master ( e7b604...7b9c2c )
by Daniel
01:49
created

OpenSslCryptoDecoder::loadPublicKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
ccs 4
cts 5
cp 0.8
rs 10
cc 2
nc 2
nop 1
crap 2.032
1
<?php
2
3
namespace Selective\XmlDSig;
4
5
use OpenSSLAsymmetricKey;
6
use Selective\XmlDSig\Exception\XmlSignatureValidatorException;
7
8
final class OpenSslCryptoDecoder implements CryptoDecoderInterface
9
{
10
    //
11
    // RSA (PKCS#1 v1.5) Identifier
12
    // https://www.w3.org/TR/xmldsig-core/#sec-PKCS1
13
    //
14
    private const SHA1_URL = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
15
    private const SHA224_URL = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224';
16
    private const SHA256_URL = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
17
    private const SHA384_URL = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
18
    private const SHA512_URL = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
19
20
    /**
21
     * @var OpenSSLAsymmetricKey|resource|null
22
     */
23
    private $publicKeyId = null;
24
25
    /**
26
     * Read and load the pfx file.
27
     *
28
     * @param string $pkcs12 The certificate store data
29
     * @param string $password The encryption password for unlocking the PKCS12 file
30
     *
31
     * @throws XmlSignatureValidatorException
32
     *
33
     * @return void
34
     */
35 1
    public function loadPfx(string $pkcs12, string $password): void
36
    {
37 1
        $status = openssl_pkcs12_read($pkcs12, $certificates, $password);
38
39 1
        if (!$status) {
40
            throw new XmlSignatureValidatorException('Invalid PFX password');
41
        }
42
43 1
        $publicKeyId = openssl_get_publickey($certificates['cert']);
44
45 1
        if ($publicKeyId === false) {
46
            throw new XmlSignatureValidatorException('Invalid public key');
47
        }
48
49 1
        $this->publicKeyId = $publicKeyId;
50
    }
51
52
    /**
53
     * Load the public key content.
54
     *
55
     * @param string $publicKey The public key data
56
     *
57
     * @throws XmlSignatureValidatorException
58
     *
59
     * @return void
60
     */
61 1
    public function loadPublicKey(string $publicKey): void
62
    {
63 1
        $publicKeyId = openssl_get_publickey($publicKey);
64
65 1
        if (!$publicKeyId) {
0 ignored issues
show
introduced by
$publicKeyId is of type OpenSSLAsymmetricKey, thus it always evaluated to true.
Loading history...
66
            throw new XmlSignatureValidatorException('Invalid public key');
67
        }
68
69 1
        $this->publicKeyId = $publicKeyId;
70
    }
71
72
    /**
73
     * Destructor.
74
     */
75 2
    public function __destruct()
76
    {
77
        // Free the key from memory
78
        // PHP 8 deprecates openssl_free_key and automatically destroys the key instance when it goes out of scope.
79 2
        if ($this->publicKeyId && version_compare(PHP_VERSION, '8.0.0', '<')) {
80
            openssl_free_key($this->publicKeyId);
81
        }
82
    }
83
84 2
    public function verify(string $data, string $signature, string $algorithm): bool
85
    {
86 2
        if (!$this->publicKeyId) {
87
            throw new XmlSignatureValidatorException('No public key provided');
88
        }
89
90 2
        $algo = $this->mapUrlToOpenSslAlgoCode($algorithm);
91
92 2
        $status = openssl_verify($data, $signature, $this->publicKeyId, $algo);
93
94 2
        if ($status !== 1) {
95
            // The XML signature is not valid
96
            return false;
97
        }
98
99 2
        return true;
100
    }
101
102 2
    private function mapUrlToOpenSslAlgoCode(string $algorithm): int
103
    {
104
        switch ($algorithm) {
105
            case self::SHA1_URL:
106 2
                return OPENSSL_ALGO_SHA1;
107
            case self::SHA224_URL:
108 2
                return OPENSSL_ALGO_SHA224;
109
            case self::SHA256_URL:
110 2
                return OPENSSL_ALGO_SHA256;
111
            case self::SHA384_URL:
112 2
                return OPENSSL_ALGO_SHA384;
113
            case self::SHA512_URL:
114 2
                return OPENSSL_ALGO_SHA512;
115
            default:
116
                throw new XmlSignatureValidatorException("Cannot verify: Unsupported Algorithm <$algorithm>");
117
        }
118
    }
119
120
    /**
121
     * Map algo to OpenSSL method name.
122
     *
123
     * @param string $algorithm The url
124
     *
125
     * @return string The name of the OpenSSL algorithm
126
     */
127 2
    private function mapUrlToOpenSslDigestAlgo(string $algorithm): string
128
    {
129
        switch ($algorithm) {
130
            case self::SHA1_URL:
131 2
                return 'sha1';
132
            case self::SHA224_URL:
133 2
                return 'sha224';
134
            case self::SHA256_URL:
135 2
                return 'sha256';
136
            case self::SHA384_URL:
137 2
                return 'sha384';
138
            case self::SHA512_URL:
139 2
                return 'sha512';
140
            default:
141
                throw new XmlSignatureValidatorException("Cannot verify: Unsupported Algorithm <$algorithm>");
142
        }
143
    }
144
145 2
    public function computeDigest(string $data, string $algorithm): string
146
    {
147 2
        $digestAlgo = $this->mapUrlToOpenSslDigestAlgo($algorithm);
148 2
        $digest = openssl_digest($data, $digestAlgo, true);
149
150 2
        if ($digest === false) {
151
            throw new XmlSignatureValidatorException('Invalid digest value');
152
        }
153
154 2
        return $digest;
155
    }
156
}
157