Skip to content

Commit

Permalink
Feature: allow to add more online or local device CAs
Browse files Browse the repository at this point in the history
For the local device CA:
- A user can generate new device certificates using any added local CA, for which they possess a cert and private key.

For the online device CA:
- The device API will use the new online device CA to generate device certificates.
  Old online device CA key is over-written, so that it can no longer be used.

All newly added device CAs become trusted by the device gateway.
This command does not remove any existing device CAs from the list of trusted device CAs.

Signed-off-by: Volodymyr Khoroz <[email protected]>
  • Loading branch information
vkhoroz committed Oct 19, 2023
1 parent b3ac859 commit c6b71f4
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 6 deletions.
109 changes: 109 additions & 0 deletions subcommands/keys/ca_add_device_ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keys

import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"

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

func init() {
cmd := &cobra.Command{
Use: "add-device-ca <PKI Directory>",
Short: "Add device CA to the list of CAs allowed to issue device client certificates",
Run: doAddDeviceCa,
Args: cobra.ExactArgs(1),
Long: `Add device CA to the list of CAs allowed to issue device client certificates.
This command can add one or both of the following certificates:
### online-ca - A Foundries.io owned keypair to support lmp-device-register.
In order for lmp-device-register to work, Foundries.io needs the ability to sign client certificates for devices.
If enabled, the factory_ca keypair will sign the certificate signing request returned from the API.
If the online-ca was already created earlier, a new online-ca will replace it for the registration process.
Still, the previous online-ca will be present in a list of device CAs trusted by the device gateway,
so that devices with client certificates issued by it may continue to connect to Foundries.io services.
### local-ca - A keypair you own that can be used for things like your manufacturing process,
where you may generate device client certificates without having to communicate with Foundries.io web services.
You can create as many local-ca files as you need, and use each of them to generate device client certificates.
All such CAs will be added to the list of device CAs trusted by the device gateway.`,
}
caCmd.AddCommand(cmd)
cmd.Flags().BoolP("online-ca", "", false,
"Create an online CA owned by Foundries.io that works with lmp-device-register")
cmd.Flags().BoolP("local-ca", "", false,
"Create a local CA that you can use for signing your own device certificates")
cmd.Flags().StringP("local-ca-filename", "", x509.DeviceCaCertFile,
fmt.Sprintf("A file name of the local CA (only needed if the %s file already exists)", x509.DeviceCaCertFile))
_ = cmd.MarkFlagFilename("local-ca-filename")
// HSM variables defined in ca_create.go
cmd.Flags().StringVarP(&hsmModule, "hsm-module", "", "", "Load a root CA key from a PKCS#11 compatible HSM using this module")
cmd.Flags().StringVarP(&hsmPin, "hsm-pin", "", "", "The PKCS#11 PIN to log into the HSM")
cmd.Flags().StringVarP(&hsmTokenLabel, "hsm-token-label", "", "", "The label of the HSM token containing the root CA key")
}

