AopCertification.php 16 KB

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