KMClient.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. namespace ACES\Core;
  3. use ACES\Common\Constants;
  4. use ACES\Common\domain\JosBaseResponse;
  5. use ACES\Common\domain\JosMasterKeyGetResponse;
  6. use ACES\Common\HttpsClient;
  7. use ACES\TDEClient;
  8. use Exception;
  9. use RuntimeException;
  10. use Monolog\Logger;
  11. use Monolog\Handler\StreamHandler;
  12. use Monolog\Formatter\LineFormatter;
  13. use ACES\Common\TDEStatus;
  14. use ACES\Common\Exception\ServiceErrorException;
  15. use ACES\Common\MKey;
  16. use JsonMapper;
  17. use ACES\Common\KStoreType;
  18. use ACES\Common\KeyRequest;
  19. use ACES\Common\Token;
  20. use ACES\Common\Exception\NoValidKeyException;
  21. use ACES\Common\CacheKeyStore;
  22. use ACES\Utils\UtilTools;
  23. use Common\Exception\CorruptKeyException;
  24. /**
  25. * KMClient Implementation
  26. *
  27. * @author JD Security (tenma.lin, wei.gao, mozhiyan, xuyina)
  28. * @version 1.0.0
  29. */
  30. define("KMSLOGFILE", getenv("HOME")."/aces-log/kmclient.log");
  31. if(!defined("LOGCONSOLE")){
  32. define("LOGCONSOLE", __DIR__."/../../../tde.log");
  33. }
  34. if(!defined("LOGLEVEL")){
  35. define("LOGLEVEL", Logger::DEBUG);
  36. }
  37. if(!defined("EMPTYSTR")){
  38. define("EMPTYSTR", "");
  39. }
  40. final class KMClient{
  41. // const TVALUE = 3; // Threshold value, 3 should be good
  42. // const KMS_SERVER_ENDPOINT = Constants::KMS_SERVER_ENDPOINT;
  43. // const INDEX_SERVER_ENDPOINT = Constants::INDEX_SERVER_ENDPOINT;
  44. private $tde;
  45. private $jsonMapper;
  46. /**
  47. * @var HttpReportClient
  48. */
  49. private $reporter;
  50. private $cacheKeyStore;
  51. private $userToken;
  52. private $corruptKeyList;
  53. private $availableKeyList;
  54. private $majorSdkVer = 0; // major sdk version number (for major upgrade)
  55. private $log;
  56. // private $keyCacheDisabled = 0; // key cache capability (0 for enabled, only disabled by KMS)
  57. // private $keyBackupDisabled = 0; // key backup capability (0 for enabled, only disabled by KMS)
  58. private $keyChainIsReady = False; // flag to indicate encryption/decryption keys are ready in memory
  59. // private $keyCacheFolder; // key cache file folder
  60. // private $keyCacheFile; // key cache file location
  61. // private $keyBackupFolder; // key Backup file folder
  62. // private $keyBackupFile; // key backup file location
  63. private $majorKeyVer; //newest version holder for major service
  64. // private $epoch = 28800;
  65. private $epoch = 5;//todo: remove, just for test
  66. // local variables for kms and index server endpoints, default settings are IDC
  67. // private $kmsURL = self::KMS_SERVER_ENDPOINT;
  68. // private $idxURL = self::INDEX_SERVER_ENDPOINT;
  69. /**
  70. * @var TDEClient
  71. */
  72. // private $josBaseInfo;
  73. /* KMClient constructor
  74. * @param JMQClient $mq
  75. * @param CacheKeyStore $keyStore
  76. * @param Token $token
  77. * @param string version
  78. *
  79. * @return KMClient
  80. */
  81. public function __construct(TDEClient $tde, HttpReportClient $reporter, CacheKeyStore $keyStore, Token $token, $version) {
  82. $this->tde = $tde;
  83. $this->reporter = $reporter;
  84. $this->cacheKeyStore = $keyStore;
  85. $this->userToken = $token;
  86. $this->majorSdkVer = (int)substr($version, 0, 1);
  87. $this->corruptKeyList = array();
  88. $this->availableKeyList = array();
  89. // confige log
  90. $this->log = new Logger('kmclient');
  91. $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message%\r\n");
  92. $handle = new StreamHandler(LOGCONSOLE, LOGLEVEL);
  93. $handle->setFormatter($formatter);
  94. $this->log->pushHandler($handle);
  95. $this->jsonMapper = new JsonMapper();
  96. }
  97. public function run(){
  98. $this->log->info("Key Management Thread Performs Key Updating...");
  99. // catch all exceptions
  100. try{
  101. $this->fetchMKeys(0);
  102. } catch (Exception $e){
  103. $this->log->critical($e->getMessage());
  104. $this->reporter->insertErrReport(
  105. TDEStatus::$SDK_THROW_JDK_EXCEPTION["code"],
  106. $e->getMessage(),
  107. UtilTools::extractStackTrace($e),
  108. MSG_LEVEL::ERROR);
  109. } catch (\Throwable $t){
  110. $this->log->critical($t->getMessage());
  111. $this->reporter->insertErrReport(
  112. TDEStatus::$SDK_THROW_JDK_EXCEPTION["code"],
  113. $t->getMessage(),
  114. UtilTools::extractStackTrace($t),
  115. MSG_LEVEL::ERROR);
  116. }
  117. }
  118. /* Fetch master key from kms server
  119. * Could give a 'delay' parameter to delay fetching operation
  120. * @param int $delay
  121. *
  122. * @return void
  123. */
  124. public function fetchMKeys($delay) {
  125. try{
  126. $this->log->info("Fetch keys from " . $this->tde->getJosBaseInfo()->getServerUrl() . Constants::KMS_ENDPOINT_REQUEST_MK . ". With delay = " . $delay . " ms.");
  127. if($delay > 0){
  128. // sleep for millis
  129. sleep($delay / 1000);
  130. }
  131. // todo: consider thread safe lock
  132. $josResponse = $this->josKeyRequest();
  133. if (!$josResponse || $josResponse->getCode() !== 0) {
  134. // $this->log->error("jos response error code: " . $josResponse->getCode() . ". error message: " . $josResponse->getEnDesc());
  135. throw new Exception('code=' . $josResponse->getCode() . ', message=' . $josResponse->getEnDesc());
  136. }
  137. $keyResponse = $josResponse->getResponse();
  138. // prepare corrupt key list
  139. $this->corruptKeyList = array();
  140. if($keyResponse->getStatus_code() === 0){
  141. $this->importMKeys($keyResponse);
  142. }else{
  143. $this->log->info("KMS reponse error code: " . $keyResponse->getStatus_code() . ". error message: " . $keyResponse->getErrorMsg());
  144. if($keyResponse->getStatus_code() == TDEStatus::$TMS_REQUEST_VERIFY_FAILED["code"]
  145. || $keyResponse->getStatus_code() == TDEStatus::$TMS_TOKEN_EXPIRE["code"]
  146. || $keyResponse->getStatus_code() == TDEStatus::$TMS_NO_AVAILABLE_GRANTS_FOR_SERVICE["code"]
  147. || $keyResponse->getStatus_code() == TDEStatus::$TMS_TOKEN_IS_FROZEN["code"]
  148. || $keyResponse->getStatus_code() == TDEStatus::$TMS_TOKEN_IS_REVOKE["code"]
  149. || $keyResponse->getStatus_code() == TDEStatus::$TMS_DB_DATA_NOTFOUND_ERROR["code"]){
  150. // Errors from TMS
  151. $this->reporter->insertErrReport(
  152. $keyResponse->getStatus_code(),
  153. $keyResponse->getErrorMsg(),
  154. EMPTYSTR,
  155. MSG_LEVEL::SEVER);
  156. // Handle cases: frozen, expired, verify failed, revoke
  157. $this->cacheKeyStore->removeAllMKeys(); // For security reason, better to remove all keys
  158. // $this->deleteKeyCache(); // Delete key cache of this token only because token has issues
  159. $this->keyChainIsReady = False; // Set flag to false
  160. }else{
  161. // other errors
  162. $this->reporter->insertErrReport(
  163. $keyResponse->getStatus_code(),
  164. $keyResponse->getErrorMsg(),
  165. EMPTYSTR,
  166. MSG_LEVEL::ERROR);
  167. }
  168. throw new ServiceErrorException($keyResponse->getErrorMsg());
  169. }
  170. } catch (RuntimeException $e){
  171. $this->log->critical($e->getMessage());
  172. $this->reporter->insertErrReport(
  173. TDEStatus::$SDK_CANNOT_REACH_KMS["code"],
  174. TDEStatus::$SDK_CANNOT_REACH_KMS["message"].$e->getMessage(),
  175. UtilTools::extractStackTrace($e),
  176. MSG_LEVEL::SEVER);
  177. throw new \RuntimeException(TDEStatus::$SDK_CANNOT_REACH_KMS["message"]);
  178. }
  179. // todo: interruptedException
  180. catch (Exception $e){
  181. $this->log->critical($e);
  182. throw $e;
  183. }
  184. }
  185. private function josKeyRequest()
  186. {
  187. //TODO 只有在voucher被冻结时才应该重新获取voucher
  188. // if ($this->josBaseInfo->getAccessToken() != null && $this->isKeyChainReady()) {
  189. // //request voucher
  190. // $userToken = Token::requestJosVoucher($this->josBaseInfo);
  191. // $this->userToken->transferToken($userToken);
  192. // }
  193. //request mk
  194. $requestUrl = $this->tde->getJosBaseInfo()->getServerUrl();
  195. $keyRequest = new KeyRequest($this->userToken, $this->majorSdkVer);
  196. $payload = $keyRequest->toFormParams($this->tde->getJosBaseInfo());
  197. $this->log->info('master key request url -> ' . $requestUrl . ', payload -> ' . json_encode($payload));
  198. $josResponse = HttpsClient::postForm($requestUrl, $payload);
  199. $response = JosBaseResponse::parse($josResponse, new JosMasterKeyGetResponse());
  200. return $response;
  201. }
  202. private function importMKeys($keyResponse){
  203. if(strcmp($keyResponse->getEnc_service(), $this->userToken->get_service_name())!=0){
  204. $this->log->critical(TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE1['message']);
  205. $this->reporter->insertErrReport(
  206. TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE1["code"],
  207. TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE1["message"],
  208. EMPTYSTR,
  209. MSG_LEVEL::ERROR);
  210. throw new ServiceErrorException(TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE1['message']);
  211. }
  212. if(strcmp($keyResponse->getTid(), $this->userToken->get_id())!=0){
  213. $this->log->critical(TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE2['message']);
  214. $this->reporter->insertErrReport(
  215. TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE2["code"],
  216. TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE2["message"],
  217. EMPTYSTR,
  218. MSG_LEVEL::ERROR);
  219. throw new ServiceErrorException(TDEStatus::$SDK_RECEIVED_WRONG_KEYRESPONSE2['message']);
  220. }
  221. // get two lists of key IDs to make sure old keys could be removed from cache_ks
  222. $enc_rmv_list = $this->cacheKeyStore->getKeyIDList(KStoreType::ENC_STORE);
  223. $dec_rmv_list = $this->cacheKeyStore->getKeyIDList(KStoreType::DEC_STORE);
  224. $this->cacheKeyStore->resetFutureKeyIDs();
  225. foreach ($keyResponse->getService_key_list() as $service){
  226. $mkeys = $service->getKeys();
  227. $this->availableKeyList[$service->getService()] = sizeof($mkeys)-1;
  228. foreach ($mkeys as $key){
  229. $k = new MKey(
  230. $service->getService(),
  231. base64_decode($key->getId()),
  232. base64_decode($key->getKey_string()),
  233. $key->getKey_digest(),
  234. $key->getVersion(),
  235. $key->getKey_effective(),
  236. $key->getKey_exp(),
  237. $key->getKey_type(),
  238. $service->getGrant_usage(),
  239. $key->getKey_status());
  240. if($k->isValid()){
  241. if(strcmp($service->getService(), $this->userToken->get_service_name())===0){
  242. $this->majorKeyVer = $service->getCurrent_key_version();
  243. // update to enc/dec key cache if neccessary
  244. $this->cacheKeyStore->updateKey($key->getId(), $k, KStoreType::ENC_STORE);
  245. $this->cacheKeyStore->updateKey($key->getId(), $k, KStoreType::DEC_STORE);
  246. unset($enc_rmv_list[array_search($key->getId(), $enc_rmv_list)]);
  247. unset($dec_rmv_list[array_search($key->getId(), $dec_rmv_list)]);
  248. }else{
  249. // update to decryption key cache only
  250. $this->cacheKeyStore->updateKey($key->getId(), $k, KStoreType::DEC_STORE);
  251. unset($dec_rmv_list[array_search($key->getId(), $dec_rmv_list)]);
  252. }
  253. }else{
  254. // The key is corrupted
  255. $this->corruptKeyList[] = base64_encode($k->getID());
  256. }
  257. }
  258. $this->cacheKeyStore->updateFutureKeyIDs($service->getService(), $service->getCurrent_key_version());
  259. }
  260. // todo: generate key update report with assigned key list information
  261. $this->reporter->insertKeyUpdateEventReport(TDEStatus::$SDK_REPORT_CUR_KEYVER['code']
  262. , TDEStatus::$SDK_REPORT_CUR_KEYVER['message'] . $this->majorSdkVer, $this->majorSdkVer, $this->availableKeyList);
  263. $this->availableKeyList = array();
  264. // adjust key store cache
  265. if(sizeof($enc_rmv_list) > 0){
  266. $this->log->info(sizeof($enc_rmv_list));
  267. $this->cacheKeyStore->removeKeysViaList($enc_rmv_list, KStoreType::ENC_STORE);
  268. }
  269. if(sizeof($dec_rmv_list) > 0){
  270. $this->cacheKeyStore->removeKeysViaList($dec_rmv_list, KStoreType::DEC_STORE);
  271. }
  272. // verify key store by compare their digest
  273. $this->sendCorruptReport();
  274. // check valid key chain
  275. $this->checkValidKeyChain();
  276. TDEClient::getClientCache()->set($this->tde->getJosBaseInfo()->getAccessToken(), $this->tde);
  277. }
  278. private function sendCorruptReport(){
  279. if(!empty($this->corruptKeyList)){
  280. $this->log->critical(TDEStatus::$SDK_HAS_CORRUPTED_KEYS["message"]);
  281. // prepare string
  282. $keyids = "keyids:";
  283. foreach ($this->corruptKeyList as $corruptkey){
  284. $keyids .= $corruptkey . ",";
  285. }
  286. $keyids = substr($keyids, 0, strlen($keyids)-1);
  287. $this->reporter->insertErrReport(TDEStatus::$SDK_HAS_CORRUPTED_KEYS["code"], TDEStatus::$SDK_HAS_CORRUPTED_KEYS["message"], $keyids, MSG_LEVEL::ERROR);
  288. throw new CorruptKeyException(TDEStatus::$SDK_HAS_CORRUPTED_KEYS["message"]);
  289. }
  290. }
  291. private function checkValidKeyChain(){
  292. $this->keyChainIsReady = FALSE;
  293. $total_keys = $this->cacheKeyStore->numOfKeys(KStoreType::DEC_STORE) + $this->cacheKeyStore->numOfKeys(KStoreType::ENC_STORE);
  294. // fail-fast
  295. if($total_keys === 0){
  296. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  297. // should not happen, probably due to some internal error or other issues
  298. $this->reporter->insertErrReport(
  299. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["code"],
  300. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"],
  301. EMPTYSTR,
  302. MSG_LEVEL::SEVER);
  303. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  304. }
  305. $this->log->info("# of enc keys: " . $this->cacheKeyStore->numOfKeys(KStoreType::ENC_STORE) . " and # of dec keys:".$this->cacheKeyStore->numOfKeys(KStoreType::DEC_STORE));
  306. $this->log->info("Max key version for major service: ".$this->majorKeyVer);
  307. // at least the memory has functional keychain already
  308. $this->keyChainIsReady = TRUE;
  309. }
  310. /* Set KMS server url where SDK fetches keys
  311. * @param string $kmsURL
  312. *
  313. * @return void
  314. */
  315. // public function setKMSEndpoint($kmsURL){
  316. // $this->kmsURL = $kmsURL;
  317. // }
  318. /* Set Index server url
  319. * @param string $idxURL
  320. *
  321. * @return void
  322. */
  323. // public function setIDXEndpoint($idxURL) {
  324. // $this->idxURL = $idxURL;
  325. // }
  326. /* Get major key version
  327. *
  328. * @return int
  329. */
  330. public function getMajorKeyVersion() {
  331. return $this->majorKeyVer;
  332. }
  333. /* Return true if key chain is ready
  334. *
  335. * @return bool
  336. */
  337. public function isKeyChainReady(){
  338. return $this->keyChainIsReady;
  339. }
  340. /* Reset key chain flag to false
  341. *
  342. * @retunr void
  343. */
  344. public function resetKeyChainFlag(){
  345. $this->keyChainIsReady = FALSE;
  346. }
  347. public function setEpoch($epoch){
  348. $this->epoch = $epoch;
  349. }
  350. }