Part 2 of a small series into building a Public Key Infrastructure chain with Golang
Ok, yeah yeah yeah, Part 1 was a lot of primer (it’s really not, trust me) - where’s the action?!
Alrighty there ya Giddy Gopher, let’s start doing some Golang Programming! Let’s start down the path of creating custom Certificate Authorities and associated PKI!
Series Table of Contents
Directory Structure
It’s good to standardize on a common directory structure for your Certificate Authorities, be them Root, Intermediate, Intermediate-Intermediate, so on and so forth. This would normally be defined in something like an OpenSSL configuration file like so:
#
# OpenSSL configuration for the Root Certification Authority.
#
#
# This definition doesn't work if HOME isn't defined.
CA_HOME = .
#
# Default Certification Authority
[ ca ]
default_ca = root_ca
#
# Root Certification Authority
[ root_ca ]
dir = $ENV::CA_HOME
certs = $dir/certs
serial = $dir/ca.serial
database = $dir/ca.index
new_certs_dir = $dir/newcerts
certificate = $dir/ca.cert
private_key = $dir/private/ca.key.pem
default_days = 1826 # 5 years
crl = $dir/ca.crl
crl_dir = $dir/crl
crlnumber = $dir/ca.crlnum
We can represent this in Golang with the following typed structure - this type defines what all the different folders and files are associated with any Certificate Authority:
// CertificateAuthorityPaths returns all the default paths generated by a new CA
type CertificateAuthorityPaths struct {
RootCAPath string
RootCACertRequestsPath string
RootCACertsPath string
RootCACertRevListPath string
RootCANewCertsPath string
RootCACertKeysPath string
RootCAIntermediateCAPath string
RootCACertIndexFilePath string
RootCACertSerialFilePath string
RootCACrlnumFilePath string
}
Now let’s create a function that will create the needed directories and return them to be used in other parts of our application.
// setupCAFileStructure creates the basic directories and files required by a new CA
func setupCAFileStructure(basePath string) CertificateAuthorityPaths {
//Create root CA directory
rootCAPath := basePath
CreateDirectory(rootCAPath)
// Create certificate requests (CSR) path
rootCACertRequestsPath := rootCAPath + "/certreqs"
CreateDirectory(rootCACertRequestsPath)
// Create certs path
rootCACertsPath := rootCAPath + "/certs"
CreateDirectory(rootCACertsPath)
// Create crls path
rootCACertRevListPath := rootCAPath + "/crl"
CreateDirectory(rootCACertRevListPath)
// Create newcerts path (wtf is newcerts for vs certs?!)
rootCANewCertsPath := rootCAPath + "/newcerts"
CreateDirectory(rootCANewCertsPath)
// Create private path for CA keys
rootCACertKeysPath := rootCAPath + "/private"
CreateDirectory(rootCACertKeysPath)
// Create intermediate CA path
rootCAIntermediateCAPath := rootCAPath + "/intermed-ca"
CreateDirectory(rootCAIntermediateCAPath)
// CREATE INDEX DATABASE FILE
rootCACertIndexFilePath := rootCAPath + "/ca.index"
IndexFile, err := WriteFile(rootCACertIndexFilePath, "", 0600, false)
check(err)
if IndexFile {
logStdOut("Created Index file")
} else {
logStdOut("Index file exists")
}
// CREATE SERIAL FILE
rootCACertSerialFilePath := rootCAPath + "/ca.serial"
serialFile, err := WriteFile(rootCACertSerialFilePath, "01", 0600, false)
check(err)
if serialFile {
logStdOut("Created serial file")
} else {
logStdOut("Serial file exists")
}
// CREATE CERTIFICATE REVOCATION NUMBER FILE
rootCACrlnumFilePath := rootCAPath + "/ca.crlnum"
crlNumFile, err := WriteFile(rootCACrlnumFilePath, "00", 0600, false)
check(err)
if crlNumFile {
logStdOut("Created crlnum file")
} else {
logStdOut("crlnum file exists")
}
return CertificateAuthorityPaths{
RootCAPath: rootCAPath,
RootCACertRequestsPath: rootCACertRequestsPath,
RootCACertsPath: rootCACertsPath,
RootCACertRevListPath: rootCACertRevListPath,
RootCANewCertsPath: rootCANewCertsPath,
RootCACertKeysPath: rootCACertKeysPath,
RootCAIntermediateCAPath: rootCAIntermediateCAPath,
RootCACertIndexFilePath: rootCACertIndexFilePath,
RootCACertSerialFilePath: rootCACertSerialFilePath,
RootCACrlnumFilePath: rootCACrlnumFilePath,
}
}
The supporting functions required by that one are the following:
import (
"github.com/gosimple/slug"
)
// slugger slugs a string
func slugger(textToSlug string) string {
return slug.Make(textToSlug)
}
// check does error checking
func check(e error) {
if e != nil {
log.Printf("error: %v", e)
}
}
// logStdOut just logs something to stdout
func logStdOut(s string) {
log.Printf("%s\n", string(s))
}
// CreateDirectory is self explanitory
func CreateDirectory(path string) {
log.Printf("Creating directory %s\n", path)
_, err := os.Stat(path)
if os.IsNotExist(err) {
errDir := os.MkdirAll(path, 0755)
check(errDir)
}
}
// WriteFile creates a file only if it's new and populates it
func WriteFile(path string, content string, mode int, overwrite bool) (bool, error) {
fileMode := os.FileMode(0600)
if mode == 0 {
fileMode = os.FileMode(0600)
} else {
fileMode = os.FileMode(mode)
}
fileCheck, err := FileExists(path)
check(err)
// If not, create one with a starting digit
if !fileCheck {
d1 := []byte(content)
err = ioutil.WriteFile(path, d1, fileMode)
check(err)
return true, err
}
// If the file exists and we want to overwrite it
if fileCheck && overwrite {
d1 := []byte(content)
err = ioutil.WriteFile(path, d1, fileMode)
check(err)
return true, err
}
return false, nil
}
Running the Directory Creation Function
Now piecing that all together is pretty easy.
- To create the directory structure of a Certificate Authority you need a base path - this could be a
rootCAs/
directory, orrootCAs/my-cert-auth/intermed-ca/
as an Intermediate CA under My Cert Auth. - Try to reference this base path as an absolute path.
- ?????
- PROFIT!!!1
Create a function to create a new CA and get this whole ball rolling by making the directory structure we need:
// CreateNewCA creates a new root Certificate Authority
func CreateNewCA(certificateID string) (bool, []string, error) {
// Get the absolute path to what is the intended directory for the CA
basePath, err := filepath.Abs("./pki-root/" + certificateID)
check(err)
// Create the needed file structure for the CA
caPaths := setupCAFileStructure(basePath)
if caPaths.RootCAPath != basePath {
return false, []string{"Error creating CA file structure!"}, err
}
// More stuff to be added here later...
return true, []string{"CA Created!"}, nil
}
Finally, call the CreateNewCA
function with a certificateID - this would be a DNS/Filesystem compliant name, so a slug of the Common Name works well:
caCommonName := "Example Labs Root Certificate Authority"
commonNameSlug := slugger(caCommonName)
caCreated, messages, err := CreateNewCA(commonNameSlug)
if !caCreated {
for _, msg := range messages {
logStdOut(msg)
}
check(err)
}
We’ll be structuring our Certificate information like the Common Name a bit better down the road, but this will do for now as a proof-of-concept to get the file structure created for new CAs.
With this same setupCAFileStructure
function we can create the file structure for Intermediate CAs as well just by switching the targetted base path.
Next Steps
Now that we have the directory structure functions set up, we need to start by creating Key Pairs - more on that in the next part of this series.