Passed
Pull Request — master (#1215)
by
unknown
35:02
created

CryptKey::isValidKey()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 2
b 0
f 0
nc 3
nop 2
dl 0
loc 12
ccs 0
cts 0
cp 0
crap 20
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 56
     * @param null|string $passPhrase
38
     * @param bool        $keyPermissionsCheck
39 56
     */
40 1
    public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = true)
41 55
    {
42 1
        $this->keyPath = $keyPath;
43 1
        $this->passPhrase = $passPhrase;
44
45
        if (\is_file($this->keyPath) && !$this->isFilePath()) {
46
            $this->keyPath = self::FILE_PREFIX . $this->keyPath;
47 55
        }
48 2
49
        if ($this->isFilePath()) {
50
            if (!\file_exists($this->keyPath) || !\is_readable($this->keyPath)) {
51 55
                throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $this->keyPath));
52 1
            }
53
54
            $contents = \file_get_contents($this->keyPath);
55 54
        } else {
56
            $contents = $keyPath;
57 54
        }
58 54
59
        if ($this->isValidKey($contents, $this->passPhrase ?? '')) {
60
            if (!$this->isFilePath()) {
61
                $this->keyPath = $this->saveKeyToFile($keyPath);
62
            }
63
        } else {
64
            throw new LogicException('Unable to read key' . ($this->isFilePath() ? " from file $keyPath" : ''));
65
        }
66
67 54
        if ($keyPermissionsCheck === true) {
68 54
            // Verify the permissions of the key
69 54
            $keyPathPerms = \decoct(\fileperms($this->keyPath) & 0777);
70
            if (\in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
71
                \trigger_error(
72
                    \sprintf(
73
                        'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
74
                        $this->keyPath,
75
                        $keyPathPerms
76
                    ),
77
                    E_USER_NOTICE
78 1
                );
79
            }
80 1
        }
81 1
    }
82
83 1
    /**
84
     * @param string $key
85
     *
86
     * @throws RuntimeException
87 1
     *
88
     * @return string
89
     */
90
    private function saveKeyToFile($key)
91
    {
92
        $tmpDir = \sys_get_temp_dir();
93 1
        $keyPath = $tmpDir . '/' . \sha1($key) . '.key';
94
95
        if (\file_exists($keyPath)) {
96
            return self::FILE_PREFIX . $keyPath;
97
        }
98
99 1
        if (\file_put_contents($keyPath, $key) === false) {
100
            // @codeCoverageIgnoreStart
101
            throw new RuntimeException(\sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
102
            // @codeCoverageIgnoreEnd
103
        }
104
105
        if (\chmod($keyPath, 0600) === false) {
106
            // @codeCoverageIgnoreStart
107 19
            throw new RuntimeException(\sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
108
            // @codeCoverageIgnoreEnd
109 19
        }
110
111
        return self::FILE_PREFIX . $keyPath;
112
    }
113
114
    /**
115
     * Validate key contents.
116
     *
117 10
     * @param string $contents
118
     * @param string $passPhrase
119 10
     *
120
     * @return bool
121
     */
122
    private function isValidKey($contents, $passPhrase)
123
    {
124
        $pkey = openssl_pkey_get_private($contents, $passPhrase) ?: openssl_pkey_get_public($contents);
125
        if ($pkey === false) {
0 ignored issues
show
introduced by
The condition $pkey === false is always false.
Loading history...
126
            return false;
127
        }
128
        $details = openssl_pkey_get_details($pkey);
129
130
        return $details !== false && in_array(
131
            $details['type'] ?? -1,
132
            [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC],
133
            true
134
        );
135
    }
136
137
    /**
138
     * Checks whether the key is a file.
139
     *
140
     * @return bool
141
     */
142
    private function isFilePath()
143
    {
144
        return \strpos($this->keyPath, self::FILE_PREFIX) === 0;
145
    }
146
147
    /**
148
     * Retrieve key path.
149
     *
150
     * @return string
151
     */
152
    public function getKeyPath()
153
    {
154
        return $this->keyPath;
155
    }
156
157
    /**
158
     * Retrieve key pass phrase.
159
     *
160
     * @return null|string
161
     */
162
    public function getPassPhrase()
163
    {
164
        return $this->passPhrase;
165
    }
166
}
167