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 }