问题
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:
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.
回答2:
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!
回答3:
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.
来源:https://stackoverflow.com/questions/42643048/signing-certificate-request-with-certificate-authority