使用证书颁发机构签署证书请求。

huangapple go评论85阅读模式
英文:

Signing certificate request with certificate authority

问题

我想在使用Go语言编写的API中使用TLS双向认证来验证客户端。我已经创建了一个证书颁发机构,假设Bob有一对密钥对,他想在客户端中使用。Bob创建了一个证书请求,希望我验证他的证书,以便在API上获得授权和认证。

我使用以下命令创建了我的证书颁发机构:

openssl genrsa -aes256 -out ca.key 4096
openssl req -new -x509 -sha256 -days 730 -key ca.key -out ca.crt

Bob使用以下命令创建了他的证书和证书请求:

openssl genrsa -out bob.key 4096
openssl req -new -key bob.key -out bob.csr

我想在Go语言中实现以下功能:

openssl x509 -req -days 365 -sha256 -in bob.csr -CA ca.crt -CAkey ca.key -set_serial 3 -out bob.crt

目前,使用这些命令,Bob可以创建与我的API的TLS连接,该API使用以下tls.Config:

func createTLSConfig(certFile string, keyFile string, clientCAFilepath string) (config *tls.Config, err error) {
    cer, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        return nil, err
    }

    clientCAFile, err := ioutil.ReadFile(clientCAFilepath)
    if err != nil {
        return nil, err
    }
    clientCAPool := x509.NewCertPool()
    clientCAPool.AppendCertsFromPEM(clientCAFile)

    config = &tls.Config{
        Certificates: []tls.Certificate{cer},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    clientCAPool,
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
        },
        PreferServerCipherSuites: true,
        SessionTicketsDisabled:   false,
        MinVersion:               tls.VersionTLS12,
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384},
    }

    return config, nil
}

但是,如果Julia现在想要登录怎么办?她将需要创建一个CSR,将其发送给我,然后我将手动验证她的CSR并生成一个CRT。为了避免这个手动操作,想法是有一个注册端点,Julia可以在其中提交她的CSR并获得一个有效的CRT。该端点的基本结构如下:

func Register(c echo.Context) (err error) {
    // 从POST请求体中获取Julia的CSR
    csr := certificateFromBody(c.Body)

    // 使用CA验证CSR并生成CRT
    crt := signCSR(csr, config.ClientCAPath)

    // 将CRT返回给Julia
    return c.JSON(http.StatusCreated, base64.StdEncoding.EncodeToString(crt))
}

我花了一些时间来理解openssl如何使用CA从CSR创建CRT,但没有成功。

Go语言的crypto/x509包中有一个CertificateRequest对象,我可以使用ParseCertificateRequest创建,但我找不到将此对象和我的CA作为输入并返回证书的函数。

谢谢你的帮助!

英文:

I want to use TLS mutual authentication to authenticate a client on a API made in go. I've created a certificate authority, and let's say Bob has a key pair he wants to use with the client. Bob created a certificate request and want me to validate his certificate in order to be authorized and
authenticated on the API.

I've used this to create my Certificate Authority :

openssl genrsa -aes256 -out ca.key 4096
openssl req -new -x509 -sha256 -days 730 -key ca.key -out ca.crt

Bob used this to create his certificate and certificate request :

openssl genrsa -out bob.key 4096
openssl req -new -key bob.key -out bob.csr

I want to achive this, but in go :

openssl x509 -req -days 365 -sha256 -in bob.csr -CA ca.crt -CAkey ca.key -set_serial 3 -out bob.crt

For now, with theses commands, Bob can create a TLS connection to my API which use this tls.Config :

func createTLSConfig(certFile string, keyFile string, clientCAFilepath string) (config *tls.Config, err error) {
	cer, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, err
	}

	clientCAFile, err := ioutil.ReadFile(clientCAFilepath)
	if err != nil {
		return nil, err
	}
	clientCAPool := x509.NewCertPool()
	clientCAPool.AppendCertsFromPEM(clientCAFile)

	config = &tls.Config{
		Certificates: []tls.Certificate{cer},
		ClientAuth: tls.RequireAndVerifyClientCert,
		ClientCAs:  clientCAPool,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		},
		PreferServerCipherSuites: true,
		SessionTicketsDisabled:   false,
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384},
	}

	return config, nil
}

But what if Julia now want to login ? She will have to create a CSR, send it to me and I would have to manually validate her CSR to a CRT too. To avoid this manual operation, the idea is to have a register endpoint where Julia can submit her CSR and get back a valid CRT. The endpoint will basically look like this :

func Register(c echo.Context) (err error) {
	// get Julia's csr from POST body
	csr := certificateFromBody(c.Body)

	// valid csr with ca to generate the crt
	crt := signCSR(csr, config.ClientCAPath)

	// return the crt to julia
	return c.JSON(http.StatusCreated, base64.StdEncoding.EncodeToString(crt))
}

I spend some time to understand how openssl use the CA to create the CRT from the CRS, without success.

Golang has a CertificateRequest object from the crypto/x509 package that I can create with the ParseCertificateRequest but I can't find the function that take this object and my CA and return a certificate.

