【问题标题】:Verify SSL Certificates using Openssl in PHP在 PHP 中使用 Openssl 验证 SSL 证书
【发布时间】:2013-11-26 03:31:03
【问题描述】:

我正在做以下事情:

Generate CSR(Certificate Signing Request)
Upload SSL Certificates

要生成 SSL 证书,我使用的是:

        $privkey = openssl_pkey_new();
        $csr = openssl_csr_new($dn, $privkey);
        $sscert = openssl_csr_sign($csr, null, $privkey, $days);
        openssl_csr_export($csr, $csrout);
        openssl_pkey_export($privkey, $pkeyout, $_POST['password']);
        openssl_pkey_export_to_file($privkey, "<path/to/store/server.key>");
        openssl_csr_export_to_file($csr, "/tmp/".<domain-name>.".csr");

现在使用该 CSR 请求,我能够生成 (domain-name.cer),(DigitalCert.cer)。

现在,一旦我上传了这个 (.cer) 证书,我需要验证这些证书。

原因:有人在“a.com”上生成了这些证书,并尝试在“b.com”上上传。这不应该发生,所以我想验证上传的 SSL 证书。

在 PHP 中,我们有

$ok = openssl_verify($data, $signature, $pubkeyid);

但根据上述证书生成过程,我无法获得将被视为 $data、$signature 和 $pubkeyid 的内容。

【问题讨论】:

  • stackoverflow.com/q/19955922/569976 我认为这就是你想要做的?
  • 在您上面的代码中,我不确定您为什么要导出 CSR 而不是 $sscert。您是否使用输出 CSR 创建另一个证书? openssl_verify() 方法通常不用于验证证书签名。
  • 感谢@gtrig,我正在使用输出 CSR 创建另一个证书..
  • 如果您从同一个私钥创建多个证书,那么它们都将具有相同的公钥和模数。在下面的答案中查看我的评论。

标签: php ssl openssl certificate


【解决方案1】:

检查一下: Verify SMTP in PHP

<?php
$server   = "smtp.gmail.com";        // Who I connect to
$myself   = "my_server.example.com"; // Who I am
$cabundle = '/etc/ssl/cacert.pem';   // Where my root certificates are

// Verify server. There's not much we can do, if we suppose that an attacker
// has taken control of the DNS. The most we can hope for is that there will
// be discrepancies between the expected responses to the following code and
// the answers from the subverted DNS server.

// To detect these discrepancies though, implies we knew the proper response
// and saved it in the code. At that point we might as well save the IP, and
// decouple from the DNS altogether.

$match1   = false;
$addrs    = gethostbynamel($server);
foreach($addrs as $addr)
{
    $name = gethostbyaddr($addr);
    if ($name == $server)
    {
        $match1 = true;
        break;
    }
}
// Here we must decide what to do if $match1 is false.
// Which may happen often and for legitimate reasons.
print "Test 1: " . ($match1 ? "PASSED" : "FAILED") . "\n";

$match2   = false;
$domain   = explode('.', $server);
array_shift($domain);
$domain = implode('.', $domain);
getmxrr($domain, $mxhosts);
foreach($mxhosts as $mxhost)
{
    $tests = gethostbynamel($mxhost);
    if (0 != count(array_intersect($addrs, $tests)))
    {
        // One of the instances of $server is a MX for its domain
        $match2 = true;
        break;
    }
}
// Again here we must decide what to do if $match2 is false.
// Most small ISP pass test 2; very large ISPs and Google fail.
print "Test 2: " . ($match2 ? "PASSED" : "FAILED") . "\n";
// On the other hand, if you have a PASS on a server you use,
// it's unlikely to become a FAIL anytime soon.

// End of maybe-they-help-maybe-they-don't checks.

// Establish the connection
$smtp = fsockopen( "tcp://$server", 25, $errno, $errstr );
fread( $smtp, 512 );

// Here you can check the usual banner from $server (or in general,
// check whether it contains $server's domain name, or whether the
// domain it advertises has $server among its MX's.
// But yet again, Google fails both these tests.

fwrite($smtp,"HELO $myself\r\n");
fread($smtp, 512);

