Bug fixes while testing (#6607)
[openemr.git] / src / Common / Auth / OAuth2KeyConfig.php
blobe536d807f84fff4cc9ebfb3d51a456e5c97b283a
1 <?php
3 /**
4 * Oauth2KeyConfig is responsible for configuring, generating, and returning oauth2 keys that are used by the OpenEMR system.
5 * @package openemr
6 * @link http://www.open-emr.org
7 * @author Jerry Padgett <sjpadgett@gmail.com>
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @author Stephen Nielson <stephen@nielson.org>
10 * @copyright Copyright (c) 2020 Jerry Padgett <sjpadgett@gmail.com>
11 * @copyright Copyright (c) 2020 Brady Miller <brady.g.miller@gmail.com>
12 * @copyright Copyright (c) 2021 Stephen Nielson <stephen@nielson.org>
13 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
16 namespace OpenEMR\Common\Auth;
18 use OpenEMR\Common\Crypto\CryptoGen;
19 use OpenEMR\Common\Utils\RandomGenUtils;
21 class OAuth2KeyConfig
23 /**
24 * @var string encryption key stored in the database
26 private $oaEncryptionKey;
28 /**
29 * @var string OAUTH2 passphrase used for the private key
31 private $passphrase;
33 /**
34 * @var string File location of the oauth2 private key
36 private $privateKey;
38 /**
39 * @var string File location of the oauth2 public key
41 private $publicKey;
43 public function __construct($siteDir = null)
45 if (empty($siteDir)) {
46 // default to our global location
47 $siteDir = $GLOBALS['OE_SITE_DIR'];
50 // Create a crypto object that will be used for for encryption/decryption
51 $this->cryptoGen = new CryptoGen();
52 // verify and/or setup our key pairs.
53 $this->privateKey = $siteDir . '/documents/certificates/oaprivate.key';
54 $this->publicKey = $siteDir . '/documents/certificates/oapublic.key';
57 public function getPassPhrase()
59 return $this->passphrase;
62 public function getEncryptionKey()
64 return $this->oaEncryptionKey;
66 public function getPublicKeyLocation()
68 return $this->publicKey;
71 public function getPrivateKeyLocation()
73 return $this->privateKey;
76 /**
77 * Configures the public and private keys for OpenEMR OAuth2. If they do not exist it generates them.
78 * @throws OAuth2KeyException
80 public function configKeyPairs(): void
82 // encryption key
83 $eKey = sqlQueryNoLog("SELECT `name`, `value` FROM `keys` WHERE `name` = 'oauth2key'");
84 if (!empty($eKey['name']) && ($eKey['name'] === 'oauth2key')) {
85 // collect the encryption key from database
86 $this->oaEncryptionKey = $this->cryptoGen->decryptStandard($eKey['value']);
87 if (empty($this->oaEncryptionKey)) {
88 if (($_ENV['OPENEMR__ENVIRONMENT'] ?? '') === 'dev') {
89 // delete corrupted key if doesn't exist to regenerate on next attempt.
90 sqlStatementNoLog("DELETE FROM `keys` WHERE `name` = 'oauth2key'");
92 // if decrypted key is empty, then critical error and must exit
93 throw new OAuth2KeyException("oauth2 key problem after decrypted. Key is invalid, Try to restore the file key in sites from a backup.");
95 } else {
96 // create a encryption key and store it in database
97 $this->oaEncryptionKey = RandomGenUtils::produceRandomBytes(32);
98 if (empty($this->oaEncryptionKey)) {
99 // if empty, then force exit
100 throw new OAuth2KeyException("random generator broken during oauth2 encryption key generation");
102 $this->oaEncryptionKey = base64_encode($this->oaEncryptionKey);
103 if (empty($this->oaEncryptionKey)) {
104 // if empty, then force exit
105 throw new OAuth2KeyException("base64 encoding broken during oauth2 encryption key generation");
107 sqlStatementNoLog("INSERT INTO `keys` (`name`, `value`) VALUES ('oauth2key', ?)", [$this->cryptoGen->encryptStandard($this->oaEncryptionKey)]);
109 // private key
110 if (!file_exists($this->privateKey)) {
111 // create the private/public key pair (store in filesystem) with a random passphrase (store in database)
112 // first, create the passphrase (removing any prior passphrases)
113 sqlStatementNoLog("DELETE FROM `keys` WHERE `name` = 'oauth2passphrase'");
114 $this->passphrase = RandomGenUtils::produceRandomString(60, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
115 if (empty($this->passphrase)) {
116 // if empty, then force exit
117 throw new OAuth2KeyException("random generator broken during oauth2 key passphrase generation");
119 // second, create and store the private/public key pair
120 $keysConfig = [
121 "default_md" => "sha256",
122 "private_key_type" => OPENSSL_KEYTYPE_RSA,
123 "private_key_bits" => 2048,
124 "encrypt_key" => true,
125 "encrypt_key_cipher" => OPENSSL_CIPHER_AES_256_CBC
127 $keys = \openssl_pkey_new($keysConfig);
128 if ($keys === false) {
129 // if unable to create keys, then force exit
130 throw new OAuth2KeyException("key generation broken during oauth2");
132 $privkey = '';
133 openssl_pkey_export($keys, $privkey, $this->passphrase, $keysConfig);
134 $pubkey = openssl_pkey_get_details($keys);
135 $pubkey = $pubkey["key"];
136 if (empty($privkey) || empty($pubkey)) {
137 // if unable to construct keys, then force exit
138 throw new OAuth2KeyException("key construction broken during oauth2");
140 // third, store the keys on drive and store the passphrase in the database
141 file_put_contents($this->privateKey, $privkey);
142 chmod($this->privateKey, 0640);
143 file_put_contents($this->publicKey, $pubkey);
144 chmod($this->publicKey, 0660);
145 sqlStatementNoLog("INSERT INTO `keys` (`name`, `value`) VALUES ('oauth2passphrase', ?)", [$this->cryptoGen->encryptStandard($this->passphrase)]);
147 // confirm existence of passphrase
148 $pkey = sqlQueryNoLog("SELECT `name`, `value` FROM `keys` WHERE `name` = 'oauth2passphrase'");
149 if (!empty($pkey['name']) && ($pkey['name'] == 'oauth2passphrase')) {
150 $this->passphrase = $this->cryptoGen->decryptStandard($pkey['value']);
151 if (empty($this->passphrase)) {
152 // if decrypted pssphrase is empty, then critical error and must exit
153 throw new OAuth2KeyException("oauth2 passphrase was blank after it was decrypted");
155 } else {
156 // oauth2passphrase is missing so must exit
157 throw new OAuth2KeyException("oauth2 passphrase is missing");
159 // confirm existence of key pair
160 if (!file_exists($this->privateKey) || !file_exists($this->publicKey)) {
161 // key pair is missing so must exit
162 throw new OAuth2KeyException("oauth2 keypair is missing");