Thank you for your help!

答案1

得分: 20

现在可以工作了,这是一个基本的解决方案,用于验证使用CA的CRT的CSR:

  • 加载CA证书
  • 加载CA私钥(带密码)
  • 加载Bob的CSR
  • 使用CSR和CA信息创建证书模板
  • 使用模板和CA私钥生成证书
  • 保存Bob的证书

以下是一个可工作的示例:

package main

import (
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"os"
	"time"
)

func crsToCrtExample() {
	// 加载CA密钥对
	// 公钥
	caPublicKeyFile, err := ioutil.ReadFile("certs/ca-root.crt")
	if err != nil {
		panic(err)
	}
	pemBlock, _ := pem.Decode(caPublicKeyFile)
	if pemBlock == nil {
		panic("pem.Decode failed")
	}
	caCRT, err := x509.ParseCertificate(pemBlock.Bytes)
	if err != nil {
		panic(err)
	}

	// 私钥
	caPrivateKeyFile, err := ioutil.ReadFile("certs/ca-mutu.key")
	if err != nil {
		panic(err)
	}
	pemBlock, _ = pem.Decode(caPrivateKeyFile)
	if pemBlock == nil {
		panic("pem.Decode failed")
	}
	der, err := x509.DecryptPEMBlock(pemBlock, []byte("ca private key password"))
	if err != nil {
		panic(err)
	}
	caPrivateKey, err := x509.ParsePKCS1PrivateKey(der)
	if err != nil {
		panic(err)
	}

	// 加载客户端证书请求
	clientCSRFile, err := ioutil.ReadFile("certs/bob.csr")
	if err != nil {
		panic(err)
	}
	pemBlock, _ = pem.Decode(clientCSRFile)
	if pemBlock == nil {
		panic("pem.Decode failed")
	}
	clientCSR, err := x509.ParseCertificateRequest(pemBlock.Bytes)
	if err != nil {
		panic(err)
	}
	if err = clientCSR.CheckSignature(); err != nil {
		panic(err)
	}

	// 创建客户端证书模板
	clientCRTTemplate := x509.Certificate{
		Signature:          clientCSR.Signature,
		SignatureAlgorithm: clientCSR.SignatureAlgorithm,

		PublicKeyAlgorithm: clientCSR.PublicKeyAlgorithm,
		PublicKey:          clientCSR.PublicKey,

		SerialNumber: big.NewInt(2),
		Issuer:       caCRT.Subject,
		Subject:      clientCSR.Subject,
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}

	// 从模板和CA公钥创建客户端证书
	clientCRTRaw, err := x509.CreateCertificate(rand.Reader, &clientCRTTemplate, caCRT, clientCSR.PublicKey, caPrivateKey)
	if err != nil {
		panic(err)
	}

	// 保存证书
	clientCRTFile, err := os.Create("certs/bob.crt")
	if err != nil {
		panic(err)
	}
	pem.Encode(clientCRTFile, &pem.Block{Type: "CERTIFICATE", Bytes: clientCRTRaw})
	clientCRTFile.Close()
}

谢谢Mark!

英文:

It work now, here is a basic solution to validate a CSR from a CRT with a CA:

  • load ca certificate
  • load ca private key (with password)
  • load bob CSR
  • create a certificate template with the CSR and CA informations
  • generate the certificate from the template and with the CA private key
  • save the bob's certificate

A working example:

package main
import (
"crypto/rand"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"math/big"
"os"
"time"
)
func crsToCrtExample() {
// load CA key pair
//		public key
caPublicKeyFile, err := ioutil.ReadFile("certs/ca-root.crt")
if err != nil {
panic(err)
}
pemBlock, _ := pem.Decode(caPublicKeyFile)
if pemBlock == nil {
panic("pem.Decode failed")
}
caCRT, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
panic(err)
}
//		private key
caPrivateKeyFile, err := ioutil.ReadFile("certs/ca-mutu.key")
if err != nil {
panic(err)
}
pemBlock, _ = pem.Decode(caPrivateKeyFile)
if pemBlock == nil {
panic("pem.Decode failed")
}
der, err := x509.DecryptPEMBlock(pemBlock, []byte("ca private key password"))
if err != nil {
panic(err)
}
caPrivateKey, err := x509.ParsePKCS1PrivateKey(der)
if err != nil {
panic(err)
}
// load client certificate request
clientCSRFile, err := ioutil.ReadFile("certs/bob.csr")
if err != nil {
panic(err)
}
pemBlock, _ = pem.Decode(clientCSRFile)
if pemBlock == nil {
panic("pem.Decode failed")
}
clientCSR, err := x509.ParseCertificateRequest(pemBlock.Bytes)
if err != nil {
panic(err)
}
if err = clientCSR.CheckSignature(); err != nil {
panic(err)
}
// create client certificate template
clientCRTTemplate := x509.Certificate{
Signature:          clientCSR.Signature,
SignatureAlgorithm: clientCSR.SignatureAlgorithm,
PublicKeyAlgorithm: clientCSR.PublicKeyAlgorithm,
PublicKey:          clientCSR.PublicKey,
SerialNumber: big.NewInt(2),
Issuer:       caCRT.Subject,
Subject:      clientCSR.Subject,
NotBefore:    time.Now(),
NotAfter:     time.Now().Add(24 * time.Hour),
KeyUsage:     x509.KeyUsageDigitalSignature,
ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
// create client certificate from template and CA public key
clientCRTRaw, err := x509.CreateCertificate(rand.Reader, &clientCRTTemplate, caCRT, clientCSR.PublicKey, caPrivateKey)
if err != nil {
panic(err)
}
// save the certificate
clientCRTFile, err := os.Create("certs/bob.crt")
if err != nil {
panic(err)
}
pem.Encode(clientCRTFile, &pem.Block{Type: "CERTIFICATE", Bytes: clientCRTRaw})
clientCRTFile.Close()
}