// Switch to TLS
fwrite($smtp,"STARTTLS\r\n");
fread($smtp, 512);
stream_set_blocking($smtp, true);
stream_context_set_option($smtp, 'ssl', 'verify_peer', true);
stream_context_set_option($smtp, 'ssl', 'allow_self_signed', false);
stream_context_set_option($smtp, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($smtp, 'ssl', 'cafile', $cabundle);
$secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
stream_set_blocking($smtp, false);
$opts = stream_context_get_options($smtp);
if (!isset($opts["ssl"]["peer_certificate"]))
    $secure = false;
else
{
    $cert = openssl_x509_parse($opts["ssl"]["peer_certificate"]);
    $names = '';
    if ('' != $cert)
    {
        if (isset($cert['extensions']))
            $names = $cert['extensions']['subjectAltName'];
        elseif (isset($cert['subject']))
        {
            if (isset($cert['subject']['CN']))
                $names = 'DNS:' . $cert['subject']['CN'];
            else
                $secure = false; // No exts, subject without CN
        }
        else
            $secure = false; // No exts, no subject
    }
    $checks = explode(',', $names);

    // At least one $check must match $server
    $tmp    = explode('.', $server);
    $fles   = array_reverse($tmp);
    $okay   = false;
    foreach($checks as $check)
    {
        $tmp = explode(':', $check);
        if ('DNS' != $tmp[0])    continue;  // candidates must start with DNS:
        if (!isset($tmp[1]))     continue;  // and have something afterwards
        $tmp  = explode('.', $tmp[1]);
        if (count($tmp) < 3)     continue;  // "*.com" is not a valid match
        $cand = array_reverse($tmp);
        $okay = true;
        foreach($cand as $i => $item)
        {
            if (!isset($fles[$i]))
            {
                // We connected to www.example.com and certificate is for *.www.example.com -- bad.
                $okay = false;
                break;
            }
            if ($fles[$i] == $item)
                continue;
            if ($item == '*')
                break;
        }
        if ($okay)
            break;
    }
    if (!$okay)
        $secure = false; // No hosts matched our server.
}

if (!$secure)
        die("failed to connect securely\n");
print "Success!\n";
// Continue with connection...
?>

【讨论】:

    【解决方案2】:

    这对我有用

    $crt_md5=exec('openssl x509 -noout -modulus -in /path/to/domain.crt/ | openssl md5 | sed "s/^.* //"');
    $key_md5=exec('openssl rsa -noout -modulus -in /path/to/server.key | openssl md5 | sed "s/^.* //"');
    
    if($crt_md5 != $key_md5){
       echo 'BAD';
    }
    else{
         echo "GOOD";
    }
    

    sed "s/^.* //" - 将从输出中删除 (stdin)= 事物,这样 你得到准确的 md5 字符串

    【讨论】:

      【解决方案3】:

      这就是我的做法......

      system('openssl x509 -noout -modulus -in '.$crt.' | openssl md5', $crt_md5);
      system('openssl rsa  -noout -modulus -in '.$key.' | openssl md5', $key_md5);
      
      if($crt_md5 != $key_md5){
      echo 'BAD';
      }
      

      【讨论】:

      • 感谢@dagon 的快速回复.. 但是我怎样才能获得 $key.. 它是公钥还是私钥.. 而 $key 是实际密钥或其参考谢谢..
      • 我试过上面的 sn-p 并假设它是私钥(内容不是参考).. 它总是给我一个匹配。我从一个域生成了一个证书,并尝试为不同的域上传和验证上述代码。理想情况下,它应该给出“BAD”。但它在其他条件下给了我“好”。我回应了 $crt_md5 和 $key_md5,两者都等于“1”,因此给了我“结果很好”。我使用了 1 个域的私钥和不同域的证书。我也尝试过使用 CSR MD5,仍然得到了匹配。为什么会这样?
      • @VirenH.Ajmera,私钥和公钥将具有相同的模数。您如何生成域证书?您是否使用相同的私钥来生成 CSR/证书?如果是这样,那么您将始终与此检查匹配。私钥的模数与公钥中的模数相同,会被放入 CSR 并最终放入证书。因此,如果您对多个证书使用相同的私钥,它们都将具有相同的公钥并且都具有相同的模数。
      • @gtrig,感谢您提供的信息..我已经更新了我的代码库并且它工作了..但是我如何验证给定域证书的 CA 证书..我不能使用相同的代码,即openssl md5,因为它可能有不同的md5 ..所以我该如何验证CA证书..
      【解决方案4】:

      尝试 openssl_x509_check_private_key( $crt, $key ) 它返回布尔值

      参考http://php.net/manual/en/function.openssl-x509-check-private-key.php

      【讨论】:

        【解决方案5】:

        警告: openssl_x509_check_private_key 在某些情况下不起作用。

        例子:

        SSL证书喜欢这个:

        -----BEGIN CERTIFICATE-----
        xxxx
        -----END CERTIFICATE-----
        
        -----BEGIN CERTIFICATE-----
        xxxx
        xxxx
        

        这个证书不以-----END CERTIFICATE-----结尾,但是仍然可以通过这个函数的检查。它将返回 true 以告诉您它是正确的,但实际上并非如此。如果你将此证书上传到你的应用程序,例如 Nginx ,Nginx 会告诉你一个错误。

        这似乎不是只出现在 PHP 中的错误。如果你在命令行中检查 openssl 函数,它会告诉你同样的结果。

        所以我认为最好的方法是你需要检查证书的段落是否完整。

        确认格式正确后,使用此功能验证证书和私钥。

        【讨论】:

          猜你喜欢
          • 2012-05-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-10-27
          • 2020-10-12
          • 2010-11-08
          • 2019-06-14
          相关资源
          最近更新 更多