参考
流程
首先梳理一下流程 —— 创建秘钥 > 创建CA > 生成要颁发证书的秘钥 > 使用CA签发证书
- 正常情况下,电脑会内置 CA 机构证书(用于验证其他证书),但这个 CA 机构证书是需要花钱注册的,所以我们自行创建个 CA 机构,用于签发和验证证书
- 同时我们需要考虑将 CA 的机构证书,分发给需要验证证书的客户端(相当于内置到电脑中,只不过这一步是需要我们自己做,付费的 CA 机构证书是让厂商或浏览器直接嵌入了)
- 创建 CA 机构。
- 填写证书签名请求 CSR,
- 创建公私钥,
- 之后利用自己 csr 和私钥 对自己的 csr 和 公钥进行签名,也就是自签名,生成 CA 机构证书(证书内包含就是一些个人信息、网站信息、有效期和最重要的公钥(用于验证其他证书))
- 签名其他证书。
- 填写证书签名请求 CSR,
- 创建公私钥,
- 之后利用 CA 机构的 csr 和私钥 对自己的 csr 和 公钥进行签名,实现证书的签发
创建证书流程
本篇文章中,将描述如何使用go创建CA,并使用CA签署证书。在使用openssl创建证书时,遵循的步骤是 创建秘钥 > 创建CA > 生成要颁发证书的秘钥 > 使用CA签发证书。这种步骤,那么我们现在就来尝试下。
创建证书的颁发机构
首先,会从将从创建 CA 开始。CA 会被用来签署其他证书
// 对证书进行签名
ca := &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
CommonName: "domain name",
Organization: []string{"Company, INC."},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
},
NotBefore: time.Now(), // 生效时间
NotAfter: time.Now().AddDate(10, 0, 0), // 过期时间 年月日
IsCA: true, // 表示用于CA
// openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
// openssl 中的 keyUsage 字段
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
接下来需要对证书生成公钥和私钥
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return err
}
然后生成证书:
// 第一个参数 rand.Reader 就是生成个随机数
// 第二个参数 ca 是 待签证书(此处后续改为其他证书,实现对其他证书的签名,此处是自签,用于构造 ca 机构证书)
// 第三个参数 ca 是 ca机构证书
// 第四个参数 &caPrivKey.PublicKey 对应着 第二个参数申请证书对应的公钥(因为证书就是为了安全传输公钥)
// 第五个参数 caPrivKey 是 ca 机构的私钥,用于签名证书,这样第二个证书被签名后,传给客户,客户就可以通过内置ca机构证书中的公钥,解开并验证第二个证书
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
return err
}
caBytes
caPEM := new(bytes.Buffer)
pem.Encode(caPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})caPrivKeyPEM := new(bytes.Buffer)
pem.Encode(caPrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
})
创建证书
x509.Certificatex509.Certificate
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
CommonName: "domain name",
Organization: []string{"Company, INC."},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
},
IPAddresses: []net.IP{}, // 这里就是openssl配置文件中 subjectAltName 里的 IP:/IP=
DNSNames: []string{}, // 这里就是openssl配置文件中 subjectAltName 里的 DNS:/DNS=
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
// 这里就是openssl中的extendedKeyUsage
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
DNSIP
为该证书创建私钥和公钥:
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return err
}
使用CA签署证书
有了上述的内容后,可以创建证书并用CA进行签名
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
if err != nil {
return err
}
要保存成证书格式需要做PEM编码
certPEM := new(bytes.Buffer)
pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})certPrivKeyPEM := new(bytes.Buffer)
pem.Encode(certPrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
})
把上面内容融合为一起
ca.go
package mainimport (
"bytes"
cr "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"math/rand"
"net"
"os"
"time"
)type CERT struct {
CERT []byte
CERTKEY *rsa.PrivateKey
CERTPEM *bytes.Buffer
CERTKEYPEM *bytes.Buffer
CSR *x509.Certificate
}func CreateCA(sub *pkix.Name, expire int) (*CERT, error) {
var (
ca = new(CERT)
err error
) if expire < 1 {
expire = 1
}
// 为ca生成私钥
ca.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096)
if err != nil {
return nil, err
} // 对证书进行签名
ca.CSR = &x509.Certificate{
SerialNumber: big.NewInt(rand.Int63n(2000)),
Subject: *sub,
NotBefore: time.Now(), // 生效时间
NotAfter: time.Now().AddDate(expire, 0, 0), // 过期时间
IsCA: true, // 表示用于CA
// openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
// openssl 中的 keyUsage 字段
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
// 创建证书
// caBytes 就是生成的证书
ca.CERT, err = x509.CreateCertificate(cr.Reader, ca.CSR, ca.CSR, &ca.CERTKEY.PublicKey, ca.CERTKEY)
if err != nil {
return nil, err
}
ca.CERTPEM = new(bytes.Buffer)
pem.Encode(ca.CERTPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: ca.CERT,
})
ca.CERTKEYPEM = new(bytes.Buffer)
pem.Encode(ca.CERTKEYPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(ca.CERTKEY),
}) // 进行PEM编码,编码就是直接cat证书里面内容显示的东西
return ca, nil
}func Req(ca *x509.Certificate, sub *pkix.Name, expire int, dns []string, ip []net.IP) (*CERT, error) {
var (
cert = &CERT{}
err error
)
cert.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096)
if err != nil {
return nil, err
}
if expire < 1 {
expire = 1
}
cert.CSR = &x509.Certificate{
SerialNumber: big.NewInt(rand.Int63n(2000)),
Subject: *sub,
IPAddresses: ip,
DNSNames: dns,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expire, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
} cert.CERT, err = x509.CreateCertificate(cr.Reader, cert.CSR, ca, &cert.CERTKEY.PublicKey, cert.CERTKEY)
if err != nil {
return nil, err
} cert.CERTPEM = new(bytes.Buffer)
pem.Encode(cert.CERTPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.CERT,
})
cert.CERTKEYPEM = new(bytes.Buffer)
pem.Encode(cert.CERTKEYPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(cert.CERTKEY),
})
return cert, nil
}func Write(cert *CERT, file string) error {
keyFileName := file + ".key"
certFIleName := file + ".crt"
kf, err := os.Create(keyFileName)
if err != nil {
return err
}
defer kf.Close() if _, err := kf.Write(cert.CERTKEYPEM.Bytes()); err != nil {
return err
} cf, err := os.Create(certFIleName)
if err != nil {
return err
}
if _, err := cf.Write(cert.CERTPEM.Bytes()); err != nil {
return err
}
return nil
}
如果需要使用的话,可以引用这些函数
package mainimport (
"crypto/x509/pkix"
"log"
"net"
)func main() {
subj := &pkix.Name{
CommonName: "chinamobile.com",
Organization: []string{"Company, INC."},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
}
ca, err := CreateCA(subj, 10)
if err != nil {
log.Panic(err)
} Write(ca, "./ca") crt, err := Req(ca.CSR, subj, 10, []string{"test.default.svc", "test"}, []net.IP{}) if err != nil {
log.Panic(err)
} Write(crt, "./tls")
}
遇到的问题
panic: x509: unsupported public key type: rsa.PublicKey
x509.CreateCertificateprivatekey
注:x509: only RSA and ECDSA public keys supported
一些参数的意思
extendedKeyUsage
extendedKeyUsage = critical,serverAuth, clientAuth, timeStamping
keyUsage
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
其他参考资料
生成私钥证书
ecparamcrypto/ecdsa
// 生成 ECC 私钥
func GeneratePrivateKey() (key *ecdsa.PrivateKey) {
key, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
return
}
elliptic.P256()prime256v1
构建证书签名请求 CSR
crypto/x509x509.Certificate
x509.Certificate
x509.Certificate
Version1,2,3SerialNumberSignatureAlgorithmPublicKeyAlgorithmSubjectCountryProvinceLocalityOrganizationOrganizationalUnitCommonNameNotBeforeNotAfterEmailAddressesDNSNamesIPAddressesURIsBasicConstraintsValidtrueIsCAMaxPathLenMaxPathLenZerofalseIsCAfalsetrueMaxPathLen-1MaxPathLenZero == false && MaxPathLen == 0-1MaxPathLenZeroMaxPathLen == 0KeyUsageKeyUsageDigitalSignatureKeyUsageContentCommitmentKeyUsageKeyEnciphermentKeyUsageDataEnciphermentKeyUsageKeyAgreementKeyUsageCertSignKeyUsageCRLSignKeyUsageEncipherOnlyKeyUsageDecipherOnlyExtKeyUsageExtKeyUsageAnyExtKeyUsageServerAuthExtKeyUsageClientAuthExtKeyUsageCodeSigningExtKeyUsageEmailProtectionExtKeyUsageIPSECEndSystemExtKeyUsageIPSECTunnelExtKeyUsageIPSECUserExtKeyUsageTimeStampingExtKeyUsageOCSPSigningExtKeyUsageMicrosoftServerGatedCryptoExtKeyUsageNetscapeServerGatedCryptoExtKeyUsageMicrosoftCommercialCodeSigningExtKeyUsageMicrosoftKernelCodeSigning
我们使用这些字段就够了,其他字段缺省即可。可以使用这些参数分别签证生成根证书、中级证书和终端域名证书的签证 CSR。
根证书
var rootCsr = &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: []string{"CN"},
Province: []string{"Shanghai"},
Locality: []string{"Shanghai"},
Organization: []string{"JediLtd"},
OrganizationalUnit: []string{"JediProxy"},
CommonName: "Jedi Root CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1,
MaxPathLenZero: false,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}
CommonNameJedi Root CAMaxPathLen
KeyUsageKeyUsageCertSignKeyUsageCRLSignExtKeyUsage
中级证书
MaxPathLen
var interCsr = &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: []string{"CN"},
Province: []string{"Shanghai"},
Locality: []string{"Shanghai"},
Organization: []string{"JediLtd"},
OrganizationalUnit: []string{"JediProxy"},
CommonName: "Jedi Inter CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
MaxPathLenZero: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}
终端证书
终端域名证书的配置需要做一些变更,配置如下:
var csr = &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: []string{"CN"},
Province: []string{"Shanghai"},
Locality: []string{"Shanghai"},
Organization: []string{"JediLtd"},
OrganizationalUnit: []string{"JediProxy"},
CommonName: "foreverz.cn",
},
DNSNames: []string{"foreverz.cn"},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
BasicConstraintsValid: true,
IsCA: false,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
DNSNames**.foreverz.cn
IsCAfalseMaxPathLenMaxPathLenZero
KeyUsageKeyUsageDigitalSignatureKeyUsageKeyEnciphermentExtKeyUsageExtKeyUsageServerAuth
这样我们就配置好了三级证书的签名请求了,这里都是分开写的,大部分代码都是一样的,可以写成一个方法,这里就不扩展了。
证书签名
crypto/x509CreateCertificate
randrand.Readertemplateparentpubkey.Public()priv
具体签证方法如下:
rootDer, err := x509.CreateCertificate(rand.Reader, rootCsr, rootCsr, rootKey.Public(), rootKey)
rootCert, err := x509.ParseCertificate(rootDer)
CreateCertificate[]bytex509.ParseCertificate*x509.Certificate
生成了根证书就可以使用根证书签证中级证书了:
interDer, err := x509.CreateCertificate(rand.Reader, interCsr, rootCert, interKey.Public(), rootKey)
interCert, err := x509.ParseCertificate(interDer)
终端证书的生成与中级证书基本一致:
der, err := x509.CreateCertificate(rand.Reader, csr, interCert, key.Public(), interKey)
cert, err := x509.ParseCertificate(der)
保存 PEM 文件
*x509.Certificate.pem
签证证书:
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
pemData := pem.EncodeToMemory(certBlock)
if err = ioutil.WriteFile("xx.cert.pem", pemData, 0644); err != nil {
panic(err)
}
私钥证书:
keyDer, err := x509.MarshalECPrivateKey(key)
keyBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: keyDer,
}
keyData := := pem.EncodeToMemory(certBlock)
if err = ioutil.WriteFile("xx.key.pem", keyData, 0644); err != nil {
panic(err)
}
从 PEM 文件读取证书
crypto/tlsLoadX509KeyPair
func LoadPair(certFile, keyFile string) (cert *x509.Certificate, err error) {
if len(certFile) == 0 && len(keyFile) == 0 {
return nil, errors.New("cert or key has not provided")
}
// load cert and key by tls.LoadX509KeyPair
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return
}
cert, err = x509.ParseCertificate(tlsCert.Certificate[0])
return
}
如上,我们完成了证书的生成、存储和读取等能力,证书相关的处理就结束了。