Thanks Mark!

答案2

得分: 4

你可以使用x509.CreateCertificate

CreateCertificate的一个参数是一个“模板”证书。

你可以使用Julia的CertificateRequest中的字段来设置模板证书的字段。

Go的generate cert脚本展示了使用CreateCertificate的示例用法。

这假设来自Julia的API请求确实来自Julia,并且足够可信以签署请求并返回证书。

此外,Using your own PKI for TLS in Go可能会有所帮助。

英文:

You may be able to use x509.CreateCertificate.

One of the parameters to CreateCertificate is a 'template' certificate.

You can set the fields of the template certificate using the fields from Julia's CertificateRequest.

Go's generate cert script shows an example usage CreateCertificate.

This assumes the API request from Julia is really from Julia, and sufficiently trusted to sign the request and return a certificate.

Also, Using your own PKI for TLS in Go may be of help.

答案3

得分: 3

这是我为一篇关于PKI的博客文章编写的演示程序的代码片段。完整的文章链接:https://anchorloop.com/2017/09/25/security-iq-ii-public-key-infrastructure/

// 现在读取相应字节数并解析证书请求
asn1Data := make([]byte, asn1DataSize)
_, err = reader.Read(asn1Data)
if err != nil {
return err
}
fmt.Println("接收到证书签名请求。")
certReq, err := x509.ParseCertificateRequest(asn1Data)
if err != nil {
return err
}

// 创建证书创建模板,使用请求和根证书的属性
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return err
}
template := x509.Certificate {
Signature: certReq.Signature,
SignatureAlgorithm: certReq.SignatureAlgorithm,

PublicKeyAlgorithm: certReq.PublicKeyAlgorithm,
PublicKey: certReq.PublicKey,
SerialNumber: serialNumber,
Issuer: rootCert.Subject,
Subject: certReq.Subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},

}

// 使用模板和根证书创建由RootCA的私钥签名的证书
certData, err := x509.CreateCertificate(rand.Reader, &template, rootCert, template.PublicKey, privateKey)
if err != nil {
return err
}
fmt.Println("从CSR创建了由RootCA的私钥签名的证书。")

基本上:

  • 客户端创建并发送CSR。
  • 签名证书的所有者解析CSR并从CSR和签名证书的属性中构建一个新的x509.Certificate。
  • 签名者的私钥传递给x509.CreateCertificate进行签名。
  • 然后,您可以将签名后的证书发送回客户端。

希望对您有所帮助。

英文:

Here's a snippet of code from a demo program I wrote for a blog post about PKI. Full post: https://anchorloop.com/2017/09/25/security-iq-ii-public-key-infrastructure/

// Now read that number of bytes and parse the certificate request
asn1Data := make([]byte, asn1DataSize)
_, err = reader.Read(asn1Data)
if err != nil {
return err
}
fmt.Println("Received Certificate Signing Request.")
certReq, err := x509.ParseCertificateRequest(asn1Data)
if err != nil {
return err
}
// Create template for certificate creation, uses properties from the request and root certificate.
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return err
}
template := x509.Certificate {
Signature: certReq.Signature,
SignatureAlgorithm: certReq.SignatureAlgorithm,
PublicKeyAlgorithm: certReq.PublicKeyAlgorithm,
PublicKey: certReq.PublicKey,
SerialNumber: serialNumber,
Issuer: rootCert.Subject,
Subject: certReq.Subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
// Create certificate from template and root certificate, signed by the RootCA's private key.
certData, err := x509.CreateCertificate(rand.Reader, &template, rootCert, template.PublicKey, privateKey)
if err != nil {
return err
}
fmt.Println("Created Certificate from CSR, signed by RootCA's Private Key.")

Basically:

  • The CSR gets created and sent by the client.
  • The owner of the signing certificate parses it and builds a new x509.Certificate from a mixture of properties from the CSR and signing certificate.
  • The private key of the signer is passed to x509.CreateCertificate to sign it.
  • After that, you can send it back to the client.

I hope that helps.

huangapple
  • 本文由 发表于 2017年3月7日 15:58:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/42643048.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定