From c6b71f4910c773b0f4b880b7e17a028577ed539b Mon Sep 17 00:00:00 2001 From: Volodymyr Khoroz Date: Thu, 19 Oct 2023 18:33:46 +0300 Subject: [PATCH] Feature: allow to add more online or local device CAs 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 --- subcommands/keys/ca_add_device_ca.go | 109 +++++++++++++++++++++++++++ x509/bash.go | 10 ++- x509/golang.go | 10 ++- 3 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 subcommands/keys/ca_add_device_ca.go diff --git a/subcommands/keys/ca_add_device_ca.go b/subcommands/keys/ca_add_device_ca.go new file mode 100644 index 00000000..4c962a59 --- /dev/null +++ b/subcommands/keys/ca_add_device_ca.go @@ -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 ", + 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)) +} diff --git a/x509/bash.go b/x509/bash.go index 2026e047..52f239b6 100644 --- a/x509/bash.go +++ b/x509/bash.go @@ -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. @@ -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 } diff --git a/x509/golang.go b/x509/golang.go index b51b26d6..00cf0d87 100644 --- a/x509/golang.go +++ b/x509/golang.go @@ -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 }