php生成自签名X509证书

2021-09-06

x509是证书规范,公钥证书,只有公钥

证书签名申请CSR

/**
 * @description: 证书签名申请,
 *               生成证书前需要先生成CSR文件并提交给证书颁发机构(CA)
 */
class CSR
{
    /**
     * @description: csr可以是file://path/to/csr格式指定的PEM编码的CSR文件路径或是csr字符串或openssl_csr_new生成的资源
     * @var mixed 
     */
    public static $csr;

    /**
     * @description: 证书中使用的专有名称或主题字段
     * @var array 
     */
    public static $dn = [
        'countryName'            => 'CN', // 国家(2个字母代码)
        'stateOrProvinceName'    => 'China', // 国家或省名
        'localityName'           => 'China', // 地名(城市)
        'organizationName'       => 'ORG', // 组织名,ORG为有限公司
        'organizationalUnitName' => 'ORG', // 组织单元名称
        'commonName'             => 'xxx.com', // 域名
        'emailAddress'           => 'my@163.com', // 邮件地址
    ];

    /**
     * @description: 摘要算法或签名哈希算法,openssl_get_md_methods()之一
     * @var string 
     */
    private static $_digest_alg = 'sha256';

    /**
     * @description: openssl配置文件地址
     * windows中php安装目录下存在此文件,但是php不能自动读取,有两种解决方法,
     *      1、查看phpinfo中openssl.cnf的位置,将openssl.cnf挪到查询到的位置;
     *      2、配置option中的config参数为指定的路径
     * @var array
     */
    private static $_config = 'C:/usr/local/ssl/openssl.cnf';

    /**
     * @description: 生成证书所需的CSR
     * @param resource $privkey 私钥资源,该密钥的相应公共部分将用于签署CSR
     * @param bool $notext 输出冗余度,false时输出内容附加人类可读信息(包括主体、版本等等)
     * @return string
     */
    public static function csrGen($privkey, $notext = true): string
    {
        // 初始化请求的配置
        $configargs = [
            'config'     => static::$_config,
            'digest_alg' => static::$_digest_alg
        ];
        // 为CSR指定额外的配置选项
        $extraattribs = [] ?: null;

        static::$csr = openssl_csr_new(static::$dn, $privkey, $configargs, $extraattribs);
        if (!static::$csr) {
            throw new \Exception('csr资源生成错误');
        }
        openssl_csr_export(static::$csr, $out, $notext);
        return $out;
    }

    /**
     * @description: 获取csr中专有名称信息的主题,其中包含了通用名称(CN),机构名称(O),国家名(C)等字段
     * @param {*}
     * @return array
     */
    public static function getCsrSubject(): array
    {
        static::_vertiy();
        // true返回短名称索引
        return openssl_csr_get_subject(static::$csr, true);
    }
    
    /**
     * @description: 从csr中获取公钥资源
     * @param {*}
     * @return mixed 失败返回false,成功返回resource
     */
    public static function csrGetPublicKey()
    {
        static::_vertiy();
        return openssl_csr_get_public_key(static::$csr);
    }
    
    /**
     * @description: 将csr保存在文件中
     * @param string $outfilename 文件路径
     * @param bool $notext 输出冗余度,false时输出内容附加人类可读信息(包括主体、版本等等)
     * @return bool
     */
    public static function csrOutFile($outfilename, $notext = true): bool
    {
        static::_vertiy();
        return openssl_csr_export_to_file(static::$csr, $outfilename, $notext);
    }

    /**
     * @description: 验证csr资源是否存在
     * @param {*}
     * @return void
     */
    private static function _vertiy(): void
    {
        if (!static::$csr) {
            throw new \Exception('请先生成/设置csr资源');
        }
    }

    /**
     * @description: 获取openSSL库的错误,错误消息已被队列化,可以查询到多条错误信息,最后一个是最近的一个错误。
     * @param {*}
     * @return {*}
     */
    public static function errors(): array
    {
        $result = [];
        while ($data = openssl_error_string() !== false) {
            $result[] = $data;
        }
        return $result;
    }
}

X509证书类

/**
 * @description: 证书
 */
class Certificate
{
    /**
     * @description: x509证书资源
     * @var resource 
     */
    public static $cert;

    /**
     * @description: 摘要算法或签名哈希算法,openssl_get_md_methods()之一
     * @var string 
     */
    private static $_digest_alg = 'sha256';

    /**
     * @description: openssl配置文件地址
     * windows中php安装目录下存在此文件,但是php不能自动读取,有两种解决方法,
     *      1、查看phpinfo中openssl.cnf的位置,将openssl.cnf挪到查询到的位置;
     *      2、配置option中的config参数为指定的路径
     * @var array
     */
    private static $_config = 'C:/usr/local/ssl/openssl.cnf';


    /**
     * @description: 从给定的CSR生成一个x509证书资源;用另一个证书签署 CSR (或者本身) 并且生成一个证书
     * @param mixed $csr csr资源或字符串
     * @param mixed $privkey 私钥,可以为字符串/资源,私钥资源,该密钥的相应公共部分将用于签署CSR
     * @param int $days 证书有效时长
     * @param mixed $cacert 生成的证书将由cacert证书签名。 如果cacert为 null, 生成的证书将是自签名证书
     * @param string $serial 可选的发行证书编号。如果没有指定默认值为0
     * @return void
     */
    public static function GenX509($csr, $privkey, $days = 365, $cacert = null,  $serial = '0'): void
    {
        $configargs = [
            'config'     => static::$_config,
            'digest_alg' => static::$_digest_alg
        ];
        static::$cert = openssl_csr_sign($csr, $cacert, $privkey, $days, $configargs, $serial);
        if (!static::$cert) {
            throw new \Exception('证书生成错误');
        }
    }

