123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- #!/usr/bin/env php
- <?php
- // load autoload.php
- $possibleFiles = [__DIR__.'/../vendor/autoload.php', __DIR__.'/../../../autoload.php', __DIR__.'/../../autoload.php'];
- $file = null;
- foreach ($possibleFiles as $possibleFile) {
- if (file_exists($possibleFile)) {
- $file = $possibleFile;
- break;
- }
- }
- if (null === $file) {
- throw new RuntimeException('Unable to locate autoload.php file.');
- }
- require_once $file;
- unset($possibleFiles, $possibleFile, $file);
- use GuzzleHttp\HandlerStack;
- use GuzzleHttp\Handler\CurlHandler;
- use GuzzleHttp\Client;
- use GuzzleHttp\Exception\RequestException;
- use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
- use WechatPay\GuzzleMiddleware\Validator;
- use WechatPay\GuzzleMiddleware\Util\PemUtil;
- use WechatPay\GuzzleMiddleware\Util\AesUtil;
- use WechatPay\GuzzleMiddleware\Auth\CertificateVerifier;
- use WechatPay\GuzzleMiddleware\Auth\WechatPay2Validator;
- class CertificateDownloader
- {
- const VERSION = '0.1.0';
- public function run()
- {
- $opts = $this->parseOpts();
- if (!$opts) {
- $this->printHelp();
- exit(1);
- }
- if (isset($opts['help'])) {
- $this->printHelp();
- exit(0);
- }
- if (isset($opts['version'])) {
- echo self::VERSION . "\n";
- exit(0);
- }
- $this->downloadCert($opts);
- }
- private function downloadCert($opts)
- {
- try {
- // 构造一个WechatPayMiddleware
- $builder = WechatPayMiddleware::builder()
- ->withMerchant($opts['mchid'], $opts['serialno'], PemUtil::loadPrivateKey($opts['privatekey'])); // 传入商户相关配置
- if (isset($opts['wechatpay-cert'])) {
- $builder->withWechatPay([ PemUtil::loadCertificate($opts['wechatpay-cert']) ]); // 使用平台证书验证
- }
- else {
- $builder->withValidator(new NoopValidator); // 临时"跳过”应答签名的验证
- }
- $wechatpayMiddleware = $builder->build();
- // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
- $stack = HandlerStack::create();
- $stack->push($wechatpayMiddleware, 'wechatpay');
- // 创建Guzzle HTTP Client时,将HandlerStack传入
- $client = new GuzzleHttp\Client(['handler' => $stack]);
- // 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
- $resp = $client->request('GET', 'https://api.mch.weixin.qq.com/v3/certificates', [
- 'headers' => [ 'Accept' => 'application/json' ]
- ]);
- if ($resp->getStatusCode() < 200 || $resp->getStatusCode() > 299) {
- echo "download failed, code={$resp->getStatusCode()}, body=[{$resp->getBody()}]\n";
- return;
- }
- $list = json_decode($resp->getBody(), true);
- $plainCerts = [];
- $x509Certs = [];
- $decrypter = new AesUtil($opts['key']);
- foreach ($list['data'] as $item) {
- $encCert = $item['encrypt_certificate'];
- $plain = $decrypter->decryptToString($encCert['associated_data'],
- $encCert['nonce'], $encCert['ciphertext']);
- if (!$plain) {
- echo "encrypted certificate decrypt fail!\n";
- exit(1);
- }
- // 通过加载对证书进行简单合法性检验
- $cert = \openssl_x509_read($plain); // 从字符串中加载证书
- if (!$cert) {
- echo "downloaded certificate check fail!\n";
- exit(1);
- }
- $plainCerts[] = $plain;
- $x509Certs[] = $cert;
- }
- // 使用下载的证书再来验证一次应答的签名
- $validator = new WechatPay2Validator(new CertificateVerifier($x509Certs));
- if (!$validator->validate($resp)) {
- echo "validate response fail using downloaded certificates!";
- exit(1);
- }
- // 输出证书信息,并保存到文件
- foreach ($list['data'] as $index => $item) {
- echo "Certificate {\n";
- echo " Serial Number: ".$item['serial_no']."\n";
- echo " Not Before: ".(new DateTime($item['effective_time']))->format('Y-m-d H:i:s')."\n";
- echo " Not After: ".(new DateTime($item['expire_time']))->format('Y-m-d H:i:s')."\n";
- echo " Text: \n ".str_replace("\n", "\n ", $plainCerts[$index])."\n";
- echo "}\n";
- $outpath = $opts['output'].DIRECTORY_SEPARATOR.'wechatpay_'.$item['serial_no'].'.pem';
- file_put_contents($outpath, $plainCerts[$index]);
- }
- }
- catch (RequestException $e) {
- echo "download failed, message=[{$e->getMessage()}] ";
- if ($e->hasResponse()) {
- echo "code={$e->getResponse()->getStatusCode()}, body=[{$e->getResponse()->getBody()}]\n";
- }
- exit(1);
- }
- catch (Exception $e) {
- echo "download failed, message=[{$e->getMessage()}]\n";
- echo $e;
- exit(1);
- }
- }
- private function parseOpts()
- {
- $opts = [
- [ 'key', 'k', true ],
- [ 'mchid', 'm', true ],
- [ 'privatekey', 'f', true ],
- [ 'serialno', 's', true ],
- [ 'output', 'o', true ],
- [ 'wechatpay-cert', 'c', false ],
- ];
- $shortopts = 'hV';
- $longopts = [ 'help', 'version' ];
- foreach ($opts as $opt) {
- $shortopts .= $opt[1].':';
- $longopts[] = $opt[0].':';
- }
- $parsed = getopt($shortopts, $longopts);
- if (!$parsed) {
- return false;
- }
- $args = [];
- foreach ($opts as $opt) {
- if (isset($parsed[$opt[0]])) {
- $args[$opt[0]] = $parsed[$opt[0]];
- }
- else if (isset($parsed[$opt[1]])) {
- $args[$opt[0]] = $parsed[$opt[1]];
- }
- else if ($opt[2]) {
- return false;
- }
- }
- if (isset($parsed['h']) || isset($parsed['help'])) {
- $args['help'] = true;
- }
- if (isset($parsed['V']) || isset($parsed['version'])) {
- $args['version'] = true;
- }
- return $args;
- }
- private function printHelp()
- {
- echo <<<EOD
- Usage: 微信支付平台证书下载工具 [-hV] [-c=<wechatpayCertificatePath>]
- -f=<privateKeyFilePath> -k=<apiV3key> -m=<merchantId>
- -o=<outputFilePath> -s=<serialNo>
- -m, --mchid=<merchantId> 商户号
- -s, --serialno=<serialNo> 商户证书的序列号
- -f, --privatekey=<privateKeyFilePath>
- 商户的私钥文件
- -k, --key=<apiV3key> ApiV3Key
- -c, --wechatpay-cert=<wechatpayCertificatePath>
- 微信支付平台证书,验证签名
- -o, --output=<outputFilePath>
- 下载成功后保存证书的路径
- -V, --version Print version information and exit.
- -h, --help Show this help message and exit.
- EOD;
- }
- }
- class NoopValidator implements Validator
- {
- public function validate(\Psr\Http\Message\ResponseInterface $response)
- {
- return true;
- }
- }
- // main
- (new CertificateDownloader())->run();
|