新版订单消耗系统

AopCertification.php 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. <?php
  2. /**
  3. * 验证支付宝公钥证书是否可信
  4. * @param $alipayCert 支付宝公钥证书
  5. * @param $rootCert 支付宝根证书
  6. */
  7. function isTrusted($alipayCert, $rootCert)
  8. {
  9. $alipayCerts = readPemCertChain($alipayCert);
  10. $rootCerts = readPemCertChain($rootCert);
  11. if(verifyCertChain($alipayCerts, $rootCerts)){
  12. return verifySignature($alipayCert, $rootCert);
  13. }else{
  14. return false;
  15. }
  16. }
  17. function verifySignature($alipayCert, $rootCert){
  18. $alipayCertArray = explode("-----END CERTIFICATE-----", $alipayCert);
  19. $rootCertArray = explode("-----END CERTIFICATE-----", $rootCert);
  20. $length = count($rootCertArray) - 1;
  21. $checkSign = isCertSigner($alipayCertArray[0]."-----END CERTIFICATE-----",$alipayCertArray[1]."-----END CERTIFICATE-----");
  22. if(!$checkSign){
  23. $checkSign = isCertSigner($alipayCertArray[1]."-----END CERTIFICATE-----",$alipayCertArray[0]."-----END CERTIFICATE-----");
  24. if($checkSign){
  25. $issuer = openssl_x509_parse($alipayCertArray[0]."-----END CERTIFICATE-----")['issuer'];
  26. for($i = 0; $i < $length; $i++){
  27. $subject = openssl_x509_parse($rootCertArray[$i]."-----END CERTIFICATE-----")['subject'];
  28. if($issuer == $subject){
  29. isCertSigner($alipayCertArray[0]."-----END CERTIFICATE-----",$rootCertArray[$i].$rootCertArray);
  30. return $checkSign;
  31. }
  32. }
  33. }else{
  34. return $checkSign;
  35. }
  36. }else{
  37. $issuer = openssl_x509_parse($alipayCertArray[1]."-----END CERTIFICATE-----")['issuer'];
  38. for($i = 0; $i < $length; $i++){
  39. $subject = openssl_x509_parse($rootCertArray[$i]."-----END CERTIFICATE-----")['subject'];
  40. if($issuer == $subject){
  41. $checkSign = isCertSigner($alipayCertArray[1]."-----END CERTIFICATE-----",$rootCertArray[$i]."-----END CERTIFICATE-----");
  42. return $checkSign;
  43. }
  44. }
  45. return $checkSign;
  46. }
  47. }
  48. function readPemCertChain($cert)
  49. {
  50. $array = explode("-----END CERTIFICATE-----", $cert);
  51. $certs[] = null;
  52. for ($i = 0; $i < count($array) - 1; $i++) {
  53. $certs[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
  54. }
  55. return $certs;
  56. }
  57. function verifyCert($prev, $rootCerts)
  58. {
  59. $nowTime = time();
  60. if ($nowTime < $prev['validFrom_time_t']) {
  61. echo "证书未激活";
  62. return false;
  63. }
  64. if ($nowTime > $prev['validTo_time_t']) {
  65. echo "证书已经过期";
  66. return false;
  67. }
  68. $subjectMap = null;
  69. for ($i = 0; $i < count($rootCerts); $i++) {
  70. $subjectDN = array2string($rootCerts[$i]['subject']);
  71. $subjectMap[$subjectDN] = $rootCerts[$i];
  72. }
  73. $issuerDN = array2string(($prev['issuer']));
  74. if (!array_key_exists($issuerDN, $subjectMap)) {
  75. echo "证书链验证失败";
  76. return false;
  77. }
  78. return true;
  79. }
  80. /**
  81. * 验证证书链是否是信任证书库中证书签发的
  82. * @param $alipayCerts 目标验证证书列表
  83. * @param $rootCerts 可信根证书列表
  84. */
  85. function verifyCertChain($alipayCerts, $rootCerts)
  86. {
  87. $sorted = sortByDn($alipayCerts);
  88. if (!$sorted) {
  89. echo "证书链验证失败:不是完整的证书链";
  90. return false;
  91. }
  92. //先验证第一个证书是不是信任库中证书签发的
  93. $prev = $alipayCerts[0];
  94. $firstOK = verifyCert($prev, $rootCerts);
  95. $length = count($alipayCerts);
  96. if (!$firstOK || $length == 1) {
  97. return $firstOK;
  98. }
  99. $nowTime = time();
  100. //验证证书链
  101. for ($i = 1; $i < $length; $i++) {
  102. $cert = $alipayCerts[$i];
  103. if ($nowTime < $cert['validFrom_time_t']) {
  104. echo "证书未激活";
  105. return false;
  106. }
  107. if ($nowTime > $cert['validTo_time_t']) {
  108. echo "证书已经过期";
  109. return false;
  110. }
  111. }
  112. return true;
  113. }
  114. /**
  115. * 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]...
  116. * @param $certs 证书链
  117. */
  118. function sortByDn(&$certs)
  119. {
  120. //是否包含自签名证书
  121. $hasSelfSignedCert = false;
  122. $subjectMap = null;
  123. $issuerMap = null;
  124. for ($i = 0; $i < count($certs); $i++) {
  125. if (isSelfSigned($certs[$i])) {
  126. if ($hasSelfSignedCert) {
  127. return false;
  128. }
  129. $hasSelfSignedCert = true;
  130. }
  131. $subjectDN = array2string($certs[$i]['subject']);
  132. $issuerDN = array2string(($certs[$i]['issuer']));
  133. $subjectMap[$subjectDN] = $certs[$i];
  134. $issuerMap[$issuerDN] = $certs[$i];
  135. }
  136. $certChain = null;
  137. addressingUp($subjectMap, $certChain, $certs[0]);
  138. addressingDown($issuerMap, $certChain, $certs[0]);
  139. //说明证书链不完整
  140. if (count($certs) != count($certChain)) {
  141. return false;
  142. }
  143. //将证书链复制到原先的数据
  144. for ($i = 0; $i < count($certs); $i++) {
  145. $certs[$i] = $certChain[count($certs) - $i - 1];
  146. }
  147. return true;
  148. }
  149. /**
  150. * 验证证书是否是自签发的
  151. * @param $cert 目标证书
  152. */
  153. function isSelfSigned($cert)
  154. {
  155. $subjectDN = array2string($cert['subject']);
  156. $issuerDN = array2string($cert['issuer']);
  157. return ($subjectDN == $issuerDN);
  158. }
  159. function array2string($array)
  160. {
  161. $string = [];
  162. if ($array && is_array($array)) {
  163. foreach ($array as $key => $value) {
  164. $string[] = $key . '=' . $value;
  165. }
  166. }
  167. return implode(',', $string);
  168. }
  169. /**
  170. * 向上构造证书链
  171. * @param $subjectMap 主题和证书的映射
  172. * @param $certChain 证书链
  173. * @param $current 当前需要插入证书链的证书,include
  174. */
  175. function addressingUp($subjectMap, &$certChain, $current)
  176. {
  177. $certChain[] = $current;
  178. if (isSelfSigned($current)) {
  179. return;
  180. }
  181. $issuerDN = array2string($current['issuer']);
  182. if (!array_key_exists($issuerDN, $subjectMap)) {
  183. return;
  184. }
  185. addressingUp($subjectMap, $certChain, $subjectMap[$issuerDN]);
  186. }
  187. /**
  188. * 向下构造证书链
  189. * @param $issuerMap 签发者和证书的映射
  190. * @param $certChain 证书链
  191. * @param $current 当前需要插入证书链的证书,exclude
  192. */
  193. function addressingDown($issuerMap, &$certChain, $current)
  194. {
  195. $subjectDN = array2string($current['subject']);
  196. if (!array_key_exists($subjectDN, $issuerMap)) {
  197. return $certChain;
  198. }
  199. $certChain[] = $issuerMap[$subjectDN];
  200. addressingDown($issuerMap, $certChain, $issuerMap[$subjectDN]);
  201. }
  202. /**
  203. * Extract signature from der encoded cert.
  204. * Expects x509 der encoded certificate consisting of a section container
  205. * containing 2 sections and a bitstream. The bitstream contains the
  206. * original encrypted signature, encrypted by the public key of the issuing
  207. * signer.
  208. * @param string $der
  209. * @return string on success
  210. * @return bool false on failures
  211. */
  212. function extractSignature($der=false) {
  213. if (strlen($der) < 5) { return false; }
  214. // skip container sequence
  215. $der = substr($der,4);
  216. // now burn through two sequences and the return the final bitstream
  217. while(strlen($der) > 1) {
  218. $class = ord($der[0]);
  219. $classHex = dechex($class);
  220. switch($class) {
  221. // BITSTREAM
  222. case 0x03:
  223. $len = ord($der[1]);
  224. $bytes = 0;
  225. if ($len & 0x80) {
  226. $bytes = $len & 0x0f;
  227. $len = 0;
  228. for ($i = 0; $i < $bytes; $i++) {
  229. $len = ($len << 8) | ord($der[$i + 2]);
  230. }
  231. }
  232. return substr($der,3 + $bytes, $len);
  233. break;
  234. // SEQUENCE
  235. case 0x30:
  236. $len = ord($der[1]);
  237. $bytes = 0;
  238. if ($len & 0x80) {
  239. $bytes = $len & 0x0f;
  240. $len = 0;
  241. for($i = 0; $i < $bytes; $i++) {
  242. $len = ($len << 8) | ord($der[$i + 2]);
  243. }
  244. }
  245. $contents = substr($der, 2 + $bytes, $len);
  246. $der = substr($der,2 + $bytes + $len);
  247. break;
  248. default:
  249. return false;
  250. break;
  251. }
  252. }
  253. return false;
  254. }
  255. /**
  256. * Get signature algorithm oid from der encoded signature data.
  257. * Expects decrypted signature data from a certificate in der format.
  258. * This ASN1 data should contain the following structure:
  259. * SEQUENCE
  260. * SEQUENCE
  261. * OID (signature algorithm)
  262. * NULL
  263. * OCTET STRING (signature hash)
  264. * @return bool false on failures
  265. * @return string oid
  266. */
  267. function getSignatureAlgorithmOid($der=null) {
  268. // Validate this is the der we need...
  269. if (!is_string($der) or strlen($der) < 5) { return false; }
  270. $bit_seq1 = 0;
  271. $bit_seq2 = 2;
  272. $bit_oid = 4;
  273. if (ord($der[$bit_seq1]) !== 0x30) {
  274. die('Invalid DER passed to getSignatureAlgorithmOid()');
  275. }
  276. if (ord($der[$bit_seq2]) !== 0x30) {
  277. die('Invalid DER passed to getSignatureAlgorithmOid()');
  278. }
  279. if (ord($der[$bit_oid]) !== 0x06) {
  280. die('Invalid DER passed to getSignatureAlgorithmOid');
  281. }
  282. // strip out what we don't need and get the oid
  283. $der = substr($der,$bit_oid);
  284. // Get the oid
  285. $len = ord($der[1]);
  286. $bytes = 0;
  287. if ($len & 0x80) {
  288. $bytes = $len & 0x0f;
  289. $len = 0;
  290. for ($i = 0; $i < $bytes; $i++) {
  291. $len = ($len << 8) | ord($der[$i + 2]);
  292. }
  293. }
  294. $oid_data = substr($der, 2 + $bytes, $len);
  295. // Unpack the OID
  296. $oid = floor(ord($oid_data[0]) / 40);
  297. $oid .= '.' . ord($oid_data[0]) % 40;
  298. $value = 0;
  299. $i = 1;
  300. while ($i < strlen($oid_data)) {
  301. $value = $value << 7;
  302. $value = $value | (ord($oid_data[$i]) & 0x7f);
  303. if (!(ord($oid_data[$i]) & 0x80)) {
  304. $oid .= '.' . $value;
  305. $value = 0;
  306. }
  307. $i++;
  308. }
  309. return $oid;
  310. }
  311. /**
  312. * Get signature hash from der encoded signature data.
  313. * Expects decrypted signature data from a certificate in der format.
  314. * This ASN1 data should contain the following structure:
  315. * SEQUENCE
  316. * SEQUENCE
  317. * OID (signature algorithm)
  318. * NULL
  319. * OCTET STRING (signature hash)
  320. * @return bool false on failures
  321. * @return string hash
  322. */
  323. function getSignatureHash($der=null) {
  324. // Validate this is the der we need...
  325. if (!is_string($der) or strlen($der) < 5) { return false; }
  326. if (ord($der[0]) !== 0x30) {
  327. die('Invalid DER passed to getSignatureHash()');
  328. }
  329. // strip out the container sequence
  330. $der = substr($der,2);
  331. if (ord($der[0]) !== 0x30) {
  332. die('Invalid DER passed to getSignatureHash()');
  333. }
  334. // Get the length of the first sequence so we can strip it out.
  335. $len = ord($der[1]);
  336. $bytes = 0;
  337. if ($len & 0x80) {
  338. $bytes = $len & 0x0f;
  339. $len = 0;
  340. for ($i = 0; $i < $bytes; $i++) {
  341. $len = ($len << 8) | ord($der[$i + 2]);
  342. }
  343. }
  344. $der = substr($der, 2 + $bytes + $len);
  345. // Now we should have an octet string
  346. if (ord($der[0]) !== 0x04) {
  347. die('Invalid DER passed to getSignatureHash()');
  348. }
  349. $len = ord($der[1]);
  350. $bytes = 0;
  351. if ($len & 0x80) {
  352. $bytes = $len & 0x0f;
  353. $len = 0;
  354. for ($i = 0; $i < $bytes; $i++) {
  355. $len = ($len << 8) | ord($der[$i + 2]);
  356. }
  357. }
  358. return bin2hex(substr($der, 2 + $bytes, $len));
  359. }
  360. /**
  361. * Determine if one cert was used to sign another
  362. * Note that more than one CA cert can give a positive result, some certs
  363. * re-issue signing certs after having only changed the expiration dates.
  364. * @param string $cert - PEM encoded cert
  365. * @param string $caCert - PEM encoded cert that possibly signed $cert
  366. * @return bool
  367. */
  368. function isCertSigner($certPem=null,$caCertPem=null) {
  369. if (!function_exists('openssl_pkey_get_public')) {
  370. die('Need the openssl_pkey_get_public() function.');
  371. }
  372. if (!function_exists('openssl_public_decrypt')) {
  373. die('Need the openssl_public_decrypt() function.');
  374. }
  375. if (!function_exists('hash')) {
  376. die('Need the php hash() function.');
  377. }
  378. if (empty($certPem) or empty($caCertPem)) { return false; }
  379. // Convert the cert to der for feeding to extractSignature.
  380. $certDer = pemToDer($certPem);
  381. if (!is_string($certDer)) { die('invalid certPem'); }
  382. // Grab the encrypted signature from the der encoded cert.
  383. $encryptedSig = extractSignature($certDer);
  384. if (!is_string($encryptedSig)) {
  385. die('Failed to extract encrypted signature from certPem.');
  386. }
  387. // Extract the public key from the ca cert, which is what has
  388. // been used to encrypt the signature in the cert.
  389. $pubKey = openssl_pkey_get_public($caCertPem);
  390. if ($pubKey === false) {
  391. die('Failed to extract the public key from the ca cert.');
  392. }
  393. // Attempt to decrypt the encrypted signature using the CA's public
  394. // key, returning the decrypted signature in $decryptedSig. If
  395. // it can't be decrypted, this ca was not used to sign it for sure...
  396. $rc = openssl_public_decrypt($encryptedSig,$decryptedSig,$pubKey);
  397. if ($rc === false) { return false; }
  398. // We now have the decrypted signature, which is der encoded
  399. // asn1 data containing the signature algorithm and signature hash.
  400. // Now we need what was originally hashed by the issuer, which is
  401. // the original DER encoded certificate without the issuer and
  402. // signature information.
  403. $origCert = stripSignerAsn($certDer);
  404. if ($origCert === false) {
  405. die('Failed to extract unsigned cert.');
  406. }
  407. // Get the oid of the signature hash algorithm, which is required
  408. // to generate our own hash of the original cert. This hash is
  409. // what will be compared to the issuers hash.
  410. $oid = getSignatureAlgorithmOid($decryptedSig);
  411. if ($oid === false) {
  412. die('Failed to determine the signature algorithm.');
  413. }
  414. switch($oid) {
  415. case '1.2.840.113549.2.2': $algo = 'md2'; break;
  416. case '1.2.840.113549.2.4': $algo = 'md4'; break;
  417. case '1.2.840.113549.2.5': $algo = 'md5'; break;
  418. case '1.3.14.3.2.18': $algo = 'sha'; break;
  419. case '1.3.14.3.2.26': $algo = 'sha1'; break;
  420. case '2.16.840.1.101.3.4.2.1': $algo = 'sha256'; break;
  421. case '2.16.840.1.101.3.4.2.2': $algo = 'sha384'; break;
  422. case '2.16.840.1.101.3.4.2.3': $algo = 'sha512'; break;
  423. default:
  424. die('Unknown signature hash algorithm oid: ' . $oid);
  425. break;
  426. }
  427. // Get the issuer generated hash from the decrypted signature.
  428. $decryptedHash = getSignatureHash($decryptedSig);
  429. // Ok, hash the original unsigned cert with the same algorithm
  430. // and if it matches $decryptedHash we have a winner.
  431. $certHash = hash($algo,$origCert);
  432. return ($decryptedHash === $certHash);
  433. }
  434. /**
  435. * Convert pem encoded certificate to DER encoding
  436. * @return string $derEncoded on success
  437. * @return bool false on failures
  438. */
  439. function pemToDer($pem=null) {
  440. if (!is_string($pem)) { return false; }
  441. $cert_split = preg_split('/(-----((BEGIN)|(END)) CERTIFICATE-----)/',$pem);
  442. if (!isset($cert_split[1])) { return false; }
  443. return base64_decode($cert_split[1]);
  444. }
  445. /**
  446. * Obtain der cert with issuer and signature sections stripped.
  447. * @param string $der - der encoded certificate
  448. * @return string $der on success
  449. * @return bool false on failures.
  450. */
  451. function stripSignerAsn($der=null) {
  452. if (!is_string($der) or strlen($der) < 8) { return false; }
  453. $bit = 4;
  454. $len = ord($der[($bit + 1)]);
  455. $bytes = 0;
  456. if ($len & 0x80) {
  457. $bytes = $len & 0x0f;
  458. $len = 0;
  459. for($i = 0; $i < $bytes; $i++) {
  460. $len = ($len << 8) | ord($der[$bit + $i + 2]);
  461. }
  462. }
  463. return substr($der,4,$len + 4);
  464. }