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