本篇文章中,将描述如何使用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 }
然后生成证书:
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) if err != nil { return err }
我们看到的证书内容是PEM编码后的,现在caBytes
我们有了生成的证书,我们将其进行 PEM 编码以供以后使用:
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.Certificate
与CA的 x509.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, }
注:这里会在证书中特别添加了
DNS
和IP
(这个不是必须的),这个选项的增加代表的我们的证书可以支持多域名
为该证书创建私钥和公钥:
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
里面是创建ca和颁发证书的逻辑
package main import ( "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 main import ( "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.CreateCertificate
的参数 privatekey
需要传入引用变量,而传入的是一个普通变量
注:x509: only RSA and ECDSA public keys supported
一些参数的意思
extendedKeyUsage
:增强型密钥用法(参见"new_oids"字段):服务器身份验证、客户端身份验证、时间戳。
extendedKeyUsage = critical,serverAuth, clientAuth, timeStamping
keyUsage
: 密钥用法,防否认(nonRepudiation)、数字签名(digitalSignature)、密钥加密(keyEncipherment)。
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
Reference