diff --git a/cli/internal/cmd/recover.go b/cli/internal/cmd/recover.go index dcadb6f2..655e0c38 100644 --- a/cli/internal/cmd/recover.go +++ b/cli/internal/cmd/recover.go @@ -14,7 +14,6 @@ import ( "encoding/pem" "errors" "fmt" - "os" "github.com/edgelesssys/marblerun/api" "github.com/edgelesssys/marblerun/cli/internal/file" @@ -62,7 +61,7 @@ func runRecover(cmd *cobra.Command, args []string) error { return err } - keyHandle, cancel, err := getRecoveryKeySigner(cmd) + keyHandle, cancel, err := getRecoveryKeySigner(cmd, afero.Afero{Fs: fs}) if err != nil { return err } @@ -94,7 +93,7 @@ func runRecover(cmd *cobra.Command, args []string) error { return nil } -func getRecoveryKeySigner(cmd *cobra.Command) (pkcs11.SignerDecrypter, func() error, error) { +func getRecoveryKeySigner(cmd *cobra.Command, fs afero.Afero) (pkcs11.SignerDecrypter, func() error, error) { privKeyFile, err := cmd.Flags().GetString("key") if err != nil { return nil, nil, err @@ -116,7 +115,7 @@ func getRecoveryKeySigner(cmd *cobra.Command) (pkcs11.SignerDecrypter, func() er return pkcs11.LoadRSAPrivateKey(pkcs11ConfigFile, pkcs11KeyID, pkcs11KeyLabel) } - privKeyPEM, err := os.ReadFile(privKeyFile) + privKeyPEM, err := fs.ReadFile(privKeyFile) if err != nil { return nil, nil, err } @@ -126,7 +125,12 @@ func getRecoveryKeySigner(cmd *cobra.Command) (pkcs11.SignerDecrypter, func() er } privK, err := x509.ParsePKCS8PrivateKey(privateKeyBlock.Bytes) if err != nil { - return nil, nil, err + // Try to parse as PKCS #1 private key as well + var pkcs1Err error + privK, pkcs1Err = x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes) + if pkcs1Err != nil { + return nil, nil, fmt.Errorf("parsing private key: tried PKCS #1 format: %w, tried PKCS #8 format: %w", pkcs1Err, err) + } } signer, ok := privK.(pkcs11.SignerDecrypter) if !ok { diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go new file mode 100644 index 00000000..79935035 --- /dev/null +++ b/cli/internal/cmd/recover_test.go @@ -0,0 +1,92 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: BUSL-1.1 +*/ + +package cmd + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetRecoveryKeySigner(t *testing.T) { + testCases := map[string]struct { + keyFlag string + fs afero.Fs + wantErr bool + }{ + "PKCS #8 key": { + keyFlag: "private.pem", + fs: func() afero.Fs { + fs := afero.NewMemMapFs() + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + privRaw, err := x509.MarshalPKCS8PrivateKey(privKey) + require.NoError(t, err) + privPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: privRaw, + }) + require.NoError(t, afero.WriteFile(fs, "private.pem", privPEM, 0o644)) + return fs + }(), + }, + "PKCS #1 key": { + keyFlag: "private.pem", + fs: func() afero.Fs { + fs := afero.NewMemMapFs() + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + privRaw := x509.MarshalPKCS1PrivateKey(privKey) + privPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privRaw, + }) + require.NoError(t, afero.WriteFile(fs, "private.pem", privPEM, 0o644)) + return fs + }(), + }, + "no key file": { + keyFlag: "private.pem", + fs: afero.NewMemMapFs(), + wantErr: true, + }, + "invalid key file": { + keyFlag: "private.pem", + fs: func() afero.Fs { + fs := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fs, "private.pem", []byte("invalid"), 0o644)) + return fs + }(), + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + cmd := NewRecoverCmd() + require.NoError(t, cmd.Flags().Set("key", tc.keyFlag)) + + signer, cancel, err := getRecoveryKeySigner(cmd, afero.Afero{Fs: tc.fs}) + if tc.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + assert.NotNil(signer) + assert.NotNil(cancel) + assert.NoError(cancel()) + }) + } +} diff --git a/docs/versioned_docs/version-1.7/workflows/recover-coordinator.md b/docs/versioned_docs/version-1.7/workflows/recover-coordinator.md index a357b1a3..533143a2 100644 --- a/docs/versioned_docs/version-1.7/workflows/recover-coordinator.md +++ b/docs/versioned_docs/version-1.7/workflows/recover-coordinator.md @@ -26,6 +26,19 @@ Assuming you named your recovery key `recoverKey1` in the manifest, and you save jq -r '.RecoverySecrets.recoverKey1' recovery_data | openssl base64 -d > recovery_key_encrypted ``` +:::caution + +If you generated the private recovery key using `openssl` version 1, the key will be in PKCS #1 format. +MarbleRun requires the key to be in PKCS #8 format. + +Use the following command to convert the key: + +```bash +openssl pkcs8 -topk8 -in private_key.pem -out private_key_pkcs8.pem -nocrypt +``` + +::: + Then decrypt and upload the extracted secret using the MarbleRun CLI: ```bash