Passed
Push — master ( c1e7da...0d57b7 )
by Andrew
01:32
created

CryptKey::getPassPhrase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * Cryptography key holder.
4
 *
5
 * @author      Julián Gutiérrez <[email protected]>
6
 * @copyright   Copyright (c) Alex Bilbie
7
 * @license     http://mit-license.org/
8
 *
9
 * @link        https://github.com/thephpleague/oauth2-server
10
 */
11
12
namespace League\OAuth2\Server;
13
14
use LogicException;
15
use RuntimeException;
16
17
class CryptKey
18
{
19
    /** @deprecated left for backward compatibility check */
20
    const RSA_KEY_PATTERN =
21
        '/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s';
22
23
    private const FILE_PREFIX = 'file://';
24
25
    /**
26
     * @var string
27
     */
28
    protected $keyPath;
29
30
    /**
31
     * @var null|string
32
     */
33
    protected $passPhrase;
34
35
    /**
36
     * @param string      $keyPath
37
     * @param null|string $passPhrase
38
     * @param bool        $keyPermissionsCheck
39
     */
40 58
    public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = true)
41
    {
42 58
        $this->passPhrase = $passPhrase;
43
44 58
        if (\is_file($keyPath)) {
45 53
            if (\strpos($keyPath, self::FILE_PREFIX) !== 0) {
46 1
                $keyPath = self::FILE_PREFIX . $keyPath;
47
            }
48
49 53
            if (!\is_readable($keyPath)) {
50
                throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath));
51
            }
52 53
            $isFileKey = true;
53 53
            $contents = \file_get_contents($keyPath);
54 53
            $this->keyPath = $keyPath;
55
        } else {
56 5
            $isFileKey = false;
57 5
            $contents = $keyPath;
58 5
            $this->keyPath = $this->saveKeyToFile($keyPath);
59
        }
60
61 58
        if ($keyPermissionsCheck === true) {
62
            // Verify the permissions of the key
63 58
            $keyPathPerms = \decoct(\fileperms($this->keyPath) & 0777);
64 58
            if (\in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
65
                \trigger_error(
66
                    \sprintf(
67
                        'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
68
                        $this->keyPath,
69
                        $keyPathPerms
70
                    ),
71
                    E_USER_NOTICE
72
                );
73
            }
74
        }
75
76 58
        if (!$this->isValidKey($contents, $this->passPhrase ?? '')) {
77 2
            throw new LogicException('Unable to read key' . ($isFileKey ? " from file $keyPath" : ''));
78
        }
79 56
    }
80
81
    /**
82
     * @param string $key
83
     *
84
     * @throws RuntimeException
85
     *
86
     * @return string
87
     */
88 5
    private function saveKeyToFile($key)
89
    {
90 5
        $tmpDir = \sys_get_temp_dir();
91 5
        $keyPath = $tmpDir . '/' . \sha1($key) . '.key';
92
93 5
        if (\file_exists($keyPath)) {
94
            return self::FILE_PREFIX . $keyPath;
95
        }
96
97 5
        if (\file_put_contents($keyPath, $key) === false) {
98
            // @codeCoverageIgnoreStart
99
            throw new RuntimeException(\sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
100
            // @codeCoverageIgnoreEnd
101
        }
102
103 5
        if (\chmod($keyPath, 0600) === false) {
104
            // @codeCoverageIgnoreStart
105
            throw new RuntimeException(\sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
106
            // @codeCoverageIgnoreEnd
107
        }
108
109 5
        return self::FILE_PREFIX . $keyPath;
110
    }
111
112
    /**
113
     * Validate key contents.
114
     *
115
     * @param string $contents
116
     * @param string $passPhrase
117
     *
118
     * @return bool
119
     */
120 58
    private function isValidKey($contents, $passPhrase)
121
    {
122 58
        $pkey = \openssl_pkey_get_private($contents, $passPhrase) ?: \openssl_pkey_get_public($contents);
123 58
        if ($pkey === false) {
0 ignored issues
show
introduced by
The condition $pkey === false is always false.
Loading history...
124 1
            return false;
125
        }
126 57
        $details = \openssl_pkey_get_details($pkey);
127
128 57
        return $details !== false && \in_array(
129 57
            $details['type'] ?? -1,
130 57
            [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC],
131 57
            true
132
        );
133
    }
134
135
    /**
136
     * Retrieve key path.
137
     *
138
     * @return string
139
     */
140 21
    public function getKeyPath()
141
    {
142 21
        return $this->keyPath;
143
    }
144
145
    /**
146
     * Retrieve key pass phrase.
147
     *
148
     * @return null|string
149
     */
150 12
    public function getPassPhrase()
151
    {
152 12
        return $this->passPhrase;
153
    }
154
}
155