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!
It work now, here is a basic solution to validate a CSR from a CRT with a CA:
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!