diff --git a/cmd/stbak/cmd/archive.go b/cmd/stbak/cmd/archive.go index e2a5927..6933b23 100644 --- a/cmd/stbak/cmd/archive.go +++ b/cmd/stbak/cmd/archive.go @@ -150,7 +150,7 @@ var archiveCmd = &cobra.Command{ viper.GetBool(overwriteFlag), viper.GetString(compressionFlag), viper.GetString(encryptionFlag), - func(hdr *tar.Header, encryptionFormat string, i int) error { + func(hdr *tar.Header, i int) error { if len(hdrs) <= i { return errMissingTarHeader } diff --git a/cmd/stbak/cmd/recovery_fetch.go b/cmd/stbak/cmd/recovery_fetch.go index 0f60284..a049a2f 100644 --- a/cmd/stbak/cmd/recovery_fetch.go +++ b/cmd/stbak/cmd/recovery_fetch.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" + "aead.dev/minisign" "filippo.io/age" "github.com/ProtonMail/go-crypto/openpgp" "github.com/andybalholm/brotli" @@ -40,6 +41,8 @@ var ( errEmbeddedHeaderMissing = errors.New("embedded header is missing") errIdentityUnparsable = errors.New("recipient could not be parsed") + + errInvalidSignature = errors.New("invalid signature") ) var recoveryFetchCmd = &cobra.Command{ @@ -50,7 +53,11 @@ var recoveryFetchCmd = &cobra.Command{ return err } - return checkKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(identityFlag)) + if err := checkKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(identityFlag)); err != nil { + return err + } + + return checkKeyAccessible(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) }, RunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { @@ -61,6 +68,16 @@ var recoveryFetchCmd = &cobra.Command{ boil.DebugMode = true } + pubkey, err := readKey(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) + if err != nil { + return err + } + + recipient, err := parseSignerRecipient(viper.GetString(signatureFlag), pubkey) + if err != nil { + return err + } + privkey, err := readKey(viper.GetString(encryptionFlag), viper.GetString(identityFlag)) if err != nil { return err @@ -82,6 +99,8 @@ var recoveryFetchCmd = &cobra.Command{ viper.GetString(compressionFlag), viper.GetString(encryptionFlag), identity, + viper.GetString(signatureFlag), + recipient, ) }, } @@ -97,6 +116,8 @@ func restoreFromRecordAndBlock( compressionFormat string, encryptionFormat string, identity interface{}, + signatureFormat string, + recipient interface{}, ) error { f, isRegular, err := openTapeReadOnly(tape) if err != nil { @@ -183,7 +204,23 @@ func restoreFromRecordAndBlock( return err } - if _, err := io.Copy(dstFile, decompressor); err != nil { + signature := "" + if hdr.PAXRecords != nil { + if s, ok := hdr.PAXRecords[pax.STFSRecordSignature]; ok { + signature = s + } + } + + verifier, verify, err := verify(decompressor, signatureFormat, recipient, signature) + if err != nil { + return err + } + + if _, err := io.Copy(dstFile, verifier); err != nil { + return err + } + + if err := verify(); err != nil { return err } @@ -429,6 +466,61 @@ func decrypt( } } +func parseSignerRecipient( + signatureFormat string, + pubkey []byte, +) (interface{}, error) { + switch signatureFormat { + case signatureFormatMinisignKey: + var recipient minisign.PublicKey + if err := recipient.UnmarshalText(pubkey); err != nil { + return nil, err + } + + return recipient, nil + case noneKey: + return pubkey, nil + default: + return nil, errUnsupportedSignatureFormat + } +} + +func verify( + src io.Reader, + signatureFormat string, + recipient interface{}, + signature string, +) (io.Reader, func() error, error) { + switch signatureFormat { + case signatureFormatMinisignKey: + recipient, ok := recipient.(minisign.PublicKey) + if !ok { + return nil, nil, errRecipientUnparsable + } + + verifier := minisign.NewReader(src) + + return verifier, func() error { + decodedSignature, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return err + } + + if verifier.Verify(recipient, decodedSignature) { + return nil + } + + return errInvalidSignature + }, nil + case noneKey: + return io.NopCloser(src), func() error { + return nil + }, nil + default: + return nil, nil, errUnsupportedSignatureFormat + } +} + func init() { recoveryFetchCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") recoveryFetchCmd.PersistentFlags().IntP(recordFlag, "k", 0, "Record to seek too") @@ -437,6 +529,7 @@ func init() { recoveryFetchCmd.PersistentFlags().BoolP(previewFlag, "w", false, "Only read the header") recoveryFetchCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") recoveryFetchCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") + recoveryFetchCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to the public key to verify with") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/recovery_index.go b/cmd/stbak/cmd/recovery_index.go index fb19ee4..263d2c2 100644 --- a/cmd/stbak/cmd/recovery_index.go +++ b/cmd/stbak/cmd/recovery_index.go @@ -61,8 +61,8 @@ var recoveryIndexCmd = &cobra.Command{ viper.GetBool(overwriteFlag), viper.GetString(compressionFlag), viper.GetString(encryptionFlag), - func(hdr *tar.Header, encryptionFormat string, i int) error { - return decryptHeader(hdr, encryptionFormat, identity) + func(hdr *tar.Header, i int) error { + return decryptHeader(hdr, viper.GetString(encryptionFlag), identity) }, 0, ) @@ -80,7 +80,6 @@ func index( encryptionFormat string, decryptHeader func( hdr *tar.Header, - encryptionFormat string, i int, ) error, offset int, @@ -173,7 +172,7 @@ func index( } if i >= offset { - if err := decryptHeader(hdr, encryptionFormat, i-offset); err != nil { + if err := decryptHeader(hdr, i-offset); err != nil { return err } @@ -255,7 +254,7 @@ func index( } if i >= offset { - if err := decryptHeader(hdr, encryptionFormat, i-offset); err != nil { + if err := decryptHeader(hdr, i-offset); err != nil { return err } @@ -288,19 +287,6 @@ func index( return nil } -func init() { - recoveryIndexCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") - recoveryIndexCmd.PersistentFlags().IntP(recordFlag, "k", 0, "Record to seek too before counting") - recoveryIndexCmd.PersistentFlags().IntP(blockFlag, "b", 0, "Block in record to seek too before counting") - recoveryIndexCmd.PersistentFlags().BoolP(overwriteFlag, "o", false, "Remove the old index before starting to index") - recoveryIndexCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") - recoveryIndexCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") - - viper.AutomaticEnv() - - recoveryCmd.AddCommand(recoveryIndexCmd) -} - func indexHeader( record, block int64, hdr *tar.Header, @@ -472,3 +458,16 @@ func openTapeReadOnly(tape string) (f *os.File, isRegular bool, err error) { return f, isRegular, nil } + +func init() { + recoveryIndexCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") + recoveryIndexCmd.PersistentFlags().IntP(recordFlag, "k", 0, "Record to seek too before counting") + recoveryIndexCmd.PersistentFlags().IntP(blockFlag, "b", 0, "Block in record to seek too before counting") + recoveryIndexCmd.PersistentFlags().BoolP(overwriteFlag, "o", false, "Remove the old index before starting to index") + recoveryIndexCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") + recoveryIndexCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") + + viper.AutomaticEnv() + + recoveryCmd.AddCommand(recoveryIndexCmd) +} diff --git a/cmd/stbak/cmd/restore.go b/cmd/stbak/cmd/restore.go index dc99b9a..39ee4f9 100644 --- a/cmd/stbak/cmd/restore.go +++ b/cmd/stbak/cmd/restore.go @@ -30,7 +30,11 @@ var restoreCmd = &cobra.Command{ return err } - return checkKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(identityFlag)) + if err := checkKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(identityFlag)); err != nil { + return err + } + + return checkKeyAccessible(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) }, RunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { @@ -46,6 +50,16 @@ var restoreCmd = &cobra.Command{ return err } + pubkey, err := readKey(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) + if err != nil { + return err + } + + recipient, err := parseSignerRecipient(viper.GetString(signatureFlag), pubkey) + if err != nil { + return err + } + privkey, err := readKey(viper.GetString(encryptionFlag), viper.GetString(identityFlag)) if err != nil { return err @@ -123,6 +137,8 @@ var restoreCmd = &cobra.Command{ viper.GetString(compressionFlag), viper.GetString(encryptionFlag), identity, + viper.GetString(signatureFlag), + recipient, ); err != nil { return err } @@ -139,6 +155,7 @@ func init() { restoreCmd.PersistentFlags().BoolP(flattenFlag, "a", false, "Ignore the folder hierarchy on the tape or tar file") restoreCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") restoreCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") + restoreCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to the public key to verify with") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/update.go b/cmd/stbak/cmd/update.go index c0450a1..a89109f 100644 --- a/cmd/stbak/cmd/update.go +++ b/cmd/stbak/cmd/update.go @@ -88,7 +88,7 @@ var updateCmd = &cobra.Command{ false, viper.GetString(compressionFlag), viper.GetString(encryptionFlag), - func(hdr *tar.Header, encryptionFormat string, i int) error { + func(hdr *tar.Header, i int) error { if len(hdrs) <= i { return errMissingTarHeader }