     /**
     * @description: 从证书文件中获取证书资源
     * @param string $certdata 包含PEM编码(-----BEGIN CERTIFICATE----- 开头)的证书文件地址
     * @return void
     */
    public static function getCertifResource($certdata): void
    {
        static::$cert = openssl_x509_read('file://' . $certdata);
    }

    /**
     * @description: 获取证书主题名称、发行方名称、目的、有效日期等字段
     * @param bool $shortnames true返回短名称索引
     * @return array
     */
    public static function getCertifSubject($shortnames = true): array
    {
        static::_vertiy();
        return openssl_x509_parse(static::$cert, $shortnames);
    }

    /**
     * @description: 验证证书签名是否由公钥所对应的私钥签名,php7.4版本以上可用
     * @param string $publicKey PEM格式的公钥
     * @return int 1、正确 0、不正确 -1、错误
     */
    public static function certifSignVerify($publicKey): int
    {
        static::_vertiy();
        return openssl_x509_verify(static::$cert, $publicKey);
    }

    /**
     * @description: 获取证书字符串
     * @param bool $notext 输出冗余度,false时输出内容附加人类可读信息
     * @return {*}
     */
    public static function certData($notext = true): string
    {
        static::_vertiy();
        openssl_x509_export(static::$cert, $output, $notext);
        return $output;
    }

    /**
     * @description: 将证书保存在文件中
     * @param string $outfilename 文件路径
     * @param bool $notext 输出冗余度,false时输出内容附加人类可读信息
     * @return void
     */
    public static function certOutFile($outfilename, $notext = true): void
    {
        static::_vertiy();
        openssl_x509_export_to_file(static::$cert, $outfilename, $notext);
    }

    /**
     * @description: 释放证书资源
     * @param {*}
     * @return void
     */
    public static function freeCert(): void
    {
        static::_vertiy();
        openssl_x509_free(static::$cert);
    }

    /**
     * @description: 验证私钥是否属于证书,比较了和密钥匹配的公共材料(比如,RSA密钥的指数和模量)和/或密钥参数(比如,EC密钥的参数)。
     * @param mixed $key 私钥资源,私钥字符串
     * @return bool
     */
    public static function verifyCertKey($key): bool
    {
        static::_vertiy();
        return openssl_x509_check_private_key(static::$cert, $key);
    }

    /**
     * @description: 计算证书摘要
     * @param string $hash_algorithm 摘要算法,openssl_get_md_methods()算法之一
     * @param bool $raw_output  true输出原始二进制数据。false输出小写的16进制字符串。
     * @return string
     */
    public static function certifDigest($hash_algorithm = 'sha256', $raw_output = false): string
    {
        static::_vertiy();
        return openssl_x509_fingerprint(static::$cert, $hash_algorithm, $raw_output);
    }

    /**
     * @description: 验证证书是否具有指定用途
     * @param int $purpose 用途,只能指定一个字段
     *                      X509_PURPOSE_SSL_CLIENT      SSL连接的客户端
     *                      X509_PURPOSE_SSL_SERVER      SSL连接的服务器端
     *                      X509_PURPOSE_NS_SSL_SERVER   Netscape SSL服务器
     *                      X509_PURPOSE_SMIME_SIGN      签名 S/MIME 邮件
     *                      X509_PURPOSE_SMIME_ENCRYPT   加密 S/MIME 邮件
     *                      X509_PURPOSE_CRL_SIGN        签名证书撤销列表(CRL)
     *                      X509_PURPOSE_ANY             任何目的
     * @param bool $cainfo  包含可信CA文件的文件夹和文件名的数组,用于验证签名/认证
     * @return mixed true、false -1、发生错误
     */
    public static function certifEffect($purpose, $cainfo = []):int
    {
        static::_vertiy();
        return openssl_x509_checkpurpose(static::$cert, $purpose, $cainfo);
    }

    /**
     * @description: 查找本地可用SSL证书的位置
     * @param {*}
     * @return array
     */
    public static function SSLlocaltion(): array
    {
        return openssl_get_cert_locations();
    }

    /**
     * @description: 验证证书资源是否存在
     * @param {*}
     * @return void
     */
    private static function _vertiy(): void
    {
        if (!static::$cert) {
            throw new \Exception('请先生成/设置证书资源');
        }
    }

    /**
     * @description: 获取openSSL库的错误,错误消息已被队列化,可以查询到多条错误信息,最后一个是最近的一个错误。
     * @param {*}
     * @return {*}
     */
    public static function errors(): array
    {
        $result = [];
        while ($data = openssl_error_string() !== false) {
            $result[] = $data;
        }
        return $result;
    }
}

生成私钥资源:SecretKey类

$path = 'C:\Users\Administrator\Desktop\12\\';
SecretKey::genPrivateKey();
SecretKey::privateKeyOutFile($path . 'pkey.key');
CSR::csrGen(SecretKey::$privateKey);
CSR::csrOutFile($path . 'csr.csr');
Certificate::GenX509(CSR::$csr, SecretKey::$privateKey);
var_dump(Certificate::certData());
Certificate::certOutFile($path . 'cert.cer');

 

{/if}