diff --git a/subcommands/keys/ca_renewal_resign_device_ca.go b/subcommands/keys/ca_renewal_resign_device_ca.go new file mode 100644 index 00000000..1acfee7c --- /dev/null +++ b/subcommands/keys/ca_renewal_resign_device_ca.go @@ -0,0 +1,66 @@ +package keys + +import ( + "fmt" + "os" + + "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: "re-sign-device-ca []", + Short: "Re-sign all existing Device CAs with a new root CA for your Factory PKI", + Run: doReSignDeviceCaRenewal, + Args: cobra.RangeArgs(1, 2), + Long: `Re-sign all existing Device CAs with a new root CA for your Factory PKI. + +Both currently active and disabled Device CAs are being re-signed. +All their properties are preserved, including a serial number. +Only the signature and authority key ID are being changed. +This allows old certificates (issued by a previous root CA) to continue being used to issue device client certificates. + +Re-signed device CA certificates are stored in the provided PKI directory. +An old PKI directory is used to locate corresponding private keys, and copy them into the PKI directory. +Each located device CA gets the same file name, as it was in the old PKI directory. +If a device CA certificate cannot be located in an old PKI directory - it does not get stored locally. +If an old PKI directory argument is not provided, new certificates are not stored locally. +`, + } + caRenewalCmd.AddCommand(cmd) + 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 holding the root CA key") +} + +func doReSignDeviceCaRenewal(cmd *cobra.Command, args []string) { + factory := viper.GetString("factory") + certsDir := args[0] + oldCertsDir := args[1] + subcommands.DieNotNil(os.Chdir(certsDir)) + hsm, err := x509.ValidateHsmArgs( + hsmModule, hsmPin, hsmTokenLabel, "--hsm-module", "--hsm-pin", "--hsm-token-label") + subcommands.DieNotNil(err) + x509.InitHsm(hsm) + + // TBD: load serial-to-filename map from oldCertsDir + // TBD: fetch current device CAs from the server, re-sign them, store locally (if matched), and upload back. + // TBD - linter + fmt.Println(factory, oldCertsDir) +} + +// TBD: commands to add: +// # Re-sign existing TLS certs. As we don't care for their serial numbers - it is an alias to "fioctl keys rotate-tls". +// - fioctl keys renewal re-sign-tls +// # Revoke a specific root CA by its serial number. Cannot revoke currently active root CA. +// # This also revokes the cross-signed renewal bundle root CAs, associated with this root CA. +// - fioctl keys renewal revoke-root --serial + +// There will be no such command as `fioctl keys renewal finish`. +// Otherwise, users would often abuse it, leading to devices losing connectivity. +// Having harder to grasp command names, like e.g. revoke-root makes this process more controlled. diff --git a/x509/bash.go b/x509/bash.go index 65c61163..34812407 100644 --- a/x509/bash.go +++ b/x509/bash.go @@ -7,6 +7,7 @@ package x509 import ( "crypto" + "crypto/x509" "os" "os/exec" @@ -164,6 +165,10 @@ func SignEl2GoCsr(csrPem string) string { return signCaCsr("el2g-*", csrPem) } +func ReSignCrt(crt *x509.Certificate) string { + return neverland() +} + func CreateCrl(serials map[string]int) string { return neverland() } diff --git a/x509/golang.go b/x509/golang.go index 2546e42f..2be9ccd6 100644 --- a/x509/golang.go +++ b/x509/golang.go @@ -34,6 +34,7 @@ func CreateFactoryCrossCa(ou string, pubkey crypto.PublicKey) string { // This function does an inverse: produces a factory CA with a public key "borrowed" from another factory CA. // The end result is the same, but we don't need to export the internal key storage interface. // This certificate is not written to disk, as it is only needed intermittently. + // Cannot use a ReSignCrt as we need a new certificate here (with e.g. a new serial number). priv := factoryCaKeyStorage.loadKey() crtTemplate := genFactoryCaTemplate(marshalSubject(factoryCaName, ou)) return genCertificate(crtTemplate, crtTemplate, pubkey, priv) @@ -74,6 +75,13 @@ func SignEstCsr(csrPem string) string { return genTlsCert(csr.Subject, csr.DNSNames, csr.PublicKey) } +func ReSignCrt(crt *x509.Certificate) string { + // Use an input certificate as a template for a new certificate, preserving all its properties except a signature. + factoryKey := factoryCaKeyStorage.loadKey() + factoryCa := LoadCertFromFile(FactoryCaCertFile) + return genCertificate(crt, factoryCa, crt.PublicKey, factoryKey) +} + var oidExtensionReasonCode = []int{2, 5, 29, 21} func CreateCrl(serials map[string]int) string {