Passed
Pull Request — master (#1215)
by
unknown
95:54
created

CryptKey::saveKeyToFile()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
nc 4
nop 1
dl 0
loc 22
ccs 4
cts 4
cp 1
crap 4
rs 9.9666
c 2
b 0
f 0
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 phpseclib3\Crypt\EC;
16
use phpseclib3\Crypt\RSA;
17
use phpseclib3\Exception\NoKeyLoadedException;
18
use RuntimeException;
19
20
class CryptKey
21
{
22
    /** @deprecated left for backward compatibility check */
23
    const RSA_KEY_PATTERN =
24
        '/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s';
25
26
    private const FILE_PREFIX = 'file://';
27
28
    /**
29
     * @var string
30
     */
31
    protected $keyPath;
32
33
    /**
34
     * @var null|string
35
     */
36
    protected $passPhrase;
37 56
38
    /**
39 56
     * @param string      $keyPath
40 1
     * @param null|string $passPhrase
41 55
     * @param bool        $keyPermissionsCheck
42 1
     */
43 1
    public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = true)
44
    {
45
        $this->keyPath = $keyPath;
46
        $this->passPhrase = $passPhrase;
47 55
48 2
        if (\is_file($this->keyPath) && !$this->isFilePath()) {
49
            $this->keyPath = self::FILE_PREFIX . $this->keyPath;
50
        }
51 55
52 1
        if ($this->isFilePath()) {
53
            if (!\file_exists($this->keyPath) || !\is_readable($this->keyPath)) {
54
                throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $this->keyPath));
55 54
            }
56
57 54
            $contents = \file_get_contents($this->keyPath);
58 54
        } else {
59
            $contents = $keyPath;
60
        }
61
62
        if ($this->isValidKey($contents, $this->passPhrase ?? '')) {
63
            if (!$this->isFilePath()) {
64
                $this->keyPath = $this->saveKeyToFile($keyPath);
65
            }
66
        } else {
67 54
            throw new LogicException('Unable to read key' . ($this->isFilePath() ? " from file $keyPath" : ''));
68 54
        }
69 54
70
        if ($keyPermissionsCheck === true) {
71
            // Verify the permissions of the key
72
            $keyPathPerms = \decoct(\fileperms($this->keyPath) & 0777);
73
            if (\in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
74
                \trigger_error(
75
                    \sprintf(
76
                        'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
77
                        $this->keyPath,
78 1
                        $keyPathPerms
79
                    ),
80 1
                    E_USER_NOTICE
81 1
                );
82
            }
83 1
        }
84
    }
85
86
    /**
87 1
     * @param string $key
88
     *
89
     * @throws RuntimeException
90
     *
91
     * @return string
92
     */
93 1
    private function saveKeyToFile($key)
94
    {
95
        $tmpDir = \sys_get_temp_dir();
96
        $keyPath = $tmpDir . '/' . \sha1($key) . '.key';
97
98
        if (\file_exists($keyPath)) {
99 1
            return self::FILE_PREFIX . $keyPath;
100
        }
101
102
        if (\file_put_contents($keyPath, $key) === false) {
103
            // @codeCoverageIgnoreStart
104
            throw new RuntimeException(\sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
105
            // @codeCoverageIgnoreEnd
106
        }
107 19
108
        if (\chmod($keyPath, 0600) === false) {
109 19
            // @codeCoverageIgnoreStart
110
            throw new RuntimeException(\sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
111
            // @codeCoverageIgnoreEnd
112
        }
113
114
        return self::FILE_PREFIX . $keyPath;
115
    }
116
117 10
    /**
118
     * Validate key contents.
119 10
     *
120
     * @param string $contents
121
     * @param string $passPhrase
122
     *
123
     * @return bool
124
     */
125
    private function isValidKey($contents, $passPhrase)
126
    {
127
        try {
128
            RSA::load($contents, $passPhrase);
129
130
            return true;
131
        } catch (NoKeyLoadedException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
132
        }
133
134
        try {
135
            EC::load($contents, $passPhrase);
136
137
            return true;
138
        } catch (NoKeyLoadedException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
139
        }
140
141
        return false;
142
    }
143
144
    /**
145
     * Checks whether the key is a file.
146
     *
147
     * @return bool
148
     */
149
    private function isFilePath()
150
    {
151
        return \strpos($this->keyPath, self::FILE_PREFIX) === 0;
152
    }
153
154
    /**
155
     * Retrieve key path.
156
     *
157
     * @return string
158
     */
159
    public function getKeyPath()
160
    {
161
        return $this->keyPath;
162
    }
163
164
    /**
165
     * Retrieve key pass phrase.
166
     *
167
     * @return null|string
168
     */
169
    public function getPassPhrase()
170
    {
171
        return $this->passPhrase;
172
    }
173
}
174