
Create a PKI in GoLang
June 5, 2017
Lately I have been programming quite a bit and - for the first time - I have used Golang doing so. Go is a very nice language and really helped me with the development. One of the reasons why I have enjoyed this much Go is the standard library, which is amazing. I would like to share today the easiness of creating a basic Certificate Authority and signed certificates in Go.
Create a Certificate Authority in Go
Create a CA in Go, is very straightforward.
First of all we need to import some libraries, which will heavily simplify our work:
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"os"
"time"
)
The second step is to create an x509 certificate object within the main function. To do so, we need to declare few data such as the Subject of the certificate, the first moment when the CA is valid (right now), when the CA will expire (in 10 years), the fact that is a CA, and the authorized usage of the key.
func main() {
ca := &x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Organization: []string{"ORGANIZATION_NAME"},
Country: []string{"COUNTRY_CODE"},
Province: []string{"PROVINCE"},
Locality: []string{"CITY"},
StreetAddress: []string{"ADDRESS"},
PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
After we created the x509 certificate object, we can now proceed creating a private key (I decided to create an RSA 2048 key due to some constrains in the environment, but thi could be very different from your case, so ensure to create a key that matches your required security. After the key is created, we can use it to create the certificate we declared in the previous step. Also, we are going to self-sign the certificate.
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
pub := &priv.PublicKey
ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
if err != nil {
log.Println("create ca failed", err)
return
}
Now that we have the certificate ready, we can save the public key in a file.
// Public key
certOut, err := os.Create("ca.crt")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: ca_b})
certOut.Close()
log.Print("written cert.pem\n")
We can also save the private key in a file.
// Private key
keyOut, err := os.OpenFile("ca.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
log.Print("written key.pem\n")
}
So the full code would look something like:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"os"
"time"
)
func main() {
ca := &x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Organization: []string{"ORGANIZATION_NAME"},
Country: []string{"COUNTRY_CODE"},
Province: []string{"PROVINCE"},
Locality: []string{"CITY"},
StreetAddress: []string{"ADDRESS"},
PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
pub := &priv.PublicKey
ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
if err != nil {
log.Println("create ca failed", err)
return
}
// Public key
certOut, err := os.Create("ca.crt")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: ca_b})
certOut.Close()
log.Print("written cert.pem\n")
// Private key
keyOut, err := os.OpenFile("ca.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
log.Print("written key.pem\n")
}
Create and sign additional certificates
Now that we have a Certificate Authority, we can move to create a certificate. In this example, we are going to create the certificate and sign it in the same code. This is due to the peculiarity of what I was doing, which could be different to what you are doing. If you need to sign a certificate coming from another place, is even easier, since you can skip the whole part needed to sign the certificate.
As before, we are going to import some required libraries:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"os"
"time"
)
Now, we care going to load the CA from file. This is needed so that we can sign the certificate.
func main() {
// Load CA
catls, err := tls.LoadX509KeyPair("ca.crt", "ca.key")
if err != nil {
panic(err)
}
ca, err := x509.ParseCertificate(catls.Certificate[0])
if err != nil {
panic(err)
}
Now we are going to create the new certificate. As you will notice, this is very similar to when we created the CA.
// Prepare certificate
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Organization: []string{"ORGANIZATION_NAME"},
Country: []string{"COUNTRY_CODE"},
Province: []string{"PROVINCE"},
Locality: []string{"CITY"},
StreetAddress: []string{"ADDRESS"},
PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
pub := &priv.PublicKey
Now that we have both the certificate and the CA in memory, we can sign the certificate:
// Sign the certificate
cert_b, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, catls.PrivateKey)
As we did with the CA, we now need to save the public and private key:
// Public key
certOut, err := os.Create("bob.crt")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert_b})
certOut.Close()
log.Print("written cert.pem\n")
// Private key
keyOut, err := os.OpenFile("bob.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
log.Print("written key.pem\n")
}
So the full code is:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"os"
"time"
)
func main() {
// Load CA
catls, err := tls.LoadX509KeyPair("ca.crt", "ca.key")
if err != nil {
panic(err)
}
ca, err := x509.ParseCertificate(catls.Certificate[0])
if err != nil {
panic(err)
}
// Prepare certificate
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Organization: []string{"ORGANIZATION_NAME"},
Country: []string{"COUNTRY_CODE"},
Province: []string{"PROVINCE"},
Locality: []string{"CITY"},
StreetAddress: []string{"ADDRESS"},
PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
pub := &priv.PublicKey
// Sign the certificate
cert_b, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, catls.PrivateKey)
// Public key
certOut, err := os.Create("bob.crt")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert_b})
certOut.Close()
log.Print("written cert.pem\n")
// Private key
keyOut, err := os.OpenFile("bob.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
log.Print("written key.pem\n")
}
As you can notice, Go makes this very simple and the Go code is very easy to read, so that it makes the steps fairly clear.