func doAddDeviceCa(cmd *cobra.Command, args []string) {
factory := viper.GetString("factory")
createLocalCA, _ := cmd.Flags().GetBool("local-ca")
createOnlineCA, _ := cmd.Flags().GetBool("online-ca")
localCaFilename, _ := cmd.Flags().GetString("local-ca-filename")

if !createLocalCA && !createOnlineCA {
subcommands.DieNotNil(errors.New("At least one of --online-ca or --local-ca must be true"))
}

subcommands.DieNotNil(os.Chdir(args[0]))
hsm, err := x509.ValidateHsmArgs(
hsmModule, hsmPin, hsmTokenLabel, "--hsm-module", "--hsm-pin", "--hsm-token-label")
subcommands.DieNotNil(err)
x509.InitHsm(hsm)

fmt.Println("Fetching a list of existing device CAs")
resp, err := api.FactoryGetCA(factory)
subcommands.DieNotNil(err)
certs := client.CaCerts{CaCrt: resp.CaCrt}
if len(certs.CaCrt) == 0 {
subcommands.DieNotNil(errors.New("Factory PKI not initialized. Set it up using 'fioctl keys ca create'."))
}

if createLocalCA {
localCaKeyFilename := strings.TrimSuffix(localCaFilename, ".pem") + ".key"
if _, err := os.Stat(localCaFilename); !os.IsNotExist(err) {
subcommands.DieNotNil(fmt.Errorf(`A local device CA file %s already exists.
Please specify a different name with --local-ca-filename.`, localCaFilename))
}
if _, err := os.Stat(localCaKeyFilename); !os.IsNotExist(err) {
subcommands.DieNotNil(fmt.Errorf(`A local device CA key file %s already exists.
Please specify a different name with --local-ca-filename.`, localCaKeyFilename))
}

fmt.Println("Creating local device CA")
commonName := getDeviceCaCommonName(factory)
certs.CaCrt += "\n" + x509.CreateDeviceCaExt(commonName, factory, localCaKeyFilename, localCaFilename)
}

if createOnlineCA {
fmt.Println("Requesting new Foundries.io Online Device CA CSR")
csrs, err := api.FactoryCreateCA(factory, client.CaCreateOptions{CreateOnlineCa: true})
subcommands.DieNotNil(err)

if _, err := os.Stat(x509.OnlineCaCertFile); !os.IsNotExist(err) {
fmt.Printf("Moving existing online device CA file from %s to %s.bak", x509.OnlineCaCertFile, x509.OnlineCaCertFile)
subcommands.DieNotNil(os.Rename(x509.OnlineCaCertFile, x509.OnlineCaCertFile+".bak"))
}

fmt.Println("Signing Foundries.io CSR for online use")
certs.CaCrt += "\n" + x509.SignCaCsr(csrs.CaCsr)
}

fmt.Println("Uploading signed certs to Foundries.io")
subcommands.DieNotNil(api.FactoryPatchCA(factory, certs))
}
10 changes: 7 additions & 3 deletions x509/bash.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ rm ca.cnf
return readFile(FactoryCaCertFile)
}

func CreateDeviceCa(cn string, ou string) string {
func CreateDeviceCa(cn, ou string) string {
return CreateDeviceCaExt(cn, ou, DeviceCaKeyFile, DeviceCaCertFile)
}

func CreateDeviceCaExt(cn, ou, keyFile, certFile string) string {
const script = `#!/bin/sh -e
## This is an optional script a customer can use to create a certificate
## capable of signing a certicate signing request from an LMP device.
Expand Down Expand Up @@ -129,9 +133,9 @@ openssl ecparam -genkey -name prime256v1 | openssl ec -out $key
openssl req -new -config ca.cnf -key $key
chmod 400 $key
rm ca.cnf`
csrPem := run(script, DeviceCaKeyFile, cn, ou)
csrPem := run(script, keyFile, cn, ou)
crtPem := signCaCsr("device-ca-*", csrPem)
writeFile(DeviceCaCertFile, crtPem)
writeFile(certFile, crtPem)
return crtPem
}

Expand Down
10 changes: 7 additions & 3 deletions x509/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,14 @@ func CreateFactoryCa(ou string) string {
return factoryCaString
}

func CreateDeviceCa(cn string, ou string) string {
priv := genAndSaveKeyToFile(DeviceCaKeyFile)
func CreateDeviceCa(cn, ou string) string {
return CreateDeviceCaExt(cn, ou, DeviceCaKeyFile, DeviceCaCertFile)
}

func CreateDeviceCaExt(cn, ou, keyFile, certFile string) string {
priv := genAndSaveKeyToFile(keyFile)
crtPem := genCaCert(marshalSubject(cn, ou), priv.Public())
writeFile(DeviceCaCertFile, crtPem)
writeFile(certFile, crtPem)
return crtPem
}

Expand Down

0 comments on commit c6b71f4

Please sign in to comment.