Skip to content

Commit

Permalink
PoC: pkcs11-tool based HSM support
Browse files Browse the repository at this point in the history
An idea is to continue building Fioctl statically, and provide HSM support by wrapping pkcs11-tool.
The tool is available for Linux, Windows, and Darwin; a user can also build from source.

Signed-off-by: Volodymyr Khoroz <[email protected]>
  • Loading branch information
vkhoroz committed Sep 27, 2023
1 parent 5405c0e commit 8e3b8d9
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 61 deletions.
2 changes: 1 addition & 1 deletion subcommands/el2g/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func doDeviceGateway(cmd *cobra.Command, args []string) {
subcommands.DieNotNil(err)

fmt.Println("Signing CSR")
generatedCa := x509.SignEl2GoCsr(csr.Value)
generatedCa := x509.SignEl2GoCsr(&x509.KeyStorageFiles{}, csr.Value)

fmt.Println("Uploading signed certificate")
errPrefix := "Unable to upload certificate:\n" + generatedCa
Expand Down
15 changes: 8 additions & 7 deletions subcommands/keys/ca_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ func doCreateCA(cmd *cobra.Command, args []string) {
logrus.Debugf("Create CA for %s under %s", factory, certsDir)
subcommands.DieNotNil(os.Chdir(certsDir))

var storage x509.KeyStorage
if len(hsmModule) > 0 {
if len(hsmPin) == 0 {
fmt.Println("ERROR: --hsm-pin is required with --hsm-module")
os.Exit(1)
}
os.Setenv("HSM_MODULE", hsmModule)
os.Setenv("HSM_PIN", hsmPin)
os.Setenv("HSM_TOKEN_LABEL", hsmTokenLabel)
storage = &x509.KeyStorageHsm{Hsm: x509.HsmInfo{hsmModule, hsmPin, hsmTokenLabel}}
} else {
storage = &x509.KeyStorageFiles{}
}

resp, err := api.FactoryCreateCA(factory)
Expand All @@ -103,15 +104,15 @@ func doCreateCA(cmd *cobra.Command, args []string) {
writeFile(x509.SignTlsScript, *resp.SignTlsScript, 0700)

fmt.Println("Creating offline root CA for Factory")
resp.RootCrt = x509.CreateFactoryCa(factory)
resp.RootCrt = x509.CreateFactoryCa(storage, factory)

fmt.Println("Signing Foundries TLS CSR")
resp.TlsCrt = x509.SignTlsCsr(resp.TlsCsr)
resp.TlsCrt = x509.SignTlsCsr(storage, resp.TlsCsr)
writeFile(x509.TlsCertFile, resp.TlsCrt, 0400)

if createOnlineCA {
fmt.Println("Signing Foundries CSR for online use")
resp.CaCrt = x509.SignCaCsr(resp.CaCsr)
resp.CaCrt = x509.SignCaCsr(storage, resp.CaCsr)
writeFile(x509.OnlineCaCertFile, resp.CaCrt, 0400)
}

Expand All @@ -121,7 +122,7 @@ func doCreateCA(cmd *cobra.Command, args []string) {
resp.CaCrt += "\n"
}
commonName := getDeviceCaCommonName(factory)
resp.CaCrt += x509.CreateDeviceCa(commonName, factory)
resp.CaCrt += x509.CreateDeviceCa(storage, commonName, factory)
}

fmt.Println("Uploading signed certs to Foundries")
Expand Down
37 changes: 25 additions & 12 deletions x509/bash.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !windows
//go:build !windows && bashPKI

package x509

Expand All @@ -9,38 +9,51 @@ import (
"github.com/foundriesio/fioctl/subcommands"
)

func run(name string, arg ...string) string {
func run(storage KeyStorage, name string, arg ...string) string {
storage.setEnvVariables()
cmd := exec.Command(name, arg...)
cmd.Stderr = os.Stderr
out, err := cmd.Output()
subcommands.DieNotNil(err)
return string(out)
}

func CreateFactoryCa(ou string) string {
run("./" + CreateCaScript)
func CreateFactoryCa(s KeyStorage, ou string) string {
run(s, "./" + CreateCaScript)
return string(readFile(FactoryCaCertFile))
}

func CreateDeviceCa(cn string, ou string) string {
run("./"+CreateDeviceCaScript, DeviceCaKeyFile, DeviceCaCertFile)
func CreateDeviceCa(s KeyStorage, cn string, ou string) string {
run(s, "./"+CreateDeviceCaScript, DeviceCaKeyFile, DeviceCaCertFile)
return string(readFile(DeviceCaCertFile))
}

func SignTlsCsr(csrPem string) string {
return run("./"+SignTlsScript, TlsCsrFile)
func SignTlsCsr(s KeyStorage, csrPem string) string {
return run(s, "./"+SignTlsScript, TlsCsrFile)
}

func SignCaCsr(csrPem string) string {
return run("./"+SignCaScript, OnlineCaCsrFile)
func SignCaCsr(s KeyStorage, csrPem string) string {
return run(s, "./"+SignCaScript, OnlineCaCsrFile)
}

func SignEl2GoCsr(csrPem string) string {
func SignEl2GoCsr(s KeyStorage, csrPem string) string {
tmpFile, err := os.CreateTemp("", "el2g-*.csr")
subcommands.DieNotNil(err)
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
_, err = tmpFile.Write([]byte(csrPem))
subcommands.DieNotNil(err)
return run("./"+SignCaScript, tmpFile.Name())
return run(s, "./"+SignCaScript, tmpFile.Name())
}

type KeyStorage interface {
setEnvVariables()
}

func (s *KeyStorageHsm) setEnvVariables() {
os.Setenv("HSM_MODULE", s.Hsm.Module)
os.Setenv("HSM_PIN", s.Hsm.Pin)
os.Setenv("HSM_TOKEN_LABEL", s.Hsm.TokenLabel)
}

func (s *KeyStorageFiles) setEnvVariables() {}
12 changes: 12 additions & 0 deletions x509/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ func readFile(filename string) string {
subcommands.DieNotNil(err)
return string(data)
}

type HsmInfo struct {
Module string
Pin string
TokenLabel string
}

type KeyStorageFiles struct{}

type KeyStorageHsm struct {
Hsm HsmInfo
}
84 changes: 43 additions & 41 deletions x509/golang.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
//go:build windows
//go:build windows || !bashPKI

package x509

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
Expand All @@ -31,23 +30,8 @@ func genRandomSerialNumber() *big.Int {
return serial
}

func genAndSaveKey(fn string) *ecdsa.PrivateKey {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
subcommands.DieNotNil(err)

keyRaw, err := x509.MarshalECPrivateKey(priv)
subcommands.DieNotNil(err)

keyBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyRaw}

factoryKeyBytes := pem.EncodeToMemory(keyBlock)
err = os.WriteFile(fn, factoryKeyBytes, 0600)
subcommands.DieNotNil(err)
return priv
}

func genCertificate(crtTemplate *x509.Certificate, caCrt *x509.Certificate, pub any, signerKey *ecdsa.PrivateKey) string {
certRaw, err := x509.CreateCertificate(rand.Reader, crtTemplate, caCrt, pub, signerKey)
func genCertificate(crtTemplate *x509.Certificate, caCrt *x509.Certificate, pub crypto.PublicKey, priv crypto.Signer) string {
certRaw, err := x509.CreateCertificate(rand.Reader, crtTemplate, caCrt, pub, priv)
subcommands.DieNotNil(err)

certPemBlock := pem.Block{Type: "CERTIFICATE", Bytes: certRaw}
Expand All @@ -67,13 +51,6 @@ func parsePemCertificateRequest(csrPem string) *x509.CertificateRequest {
return clientCSR
}

func parsePemPrivateKey(keyPem string) *ecdsa.PrivateKey {
caPrivateKeyPemBlock, _ := pem.Decode([]byte(keyPem))
caPrivateKey, err := x509.ParseECPrivateKey(caPrivateKeyPemBlock.Bytes)
subcommands.DieNotNil(err)
return caPrivateKey
}

func parsePemCertificate(crtPem string) *x509.Certificate {
caCrtPemBlock, _ := pem.Decode([]byte(crtPem))
crt, err := x509.ParseCertificate(caCrtPemBlock.Bytes)
Expand Down Expand Up @@ -112,8 +89,8 @@ func marshalSubject(cn string, ou string) pkix.Name {
return pkix.Name{ExtraNames: pkixAttrTypeValue}
}

func CreateFactoryCa(ou string) string {
priv := genAndSaveKey(FactoryCaKeyFile)
func CreateFactoryCa(storage KeyStorage, ou string) string {
priv, pub := storage.genAndSaveFactoryCaKey()
crtTemplate := x509.Certificate{
SerialNumber: genRandomSerialNumber(),
Subject: marshalSubject("Factory-CA", ou),
Expand All @@ -125,15 +102,14 @@ func CreateFactoryCa(ou string) string {
KeyUsage: x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
}
factoryCaString := genCertificate(&crtTemplate, &crtTemplate, &priv.PublicKey, priv)
factoryCaString := genCertificate(&crtTemplate, &crtTemplate, pub, priv)
writeFile(FactoryCaCertFile, factoryCaString, 0400)
return factoryCaString
}

func CreateDeviceCa(cn string, ou string) string {
factoryKey := parsePemPrivateKey(readFile(FactoryCaKeyFile))
func CreateDeviceCa(storage KeyStorage, cn string, ou string) string {
factoryKey := storage.getFactoryCaKey()
factoryCa := parsePemCertificate(readFile(FactoryCaCertFile))
priv := genAndSaveKey(DeviceCaKeyFile)
_, pub := genAndSaveKeyToFile(DeviceCaKeyFile)
crtTemplate := x509.Certificate{
SerialNumber: genRandomSerialNumber(),
Subject: marshalSubject(cn, ou),
Expand All @@ -146,14 +122,14 @@ func CreateDeviceCa(cn string, ou string) string {
MaxPathLenZero: true,
KeyUsage: x509.KeyUsageCertSign,
}
crtPem := genCertificate(&crtTemplate, factoryCa, &priv.PublicKey, factoryKey)
crtPem := genCertificate(&crtTemplate, factoryCa, pub, factoryKey)
writeFile(DeviceCaCertFile, crtPem, 0400)
return crtPem
}

func SignTlsCsr(csrPem string) string {
func SignTlsCsr(storage KeyStorage, csrPem string) string {
csr := parsePemCertificateRequest(csrPem)
factoryKey := parsePemPrivateKey(readFile(FactoryCaKeyFile))
factoryKey := storage.getFactoryCaKey()
factoryCa := parsePemCertificate(readFile(FactoryCaCertFile))
crtTemplate := x509.Certificate{
SerialNumber: genRandomSerialNumber(),
Expand All @@ -171,9 +147,9 @@ func SignTlsCsr(csrPem string) string {
return crtPem
}

func SignCaCsr(csrPem string) string {
func SignCaCsr(storage KeyStorage, csrPem string) string {
csr := parsePemCertificateRequest(csrPem)
factoryKey := parsePemPrivateKey(readFile(FactoryCaKeyFile))
factoryKey := storage.getFactoryCaKey()
factoryCa := parsePemCertificate(readFile(FactoryCaCertFile))
crtTemplate := x509.Certificate{
SerialNumber: genRandomSerialNumber(),
Expand All @@ -191,6 +167,32 @@ func SignCaCsr(csrPem string) string {
return crtPem
}

func SignEl2GoCsr(csrPem string) string {
return SignCaCsr(csrPem)
func SignEl2GoCsr(storage KeyStorage, csrPem string) string {
return SignCaCsr(storage, csrPem)
}

type KeyStorage interface {
genAndSaveFactoryCaKey() (crypto.Signer, crypto.PublicKey)
getFactoryCaKey() crypto.Signer
}

func (s *KeyStorageFiles) genAndSaveFactoryCaKey() (crypto.Signer, crypto.PublicKey) {
return genAndSaveKeyToFile(FactoryCaKeyFile)
}

func (s *KeyStorageFiles) getFactoryCaKey() crypto.Signer {
return loadKeyFromFile(FactoryCaKeyFile)
}

const (
FactoryCaKeyHsmId = "1"
FactoryCaKeyHsmLabel = "root-ca"
)

func (s *KeyStorageHsm) genAndSaveFactoryCaKey() (crypto.Signer, crypto.PublicKey) {
return genAndSaveKeyToHsm(s.Hsm, FactoryCaKeyHsmId, FactoryCaKeyHsmLabel)
}

func (s *KeyStorageHsm) getFactoryCaKey() crypto.Signer {
return loadKeyFromHsm(s.Hsm, FactoryCaKeyHsmId, FactoryCaKeyHsmLabel)
}
14 changes: 14 additions & 0 deletions x509/hsm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build windows || !bashPKI

package x509

import (
"testing"
"fmt"
)

func TestHsm(t *testing.T) {
storage := &KeyStorageHsm{HsmInfo{"/usr/lib/softhsm/libsofthsm2.so", "1234", "mine"}}

fmt.Println(CreateFactoryCa(storage, "test"))
}
38 changes: 38 additions & 0 deletions x509/storage_fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build windows || !bashPKI

package x509

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"os"

"github.com/foundriesio/fioctl/subcommands"
)

func genAndSaveKeyToFile(fn string) (crypto.Signer, crypto.PublicKey) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
subcommands.DieNotNil(err)

keyRaw, err := x509.MarshalECPrivateKey(priv)
subcommands.DieNotNil(err)

keyBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyRaw}

factoryKeyBytes := pem.EncodeToMemory(keyBlock)
err = os.WriteFile(fn, factoryKeyBytes, 0600)
subcommands.DieNotNil(err)
return priv, &priv.PublicKey
}

func loadKeyFromFile(fn string) crypto.Signer {
keyPem := readFile(fn)
caPrivateKeyPemBlock, _ := pem.Decode([]byte(keyPem))
caPrivateKey, err := x509.ParseECPrivateKey(caPrivateKeyPemBlock.Bytes)
subcommands.DieNotNil(err)
return caPrivateKey
}
Loading

0 comments on commit 8e3b8d9

Please sign in to comment.