文章目录
1. X.509 V3证书介绍
X.509 是公钥证书的格式标准, 广泛用于 TLS/SSL 安全通信或者其他需要认证的环境中。
X.509 证书可以由 CA(Certificate Authority,数字证书认证机构)颁发,也可以自签名产生。
*X.509 证书中主要含有公钥、身份信息、签名信息和有效性信息等信息,这些信息用于构建一个验证公钥的体系。
- 公钥 : 非对称密码中的公钥。公钥证书的目的就是为了在互联网上分发公钥。
- 身份信息 : 公钥对应的私钥持有者的信息,域名以及用途等。
- 签名信息 : 对公钥进行签名的信息,提供公钥的验证链。可以是 CA
- 的签名或者是自签名,不同之处在于CA证书的根证书大都内置于操作系统或者浏览器中,而自签名证书的公钥验证链则需要自己维护(手动导入到操作系统中或者再验证流程中单独提供自签名的根证书)。
- 有效性信息:证书的有效时间区间,以及 CRL(证书吊销列表)等相关信息。
- X.509 证书的标准规范RFC5280中详细描述了证书的 Encoding Format(编码格式)和Structure(证书结构)。
X.509 证书相关文件常见的扩展名
- .pem : 隐私增强型电子邮件格式(缩写:PEM)格式,通常是由证书的 DER 二进制 Base64 编码得出。(最常用)。
- .key : PEM 格式的私钥文件。
- .pub : PEM 格式的公钥文件。
- .crt : PEM 格式的公钥证书文件,也可能是 DER。
- .cer : DER 格式的公钥证书文件,也可能是 PEM。
- .crs : PEM 格式的 CSR 文件,也可能是 DER。
- .p12 – PKCS#12 格式,包含证书的同时可能还包含私钥。
- .pfx – PFX,PKCS#12 之前的格式(通常用PKCS#12格式,比如由互联网信息服务产生的 PFX 文件)。
2、使用openssl生成自签名证书和解决不受信任问题
openssl命令行工具用于从shell程序使用OpenSSL加密库的各种加密功能。 它可以用于:
创建和管理私钥,公钥和参数
公钥加密操作
创建X.509证书,CSR和CRL
消息摘要的计算
使用密码进行加密和解密
SSL / TLS客户端和服务器测试
处理S / MIME签名或加密的邮件
时间戳记请求,生成和验证
2.1、生成根证书
# 生成根证书CA.crt和根证书私钥CA.key
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -subj "/C=CN/ST=GD/L=GD/O=PenngoDev/CN=Create by penngo" -keyout CA.key -out CA.crt -reqexts v3_req -extensions v3_ca
2.2、为域名生成证书申请文件
# 创建证书的私钥
openssl genrsa -out penngo.test.key 2048
# 根据私钥创建一个证书申请文件private.csr,注意证书主体的描述使用-subj参数描述,CN必须和应用中请求的地址一致,可以是IP地址或域名
openssl req -new -key penngo.test.key -subj "/C=CN/ST=GD/L=GD/O=penngo_test/CN=www.penngo.test" -sha256 -out penngo.test.csr
2.3、为域名创建证书的扩展描述文件
根据证书申请文件创建证书的扩展描述文件,如果不使用扩展描述文件,那么在浏览器中无法授信,会提示证书无效。
#penngo.test.ext
[ req ]
default_bits = 1024
distinguished_name = req_distinguished_name
req_extensions = ptest
extensions = ptest
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Definesys
localityName = Definesys
organizationName = Definesys
[PTEST]
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = DNS:www.penngo.test
2.4、为域名创建证书
根据penngo.test.csr文件和penngo.test.ext、以及根证书CA.crt创建一个域名证书penngo.test.crt
openssl x509 -req -days 365 -in penngo.test.csr -CA CA.crt -CAkey CA.key -CAcreateserial -sha256 -out penngo.test.crt -extfile penngo.test.ext -extensions PTEST
最终生成的文件包括:
CA.crt # 根证书
CA.key # 根证书私钥
penngo.test.crt # 域名证书
penngo.test.key # 域名证书私钥
根证书中的主体描述可以随意填,但是域名证书中的主体的CN必须和请求地址中的IP或域名一致。
双击根证书CA.crt选择安装证书,注意在选择安装区域时,一定要选择“受信任的根证书颁发机构”。
3、Go应用中使用自签名证书
3.1、gin框架调用实现
package main
import (
"github.com/gin-gonic/gin"
"html/template"
)
var html = template.Must(template.New("https").Parse(`
<html>
<head>
<title>Https Test</title>
<script src="/assets/app.js"></script>
</head>
<body>
<h1>Welcome, Test!</h1>
</body>
</html>
`))
func main() {
r := gin.Default()
r.Static("/assets", "./assets")
r.SetHTMLTemplate(html)
r.GET("/", func(c *gin.Context) {
c.HTML(200, "https", gin.H{
"status": "success",
})
})
// 监听并在 https://127.0.0.1:8080 上启动服务
r.RunTLS(":443", "./testdata/penngo.test.crt", "./testdata/penngo.test.key")
}
3.2、运行效果
4、使用java的bouncycastle生成ssl证书
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.penngo</groupId>
<artifactId>code_test</artifactId>
<version>1.0</version>
</parent>
<artifactId>tls_https</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-ext-jdk18on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
</dependencies>
</project>
X509V3CreateExample.java
package com.penngo;
import java.io.File;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
/**
* Basic X.509 V3 Certificate creation with TLS flagging.
*/
public class X509V3CreateExample {
static {
try {
Security.addProvider(new BouncyCastleProvider());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成Subject信息
*
* @param C Country Name (国家代号),eg: CN
* @param ST State or Province Name (洲或者省份)
* @param L Locality Name (城市名)
* @param O Organization Name (可以是公司名称)
* @param OU Organizational Unit Name (可以是单位部门名称)
* @param CN Common Name (服务器ip或者域名)
* @return X500Name Subject
*/
public static X500Name generateSubject(String C, String ST, String L,
String O, String OU, String CN) {
X500NameBuilder x500NameBuilder = new X500NameBuilder();
x500NameBuilder.addRDN(BCStyle.C, C);
x500NameBuilder.addRDN(BCStyle.ST, ST);
x500NameBuilder.addRDN(BCStyle.L, L);
x500NameBuilder.addRDN(BCStyle.O, O);
x500NameBuilder.addRDN(BCStyle.OU, OU);
x500NameBuilder.addRDN(BCStyle.CN, CN);
return x500NameBuilder.build();
}
public static X509Certificate generateV3Certificate(KeyPair pair)
throws InvalidKeyException, NoSuchProviderException, SignatureException {
// generate the certificate
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setNotBefore(new Date(System.currentTimeMillis()));
certGen.setNotAfter(new Date(System.currentTimeMillis() + (long)1000 * 3600 * 24 * 365));
// 颁发对象
X500Name subject2 = generateSubject("CN", "GuangDong", "GuangZhou", "博客:https://blog.csdn.net/penngo", "penngo", "www.penngo.test");
certGen.setSubjectDN(X509Name.getInstance(subject2));
// 颁发者
X500Name subject = generateSubject("CN", "GuangDong", "GuangZhou", "DO_NOT_TRUST", "DO_NOT_TRUST", "Created by penngo");
certGen.setIssuerDN(X509Name.getInstance(subject));
certGen.setPublicKey(pair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
// certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test@test.test")));
return certGen.generateX509Certificate(pair.getPrivate(), "BC");
}
public static void main(
String[] args)
throws Exception {
// create the keys
KeyPair pair = Utils.generateRSAKeyPair();
PrivateKey aPrivate = pair.getPrivate();
//下面是私钥key生成的过程
byte[] privateKeyEncode = aPrivate.getEncoded();
String privateKeyStr = Base64.getEncoder().encodeToString(privateKeyEncode);
String privateKeyFileContent = "" +
"-----BEGIN RSA PRIVATE KEY-----\n" +
lf(privateKeyStr, 64) +
"-----END RSA PRIVATE KEY-----";
FileUtils.write(new File("logs\\server2.key"), privateKeyFileContent,
StandardCharsets.UTF_8);
// generate the certificate
X509Certificate cert = generateV3Certificate(pair);
// show some basic validation
cert.checkValidity(new Date());
cert.verify(cert.getPublicKey());
byte[] encoded = cert.getEncoded();
String certStr = Base64.getEncoder().encodeToString(encoded);
System.out.println("valid certificate generated" + certStr);
String certFileContent = "" +
"-----BEGIN CERTIFICATE-----\n" +
lf(certStr, 64) +
"-----END CERTIFICATE-----";
FileUtils.write(new File("logs\\server2.pem"), certFileContent,
StandardCharsets.UTF_8);
}
public static String lf(String str, int lineLength) {
assert str != null;
assert lineLength > 0;
StringBuilder sb = new StringBuilder();
char[] chars = str.toCharArray();
int n = 0;
for (char aChar : chars) {
sb.append(aChar);
n++;
if (n == lineLength) {
n = 0;
sb.append("\n");
}
}
if (n != 0)
sb.append("\n");
return sb.toString();
}
}
官方参考自官方例子:https://www.bouncycastle.org/documentation.html
注意当前Java例子生成的证书,在浏览器中会显示不受信任的。