TDEClient.php 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. <?php
  2. namespace ACES;
  3. use ACES\Common\cache\ApcuCache;
  4. use ACES\Common\cache\YacCache;
  5. use ACES\Common\cache\iCache;
  6. use ACES\Common\domain\JosBaseInfo;
  7. use ACES\Common\Exception\IndexCalculateException;
  8. use ACES\Core\HttpReportClient;
  9. use ACES\Core\KMClient;
  10. use ACES\Common\STATE;
  11. use ACES\Common\Token;
  12. use ACES\Common\CacheKeyStore;
  13. use ACES\Common\Constants;
  14. use Monolog\Logger;
  15. use Monolog\Handler\StreamHandler;
  16. use Monolog\Formatter\LineFormatter;
  17. use ACES\Common\TDEStatus;
  18. use ACES\Common\Exception\InvalidTokenException;
  19. use ACES\Common\Exception\MalformedException;
  20. use ACES\Common\Exception\NoValidKeyException;
  21. use ACES\Common\Exception\ArgumentNullException;
  22. use ACES\Utils\UtilTools;
  23. use ACES\Core\MSG_LEVEL;
  24. use ACES\Common\Exception\ServiceErrorException;
  25. use ACES\Common\KEY_STATUS;
  26. use ACES\Common\Exception\InvalidKeyException;
  27. use ACES\Common\KEY_USAGE;
  28. use ACES\Common\Exception\InvalidKeyPermission;
  29. use ACES\Common\IndexCalculator;
  30. use ACES\Common\KeyEncryption;
  31. use ACES\Common\Salsa20\FieldElement;
  32. use ACES\Common\Salsa20\IndexCalculationHelper;
  33. use ACES\Common\Salsa20\Salsa20;
  34. // Cipher status
  35. define("CIPHER_ST_DECRYPTABLE", 0); // valid cipher, can be decrypted
  36. define("CIPHER_ST_MALFORMED", 1); // invalid cipher because the format is malformed (by checking cipher header)
  37. define("CIPHER_ST_FEASIBLE", 2); // valid cipher but key are not ready
  38. define("CIPHER_ST_UNDECRYPTABLE", 3); // valid cipher but undecryptable
  39. // Token origins
  40. define("TOKEN_ORIGIN_UNDEFINED", 0);
  41. define("TOKEN_ORIGIN_IDC", 1);
  42. define("TOKEN_ORIGIN_BETA", 2);
  43. define("TOKEN_ORIGIN_DEV", 3);
  44. if(!defined("EMPTYSTR")){
  45. define("EMPTYSTR", "");
  46. }
  47. if(!defined("LOGCONSOLE")){
  48. define("LOGCONSOLE", __DIR__."/../../tde.log");
  49. }
  50. if(!defined("LOGLEVEL")){
  51. define("LOGLEVEL", Logger::DEBUG);
  52. }
  53. final class TDEClient
  54. {
  55. const version = "php 1.0.7";
  56. const salt = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
  57. const keyWordSalt = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
  58. /**
  59. * @var iCache
  60. */
  61. private static $clientCache;
  62. /**
  63. * @var KMClient
  64. */
  65. private $kmc;
  66. /**
  67. * @var CacheKeyStore
  68. */
  69. private $cache_ks;
  70. /**
  71. * @var Token
  72. */
  73. private $token;
  74. /**
  75. * @var HttpReportClient
  76. */
  77. private $reporter;
  78. private $statistic;
  79. private $log;
  80. // private $mapper; // json mappper;
  81. /**
  82. * @var JosBaseInfo
  83. */
  84. private $josBaseInfo;
  85. /* TDEClient constructor
  86. *
  87. * @param string $tokenStr
  88. * @param string $rpath
  89. * @param string $kmsUrl
  90. * @param string $idxUrl
  91. * @param bool $isProd
  92. *
  93. */
  94. public function __construct($tokenStr, $josBaseInfo) {
  95. // confige log
  96. $this->log = new Logger('tdeClient');
  97. $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message%\r\n");
  98. $handle = new StreamHandler(LOGCONSOLE, LOGLEVEL);
  99. $handle->setFormatter($formatter);
  100. $this->log->pushHandler($handle);
  101. // config jsonmapper;
  102. // $this->mapper = new \JsonMapper();
  103. $this->log->info("Creating tdeclient with given token string.");
  104. $this->josBaseInfo = $josBaseInfo;
  105. $this->InitClient($tokenStr);
  106. }
  107. /**
  108. * @return iCache
  109. */
  110. public static function getClientCache()
  111. {
  112. if (!self::$clientCache) {
  113. if (extension_loaded("yac") && ini_get('yac.enable')==1) {
  114. self::$clientCache = new YacCache();
  115. }else if (extension_loaded("apcu") && ini_get('apc.enabled')==1) {
  116. self::$clientCache = new ApcuCache();
  117. }else{
  118. throw new \RuntimeException("neither yac nor apcu enable");
  119. }
  120. }
  121. return self::$clientCache;
  122. }
  123. /**
  124. * @param $accessToken
  125. * @param $appKey
  126. * @param $appSecret
  127. * @param string $serverUrl
  128. * @return TDEClient
  129. * @throws Common\Exception\JosGwException
  130. * @throws Common\Exception\VoucherInfoGetException
  131. * @throws \JsonMapper_Exception
  132. */
  133. public static function getInstance($accessToken, $appKey, $appSecret, $serverUrl='https://api.jd.com/routerjson')
  134. {
  135. // TBA: consider multi-thread scenario
  136. $c = self::getClientCache()->get($accessToken);
  137. if(!$c) {
  138. $josBaseInfo = new JosBaseInfo($appKey, $appSecret, $accessToken, $serverUrl);
  139. $tokenStr = Token::requestJosVoucherString($josBaseInfo);
  140. $c = new TDEClient($tokenStr, $josBaseInfo);
  141. self::getClientCache()->set($accessToken, $c);
  142. }else {
  143. $nullParam = $c->checkNullParam(true);
  144. if ($nullParam) {
  145. self::getClientCache()->delete($accessToken);
  146. $josBaseInfo = new JosBaseInfo($appKey, $appSecret, $accessToken, $serverUrl);
  147. $tokenStr = Token::requestJosVoucherString($josBaseInfo);
  148. $c = new TDEClient($tokenStr, $josBaseInfo);
  149. self::getClientCache()->set($accessToken, $c);
  150. }
  151. }
  152. return $c;
  153. }
  154. private function InitClient($tokenStr)
  155. {
  156. try{
  157. // step 0: jmq client
  158. $this->reporter = new HttpReportClient($this);
  159. // step 1:load single token
  160. // throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
  161. // InvalidTokenException, MalformedException
  162. $this->token = Token::parseFromString(base64_decode($tokenStr), true);
  163. $this->log->info("Token ID: ".$this->token->get_id().", origins from ".$this->token->getTokenOrigin());
  164. $this->reporter->insertInitReport();
  165. // step 2: prepare mkey cache and corrupt key list
  166. $this->cache_ks = new CacheKeyStore();
  167. // step 3:prepare KM client
  168. $this->kmc = new KMClient($this, $this->reporter, $this->cache_ks, $this->token, self::version);
  169. // step 4: adjust settings
  170. // if(!empty($kmsUrl)){
  171. // $this->kmc->setKMSEndpoint($kmsUrl);
  172. // }
  173. // step 5: allocate some statistic
  174. $this->statistic = array(0,0,0,0,0,0,0,0);
  175. // step 6: prepare JMQ client (separate thread) immediately with a given epoch
  176. // todo: optimize with event
  177. $delay = 0;
  178. $this->kmc->fetchMKeys($delay);
  179. if(!$this->kmc->isKeyChainReady()){
  180. throw new \RuntimeException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  181. }
  182. // todo: optimize with event
  183. // todo: schedule run kmc
  184. } catch (InvalidTokenException $e){
  185. $this->log->critical($e->getMessage());
  186. $this->reporter->insertErrReport(
  187. TDEStatus::$SDK_USE_INVALID_TOKEN["code"],
  188. $e->getMessage(),
  189. UtilTools::extractStackTrace($e),
  190. MSG_LEVEL::SEVER);
  191. throw $e;
  192. } catch (MalformedException $e){
  193. $this->log->critical($e->getMessage());
  194. $this->reporter->insertErrReport(
  195. TDEStatus::$SDK_THROW_JDK_EXCEPTION["code"],
  196. $e->getMessage(),
  197. UtilTools::extractStackTrace($e),
  198. MSG_LEVEL::ERROR);
  199. throw $e;
  200. } catch(ServiceErrorException $e){
  201. throw $e;
  202. } catch(NoValidKeyException $e){
  203. throw $e;
  204. } catch (\RuntimeException $e){
  205. $this->log->critical($e->getMessage());
  206. $this->reporter->insertErrReport(
  207. TDEStatus::$SDK_THROW_JDK_EXCEPTION["code"],
  208. $e->getMessage(),
  209. UtilTools::extractStackTrace($e),
  210. MSG_LEVEL::ERROR);
  211. throw $e;
  212. } catch (\Throwable $e){
  213. $this->log->critical($e->getMessage());
  214. $this->reporter->insertErrReport(
  215. TDEStatus::$SDK_INTERNAL_ERROR["code"],
  216. $e->getMessage(),
  217. UtilTools::extractStackTrace($e),
  218. MSG_LEVEL::ERROR);
  219. throw $e;
  220. }
  221. }
  222. /**
  223. * @return JosBaseInfo
  224. */
  225. public function getJosBaseInfo()
  226. {
  227. return $this->josBaseInfo;
  228. }
  229. /* Calculate the given string's SHA256 index value
  230. *
  231. * @param string $ct
  232. *
  233. * @return string
  234. */
  235. // public static function calculateIndex($pt, $salt) {
  236. // $ret = IndexCalculator::sha256Index($pt, $salt);
  237. //
  238. // return $ret;
  239. // }
  240. //
  241. // public static function calculateStringIndex($pt, $salt) {
  242. // $ret = base64_encode(IndexCalculator::sha256Index($pt, $salt));
  243. //
  244. // return $ret;
  245. // }
  246. public function calculateIndex($pt){
  247. $k0 = $this->cache_ks->getEncKeyByVersion(0);
  248. if($k0 == null){
  249. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  250. $this->reporter->insertErrReport(
  251. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["code"],
  252. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"],
  253. EMPTYSTR,
  254. MSG_LEVEL::SEVER);
  255. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  256. }
  257. $index = NULL;
  258. try{
  259. $computed_salt = KeyEncryption::wrap($k0, self::salt);
  260. $index = IndexCalculator::sha256Index($pt, $computed_salt);
  261. }catch (\Exception $e){
  262. throw new IndexCalculateException($e->getMessage());
  263. }
  264. return $index;
  265. }
  266. public function calculateStringIndex($pt){
  267. $index = $this->calculateIndex($pt);
  268. return base64_encode($index);
  269. }
  270. /* Encrypt data
  271. *
  272. * @param string $pt
  273. *
  274. * @return string base64encoded
  275. */
  276. public function encrypt($pt, $encoding="") {
  277. if($pt === NULL){
  278. throw new ArgumentNullException("Input string pt is null.");
  279. }
  280. if($encoding != NULL){
  281. $pt = mb_convert_encoding($pt, $encoding);
  282. }
  283. // validate token
  284. $this->validateToken();
  285. $k = $this->cache_ks->getEnckeyByVersion($this->kmc->getMajorKeyVersion());
  286. $this->check_key_status_forEncryption($k);
  287. $this->log->info("Weak encrypt with key version:".$k->getVersion());
  288. $ct = null;
  289. try{
  290. $ct = $k->encrypt($pt);
  291. $this->reporter->insertStatReport(StatisticType::ENCCNT);
  292. } catch (\Throwable $e){
  293. $this->reporter->insertStatReport(StatisticType::ENCERRCNT);
  294. throw $e;
  295. }
  296. return base64_encode($ct);
  297. }
  298. private function check_key_status_forEncryption($key, $isEncryption=TRUE){
  299. if($key == null){
  300. if($isEncryption){
  301. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_ENC_KEYS["message"]);
  302. // should not happen, probably due to some internal error or other issues
  303. $this->reporter->insertErrReport(
  304. TDEStatus::$SDK_HAS_NO_AVAILABLE_ENC_KEYS["code"],
  305. TDEStatus::$SDK_HAS_NO_AVAILABLE_ENC_KEYS["message"],
  306. EMPTYSTR,
  307. MSG_LEVEL::SEVER);
  308. $this->reporter->insertStatReport(StatisticType::ENCERRCNT);
  309. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_ENC_KEYS["message"]);
  310. } else {
  311. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_SIGN_KEYS["message"]);
  312. // should not happen, probably due to some internal error or other issues
  313. $this->reporter->insertErrReport(
  314. TDEStatus::$SDK_HAS_NO_AVAILABLE_SIGN_KEYS["code"],
  315. TDEStatus::$SDK_HAS_NO_AVAILABLE_SIGN_KEYS["message"],
  316. EMPTYSTR,
  317. MSG_LEVEL::SEVER);
  318. $this->reporter->insertStatReport(StatisticType::SIGNERRCNT);
  319. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_ENC_KEYS["message"]);
  320. }
  321. }
  322. // encrypt only for ACTIVE key
  323. if($key->getKeyStatus() != KEY_STATUS::ACTIVE){
  324. $this->log->critical(TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["message"]);
  325. $this->reporter->insertErrReport(
  326. TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["code"],
  327. TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["message"],
  328. EMPTYSTR,
  329. MSG_LEVEL::ERROR);
  330. if($isEncryption){
  331. $this->reporter->insertStatReport(StatisticType::ENCERRCNT);
  332. }else{
  333. $this->reporter->insertStatReport(StatisticType::SIGNERRCNT);
  334. }
  335. throw new InvalidKeyException(TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["message"]);
  336. }
  337. if($key->getKeyUsage() == KEY_USAGE::N || $key->getKeyUsage() == KEY_USAGE::D){
  338. if($isEncryption){
  339. $this->reporter->insertStatReport(StatisticType::ENCERRCNT);
  340. }else {
  341. $this->reporter->insertStatReport(StatisticType::SIGNERRCNT);
  342. }
  343. throw new InvalidKeyPermission("Key Permission Invalid.");
  344. }
  345. // check key timestamp, millis
  346. $now = date_timestamp_get(new \DateTime()) * 1000;
  347. if($key->getExpiredTime()<$now){
  348. $this->reporter->insertErrReport(
  349. TDEStatus::$SDK_OPERATE_WITH_EXPIRED_KEYS["code"],
  350. TDEStatus::$SDK_OPERATE_WITH_EXPIRED_KEYS["message"],
  351. EMPTYSTR,
  352. MSG_LEVEL::WARN);
  353. $this->log->info(TDEStatus::$SDK_OPERATE_WITH_EXPIRED_KEYS["message"]);
  354. }
  355. }
  356. /* Decrypt data
  357. *
  358. * @param string $ct
  359. *
  360. * @return string
  361. */
  362. public function decrypt($ct, $encoding="") {
  363. if($ct == NULL){
  364. throw new ArgumentNullException("Input cipher string base64ct is NULL.");
  365. }
  366. $ct = base64_decode($ct);
  367. // validate token
  368. $this->validateToken();
  369. // check cipher and handle exception
  370. $cipherResult = $this->getCipherResult($ct);
  371. // MQ handle for different cases
  372. if($cipherResult->status === ResultType::UnDecryptable){
  373. $min_len = $cipherResult->isStrong ? min(strlen($ct), Constants::STRONG_HDR_LEN) : min(strlen($ct), Constants::WEAK_HDR_LEN);
  374. $header = base64_encode(substr($ct, 0, $min_len));
  375. $this->reporter->insertErrReport(
  376. TDEStatus::$SDK_HAS_NO_CORRESPONDING_DEC_KEYS["code"],
  377. TDEStatus::$SDK_HAS_NO_CORRESPONDING_DEC_KEYS["message"].$header,
  378. EMPTYSTR,
  379. MSG_LEVEL::SEVER);
  380. $this->reporter->insertStatReport(StatisticType::DECERRCNT);
  381. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_CORRESPONDING_DEC_KEYS["message"].$header);
  382. } elseif($cipherResult->status === ResultType::Feasible){
  383. $this->log->info("Feasible case: KMS client needs to fetch keys from KMS.");
  384. $this->reporter->insertEventReport(TDEStatus::$SDK_TRIGGER_ROTATED_KEY_FETCH["code"], TDEStatus::$SDK_TRIGGER_ROTATED_KEY_FETCH["message"]);
  385. // fetch keys from KMS
  386. // blocking call!!
  387. $this->kmc->fetchMKeys(0);
  388. self::getClientCache()->set($this->josBaseInfo->getAccessToken(), $this);
  389. if($this->cache_ks->hasFutureKeyID($cipherResult->keyid)){
  390. $min_len = $cipherResult->isStrong ? min(strlen($ct), Constants::STRONG_HDR_LEN) : min(strlen($ct), Constants::WEAK_HDR_LEN);
  391. $header = base64_encode(substr($ct, 0, $min_len));
  392. $this->reporter->insertErrReport(
  393. TDEStatus::$SDK_FAILS_TO_FETCH_UPDATED_KEYS["code"],
  394. TDEStatus::$SDK_FAILS_TO_FETCH_UPDATED_KEYS["message"].$header,
  395. EMPTYSTR,
  396. MSG_LEVEL::SEVER);
  397. $this->reporter->insertStatReport(StatisticType::DECERRCNT);
  398. throw new NoValidKeyException(TDEStatus::$SDK_FAILS_TO_FETCH_UPDATED_KEYS["message"].$header);
  399. }
  400. } elseif ($cipherResult->status === ResultType::Malformed){
  401. $this->reporter->insertStatReport(StatisticType::DECERRCNT);
  402. // fetch available ciphertext
  403. $corrpted_cipher = "(NULL)";
  404. if($ct !== NULL){
  405. $min_len = min(strlen($ct), Constants::WEAK_HDR_LEN);
  406. $corrpted_cipher = $min_len === 0 ? "(EMPTY)" : base64_encode(substr($ct, 0, $min_len));
  407. }
  408. $this->reporter->insertErrReport(
  409. TDEStatus::$SDK_HAS_CORRUPTED_CIPHER["code"],
  410. TDEStatus::$SDK_HAS_CORRUPTED_CIPHER["message"].$corrpted_cipher,
  411. EMPTYSTR,
  412. MSG_LEVEL::SEVER);
  413. throw new MalformedException(TDEStatus::$SDK_HAS_CORRUPTED_CIPHER["message"].$corrpted_cipher);
  414. }
  415. // already double check by getCipherResult()
  416. $k = $this->cache_ks->searchDeckey($cipherResult->keyid);
  417. $this->log->info("Decrypt with key version:".$k->getVersion());
  418. // check key status
  419. $this->check_key_status_forDecryption($k);
  420. $pt = NULL;
  421. try{
  422. $pt = $cipherResult->isStrong ? $k->strong_decrypt($ct):$k->decrypt($ct);
  423. $this->reporter->insertStatReport(StatisticType::DECCNT);
  424. } catch (\Throwable $e){
  425. $this->reporter->insertStatReport(StatisticType::DECERRCNT);
  426. throw $e;
  427. }
  428. if($encoding != NULL){
  429. $pt = mb_convert_encoding($pt, $encoding);
  430. }
  431. return $pt;
  432. }
  433. private function check_key_status_forDecryption($key, $isDecryption=TRUE){
  434. // check if it's revoked
  435. if($key->getKeyStatus() === KEY_STATUS::REVOKED){
  436. // due to key rotation, decryption can use both active/suspend key but not for revoked one
  437. $this->log->critical(TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["message"]);
  438. $this->reporter.insertErrReport(
  439. TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["code"],
  440. TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["message"],
  441. EMPTYSTR,
  442. MSG_LEVEL::SEVER);
  443. if($isDecryption){
  444. $this->reporter->insertStatReport(StatisticType::DECERRCNT);
  445. } else {
  446. $this->reporter->insertStatReport(StatisticType::VERIFYERRCNT);
  447. }
  448. throw new InvalidKeyException(TDEStatus::$SDK_OPERATE_WITH_INACTIVE_KEYS["message"]);
  449. }
  450. if($key->getKeyUsage() === KEY_USAGE::N || $key->getKeyUsage() === KEY_USAGE::E){
  451. if($isDecryption){
  452. $this->reporter->insertStatReport(StatisticType::DECERRCNT);
  453. }else {
  454. $this->reporter->insertStatReport(StatisticType::VERIFYERRCNT);
  455. }
  456. throw new InvalidKeyPermission("Key Permission Invalid.");
  457. }
  458. // check key timestamp
  459. $now = date_timestamp_get(new \DateTime()) * 1000;
  460. if($key->getExpiredTime()<$now){
  461. $this->reporter->insertErrReport(
  462. TDEStatus::$SDK_OPERATE_WITH_EXPIRED_KEYS["code"],
  463. TDEStatus::$SDK_OPERATE_WITH_EXPIRED_KEYS["message"],
  464. EMPTYSTR,
  465. MSG_LEVEL::WARN);
  466. $this->log->info(TDEStatus::$SDK_OPERATE_WITH_EXPIRED_KEYS["message"]);
  467. }
  468. }
  469. public function isEncryptionBytesData($ct)
  470. {
  471. try {
  472. $ctype_ = unpack("C", substr($ct, 0, Constants::CIPHER_TYPE_LEN));
  473. $ctype = $ctype_[1];
  474. $isStrong = false;
  475. if($ctype == Constants::CIPHER_TYPE_LARGE ||
  476. $ctype == Constants::CIPHER_TYPE_REGULAR){
  477. $isStrong = TRUE;
  478. } elseif ($ctype !== Constants::CIPHER_TYPE_WEAK) {
  479. return false;
  480. }
  481. $mkIdx = $this->extractKeyId($ct, $isStrong);
  482. if (isset($mkIdx)) {
  483. return true;
  484. }
  485. } catch (\Exception $e) {
  486. //do nothing
  487. }
  488. return false;
  489. }
  490. public function isEncryptionStringData($ct)
  491. {
  492. try {
  493. $ct = base64_decode($ct);
  494. return $this->isEncryptionBytesData($ct);
  495. } catch (\Exception $e) {
  496. //do nothing
  497. }
  498. return false;
  499. }
  500. /* Indicate whether the cipher can be decrypted or not, and return CIPHER_ST_XXX
  501. *
  502. * @param string $ct
  503. *
  504. * @return constant
  505. */
  506. public function isDecryptable($ct) {
  507. try{
  508. $ct = base64_decode($ct);
  509. $ctype_ = unpack("C", substr($ct, 0, Constants::CIPHER_TYPE_LEN));
  510. $ctype = $ctype_[1];
  511. $isStrong = FALSE;
  512. if($ctype == Constants::CIPHER_TYPE_LARGE ||
  513. $ctype == Constants::CIPHER_TYPE_REGULAR){
  514. $isStrong = TRUE;
  515. }
  516. $mkIdx = $this->extractKeyId($ct, $isStrong);
  517. if($mkIdx === NULL){
  518. return ResultType::Malformed;
  519. }
  520. if($this->cache_ks->searchDeckey($mkIdx) !== NULL){
  521. return ResultType::Decryptable;
  522. }elseif ($this->cache_ks->hasFutureKeyID($mkIdx)){
  523. return ResultType::Feasible;
  524. }else{
  525. return ResultType::UnDecryptable;
  526. }
  527. } catch (\Exception $e){
  528. return ResultType::Malformed;
  529. }
  530. }
  531. /* Get given cipher information
  532. *
  533. * @param string ct
  534. *
  535. * @return CipherResult
  536. */
  537. public function getCipherResult($ct) {
  538. try{
  539. $ctype_ = unpack("C", substr($ct, 0, Constants::ALGO_TYPE_LEN));
  540. $ctype = $ctype_[1];
  541. // for weak version
  542. $flag = FALSE;
  543. // MalformedException will be thrown if ctype not matched any of cipher type
  544. if(CipherType::fromValue($ctype) === CipherType::LARGE||
  545. CipherType::fromValue($ctype) === CipherType::REGULAR){
  546. $flag = TRUE;
  547. }
  548. $mkIdx = $this->extractKeyId($ct, $flag);
  549. if($mkIdx === NULL){
  550. return new CipherResult(ResultType::Malformed, NULL, FALSE);
  551. }
  552. if($this->cache_ks->searchDeckey($mkIdx) !== NULL){
  553. return new CipherResult(ResultType::Decryptable, $mkIdx, $flag);
  554. }elseif ($this->cache_ks->hasFutureKeyID($mkIdx)){
  555. return new CipherResult(ResultType::Feasible, $mkIdx, $flag);
  556. }else{
  557. return new CipherResult(ResultType::UnDecryptable, $mkIdx, $flag);
  558. }
  559. } catch (\Exception $e){
  560. // format error or other error
  561. return new CipherResult(ResultType::Malformed, NULL, FALSE);
  562. }
  563. }
  564. private function extractKeyId($ct, $isStrong){
  565. $offset = 0;
  566. $eid = NULL;
  567. // skip ciphertext type
  568. $offset += 1;
  569. if($isStrong){
  570. $eidLen_ = unpack("n", substr($ct, $offset, 2));
  571. $eidLen = $eidLen_[1];
  572. $offset += 2;
  573. // length checking, not enough space
  574. if(strlen($ct)-3 < $eidLen){
  575. return NULL;
  576. }
  577. $eid = substr($ct, $offset, $eidLen);
  578. }else{
  579. // skip algorithm
  580. $offset += 1;
  581. // length checking, not enough space
  582. if(strlen($ct)-2 < Constants::DEFAULT_KEYID_LEN){
  583. return NULL;
  584. }
  585. $eid = substr($ct, $offset, Constants::DEFAULT_KEYID_LEN);
  586. }
  587. return $eid;
  588. }
  589. /** sign data
  590. * @param string $input
  591. *
  592. * @return string singed data
  593. */
  594. public function sign($input){
  595. // validate token first
  596. $this->validateToken();
  597. $k = $this->cache_ks->getEncKeyByVersion($this->kmc->getMajorKeyVersion());
  598. $this->check_key_status_forEncryption($k, FALSE);
  599. $this->log->info("Signing with key version:".$k->getVersion());
  600. $sigData = null;
  601. try{
  602. $sigData = $k->sign($input);
  603. $this->reporter->insertStatReport(StatisticType::SIGNCNT);
  604. } catch (\Throwable $e){
  605. $this->reporter->insertStatReport(StatisticType::SIGNERRCNT);
  606. throw $e;
  607. }
  608. return $sigData;
  609. }
  610. public function verify($input, $sig){
  611. $sig_decoded = base64_decode($sig);
  612. if(strlen($sig_decoded) <= Constants::DEFAULT_KEYID_LEN + Constants::DEFAULT_SEED_LEN){
  613. $this->reporter->insertStatReport(StatisticType::VERIFYERRCNT);
  614. throw new MalformedException("Corrupted signature with illegal length.");
  615. }
  616. $keyid = substr($sig_decoded, 0, Constants::DEFAULT_KEYID_LEN);
  617. if($this->cache_ks->hasFutureKeyID($keyid)){
  618. $this->log->info("Feasible case: KMS client needs to fetch keys from KMS.");
  619. $this->reporter->insertEventReport(TDEStatus::$SDK_TRIGGER_ROTATED_KEY_FETCH["code"], TDEStatus::$SDK_TRIGGER_ROTATED_KEY_FETCH["message"]);
  620. // fetch keys from KMS
  621. // blocking call!!
  622. $this->kmc->fetchMKeys(0);
  623. if($this->cache_ks->hasFutureKeyID($keyid)){
  624. $this->reporter->insertErrReport(
  625. TDEStatus::$SDK_FAILS_TO_FETCH_UPDATED_KEYS["code"],
  626. TDEStatus::$SDK_FAILS_TO_FETCH_UPDATED_KEYS["message"].base64_encode($keyid),
  627. EMPTYSTR,
  628. MSG_LEVEL::SEVER);
  629. $this->reporter->insertStatReport(StatisticType::VERIFYERRCNT);
  630. throw new NoValidKeyException(TDEStatus::$SDK_FAILS_TO_FETCH_UPDATED_KEYS["message"].base64_encode($keyid));
  631. }
  632. }
  633. $k = $this->cache_ks->searchDeckey($keyid);
  634. if($k == null){
  635. $errMsg = TDEStatus::$SDK_HAS_NO_CORRESPONDING_VERIFY_KEYS["message"].base64_encode($keyid);
  636. $this->log->critical($errMsg);
  637. $this->reporter->insertErrReport(
  638. TDEStatus::$SDK_HAS_NO_CORRESPONDING_VERIFY_KEYS["code"],
  639. $errMsg,
  640. EMPTYSTR,
  641. MSG_LEVEL::SEVER);
  642. $this->reporter->insertStatReport(StatisticType::VERIFYERRCNT);
  643. throw new NoValidKeyException($errMsg);
  644. }
  645. $this->check_key_status_forDecryption($k, FALSE);
  646. $this->log->info("Verifying with key version:".$k->getVersion());
  647. $ret = FALSE;
  648. try{
  649. $ret = $k->verify($input, $sig);
  650. $this->reporter->insertStatReport(StatisticType::VERIFYCNT);
  651. } catch (\Throwable $e){
  652. $this->reporter->insertStatReport(StatisticType::VERIFYERRCNT);
  653. throw $e;
  654. }
  655. return $ret;
  656. }
  657. public function obtainWildCardKeyWordIndex($spt){
  658. $spt = IndexCalculationHelper::formatPlaintext($spt);
  659. $k0 = $this->cache_ks->getEncKeyByVersion(0);
  660. if($k0 == null){
  661. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  662. $this->reporter->insertErrReport(
  663. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["code"],
  664. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"],
  665. EMPTYSTR,
  666. MSG_LEVEL::SEVER);
  667. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  668. }
  669. $key = KeyEncryption::wrap($k0, self::keyWordSalt);
  670. $nonce = substr($key, 0, 24);
  671. // prepare to encrypt with salsa
  672. $m = FieldElement::fromString($spt);
  673. $k = FieldElement::fromString($key);
  674. $n = FieldElement::fromString($nonce);
  675. $ret = Salsa20::instance()->crypto_stream_xor($m,count($m), $n, $k);
  676. return $ret->toHex();
  677. }
  678. public function calculateWildCardKeyWord($queryW, $asciiCharPrefixNumber = 0, $nonAsciiCharPrefixNumber = 0){
  679. $queryW = IndexCalculationHelper::generateWildcardKeyword($queryW, $asciiCharPrefixNumber, $nonAsciiCharPrefixNumber);
  680. $queryW = IndexCalculationHelper::formatQueryKeyword($queryW);
  681. $k0 = $this->cache_ks->getEncKeyByVersion(0);
  682. if($k0 == null){
  683. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  684. $this->reporter->insertErrReport(
  685. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["code"],
  686. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"],
  687. EMPTYSTR,
  688. MSG_LEVEL::SEVER);
  689. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  690. }
  691. $key = KeyEncryption::wrap($k0, self::keyWordSalt);
  692. $nonce = substr($key, 0, 24);
  693. // prepare to encrypt with salsa
  694. $m = FieldElement::fromString($queryW);
  695. $k = FieldElement::fromString($key);
  696. $n = FieldElement::fromString($nonce);
  697. $ret = Salsa20::instance()->crypto_stream_xor($m,count($m), $n, $k);
  698. $skip = 0;
  699. for($i = 0; $i < mb_strlen($queryW); $i ++){
  700. if(mb_substr($queryW, $i, 1) == IndexCalculationHelper::ASCII_PLACEHOLDER){
  701. $skip ++;
  702. } else{
  703. break;
  704. }
  705. }
  706. if($skip == mb_strlen($queryW)){
  707. throw new \Exception("keyword format does not match!");
  708. }
  709. return substr($ret->toHex(), $skip * 2);
  710. }
  711. public function obtainKeyWordIndex($spt){
  712. $k0 = $this->cache_ks->getEncKeyByVersion(0);
  713. if($k0 == null){
  714. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  715. $this->reporter->insertErrReport(
  716. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["code"],
  717. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"],
  718. EMPTYSTR,
  719. MSG_LEVEL::SEVER);
  720. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  721. }
  722. $key = KeyEncryption::wrap($k0, self::keyWordSalt);
  723. $nonce = substr($key, 0, 24);
  724. $k = FieldElement::fromString($key);
  725. $n = FieldElement::fromString($nonce);
  726. $ret = "";
  727. $mblength = mb_strlen($spt);
  728. for($i = 0; $i < $mblength; $i ++){
  729. $subW = mb_substr($spt, $i, 1);
  730. $len = strlen($subW);
  731. if($len < 4){
  732. $padOffset_ = unpack("c*", $subW);
  733. $padOffset = abs($padOffset_[1]) % (strlen($key)-8);
  734. $padSize = 4 - $len;
  735. $subW .= substr($key, $padOffset+4, $padSize);
  736. }
  737. $m = FieldElement::fromString($subW);
  738. $ct = Salsa20::instance()->crypto_stream_xor($m, count($m), $n, $k);
  739. $ret .= str_replace("==", "", $ct->toBase64());
  740. }
  741. return $ret;
  742. }
  743. public function calculateKeyWord($queryW) {
  744. $k0 = $this->cache_ks->getEncKeyByVersion(0);
  745. if($k0 == null){
  746. $this->log->critical(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  747. $this->reporter->insertErrReport(
  748. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["code"],
  749. TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"],
  750. EMPTYSTR,
  751. MSG_LEVEL::SEVER);
  752. throw new NoValidKeyException(TDEStatus::$SDK_HAS_NO_AVAILABLE_KEYS["message"]);
  753. }
  754. $key = KeyEncryption::wrap($k0, self::keyWordSalt);
  755. $nonce = substr($key, 0, 24);
  756. $k = FieldElement::fromString($key);
  757. $n = FieldElement::fromString($nonce);
  758. $ret = "";
  759. $mblength = mb_strlen($queryW);
  760. for($i = 0; $i < $mblength; $i ++){
  761. $subW = mb_substr($queryW, $i, 1);
  762. $len = strlen($subW);
  763. if($len < 4){
  764. $padOffset_ = unpack("c*", $subW);
  765. $padOffset = abs($padOffset_[1]) % (strlen($key)-8);
  766. $padSize = 4 - $len;
  767. $subW .= substr($key, $padOffset+4, $padSize);
  768. }
  769. $m = FieldElement::fromString($subW);
  770. $ct = Salsa20::instance()->crypto_stream_xor($m, count($m), $n, $k);
  771. $ret .= str_replace("==", "", $ct->toBase64());
  772. }
  773. return $ret;
  774. }
  775. /* Get current service identifier
  776. *
  777. * @return string
  778. */
  779. public function getServiceIdentifier() {
  780. return $this->token == null ? "Unknown Service" : $this->token->get_service_name();
  781. }
  782. /* Get current stat results
  783. *
  784. * @return array
  785. */
  786. public function getStatistics($reset=false) {
  787. if ($this->statistic) {
  788. $stat = $this->statistic;
  789. if ($reset) {
  790. for ($i = 0; $i < array_sum($stat); $i++) {
  791. $this->statistic[$i] = 0;
  792. }
  793. }
  794. return $stat;
  795. }
  796. }
  797. public function stat($statType)
  798. {
  799. ++ $this->statistic[StatisticType::type($statType)];
  800. }
  801. /* Get sdk version
  802. *
  803. * @return string
  804. */
  805. public static function getSdkVer() {
  806. return self::version;
  807. }
  808. /* Get current token identifier
  809. *
  810. * @return string
  811. */
  812. public function getTokenIdentifier() {
  813. return $this->token == null ? "Unknown TID" : $this->token->get_id();
  814. }
  815. /* Get token origin TOKEN_ORIGIN_XXX
  816. *
  817. * @return constant
  818. */
  819. public function getTokenOrigin() {
  820. return $this->token == null ? "Unknown OriginType" : $this->token->getOriginType();
  821. }
  822. /* Indicate whether encryption/decryption keys are ready in memory
  823. *
  824. * @return bool
  825. */
  826. public function isKeyChainReady() {
  827. return $this->kmc->isKeyChainReady();
  828. }
  829. /* For internal test only!
  830. * Clear key cache
  831. *
  832. * @return void
  833. */
  834. public function manuallyDeletesKeys() {
  835. $this->cache_ks->removeAllMKeys();
  836. $this->kmc->resetKeyChainFlag();
  837. }
  838. private function validateToken(){
  839. if(!$this->token->check_effective()){
  840. $this->log->critical("Please use this token after: ".$this->token->getEffectiveDate());
  841. $this->reporter->insertErrReport(
  842. TDEStatus::$SDK_USE_INEFFECTIVE_TOKEN["code"],
  843. TDEStatus::$SDK_USE_INEFFECTIVE_TOKEN["message"],
  844. EMPTYSTR,
  845. MSG_LEVEL::SEVER);
  846. throw new InvalidTokenException(TDEStatus::$SDK_USE_INEFFECTIVE_TOKEN["message"]);
  847. }
  848. $state = $this->token->check_expired(Constants::TOKEN_EXP_DELTA);
  849. if($state === STATE::EXPIRED){
  850. $this->log->critical("Please apply for a new token online. The current token is already expired for more than 30 days.");
  851. $this->reporter->insertErrReport(
  852. TDEStatus::$SDK_USE_HARD_EXPIRED_TOKEN["code"],
  853. TDEStatus::$SDK_USE_HARD_EXPIRED_TOKEN["message"],
  854. EMPTYSTR,
  855. MSG_LEVEL::SEVER);
  856. throw new InvalidTokenException(TDEStatus::$SDK_USE_HARD_EXPIRED_TOKEN["message"]);
  857. } elseif ($state === STATE::EXPIREWARNING){
  858. $this->log->warning("Token is already expired but less than 30 days. We still allow it to be operated. Token expired date: ".$this->token->getExpiredDate());
  859. $this->reporter->insertErrReport(
  860. TDEStatus::$SDK_USE_SOFT_EXPIRED_TOKEN["code"],
  861. TDEStatus::$SDK_USE_SOFT_EXPIRED_TOKEN["message"],
  862. EMPTYSTR,
  863. MSG_LEVEL::WARN);
  864. }
  865. }
  866. public static function generateCustomerToken($customerUserId, $appKey)
  867. {
  868. return '_' . $customerUserId . '_' . $appKey;
  869. }
  870. /**
  871. * @return false|string
  872. */
  873. public function toJson()
  874. {
  875. return json_encode(get_object_vars($this));
  876. }
  877. public function checkNullParam($report=false)
  878. {
  879. $objectVars = get_object_vars($this);
  880. foreach ($objectVars as $key => $value) {
  881. if (!isset($value)) {
  882. if ($report) {
  883. $msg = $key . " is not set in tdeClient, tdeClient to json=" . $this->toJson();
  884. if (isset($this->log)) {
  885. $this->log->error($msg);
  886. }
  887. if (isset($this->reporter)) {
  888. $this->reporter->insertErrReport(
  889. TDEStatus::$SDK_HAS_PROPERTY_NOT_SET["code"],
  890. TDEStatus::$SDK_HAS_PROPERTY_NOT_SET["message"] . $msg,
  891. EMPTYSTR,
  892. MSG_LEVEL::ERROR);
  893. }
  894. }
  895. return $key;
  896. }
  897. }
  898. return null;
  899. }
  900. }
  901. // Parsed cipher information
  902. class CipherResult {
  903. public $keyid;
  904. public $status;
  905. public $isStrong;
  906. public function __construct($resultType, $keyID, $isStrong){
  907. $this->keyid = $keyID;
  908. $this->status = $resultType;
  909. $this->isStrong = $isStrong;
  910. }
  911. }
  912. abstract class ResultType {
  913. const Decryptable = 0;
  914. const Malformed = 1;
  915. const Feasible =2;
  916. const UnDecryptable = 3;
  917. }
  918. abstract class CipherType{
  919. const WEAK = 0;
  920. const REGULAR = 1;
  921. const LARGE = 2;
  922. public static function fromValue($code){
  923. switch($code) {
  924. case 0:
  925. return CipherType::WEAK;
  926. case 1:
  927. return CipherType::REGULAR;
  928. case 2:
  929. return CipherType::LARGE;
  930. default:
  931. throw new MalformedException("unknown cipher type.");
  932. }
  933. }
  934. }
  935. abstract class StatisticType{
  936. const ENCCNT = 0;
  937. const DECCNT = 1;
  938. const ENCERRCNT =2;
  939. const DECERRCNT =3;
  940. const SIGNCNT = 4;
  941. const VERIFYCNT = 5;
  942. const SIGNERRCNT = 6;
  943. const VERIFYERRCNT = 7;
  944. /**
  945. * @param int $type
  946. * @return int
  947. * @throws \Exception
  948. */
  949. public static function type($type)
  950. {
  951. if ($type < 0 || $type > 7) {
  952. throw new \Exception('wrong statistic type, expected type between 0 and 7, but actual type is '.$type);
  953. }
  954. return $type;
  955. }
  956. }