4 * Oauth2KeyConfig is responsible for configuring, generating, and returning oauth2 keys that are used by the OpenEMR system.
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
;
24 * @var string encryption key stored in the database
26 private $oaEncryptionKey;
29 * @var string OAUTH2 passphrase used for the private key
34 * @var string File location of the oauth2 private key
39 * @var string File location of the oauth2 public key
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
;
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
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.");
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
)]);
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
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");
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");
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");