From f4dc9f9f521a5ff1c395123ada9f4bf73bf345cd Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Thu, 2 Dec 2021 11:26:18 +0100 Subject: [PATCH] feat: Start implementation of metadata encryption --- cmd/stbak/cmd/archive.go | 82 +++++++- cmd/stbak/cmd/delete.go | 155 +++++++++----- cmd/stbak/cmd/move.go | 162 ++++++++++----- cmd/stbak/cmd/recovery_fetch.go | 66 +++++- cmd/stbak/cmd/recovery_index.go | 46 ++++- cmd/stbak/cmd/recovery_query.go | 355 ++++++++++++++++++-------------- cmd/stbak/cmd/restore.go | 8 +- cmd/stbak/cmd/update.go | 28 ++- 8 files changed, 613 insertions(+), 289 deletions(-) diff --git a/cmd/stbak/cmd/archive.go b/cmd/stbak/cmd/archive.go index 55c79e0..7e26917 100644 --- a/cmd/stbak/cmd/archive.go +++ b/cmd/stbak/cmd/archive.go @@ -2,8 +2,10 @@ package cmd import ( "archive/tar" + "bytes" "compress/gzip" "context" + "encoding/base32" "errors" "fmt" "io" @@ -35,7 +37,9 @@ const ( srcFlag = "src" overwriteFlag = "overwrite" compressionLevelFlag = "compression-level" - keyFlag = "key" + + recipientFlag = "recipient" + identityFlag = "identity" compressionLevelFastest = "fastest" compressionLevelBalanced = "balanced" @@ -48,7 +52,8 @@ var ( errUnknownCompressionLevel = errors.New("unknown compression level") errUnsupportedCompressionLevel = errors.New("unsupported compression level") - errKeyNotAccessible = errors.New("key not found or accessible") + errRecipientNotAccessible = errors.New("recipient/public key not found or accessible") + errIdentityNotAccessible = errors.New("identity/private key not found or accessible") ) type flusher interface { @@ -91,8 +96,12 @@ var archiveCmd = &cobra.Command{ } if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - if _, err := os.Stat(viper.GetString(keyFlag)); err != nil { - return errKeyNotAccessible + if _, err := os.Stat(viper.GetString(recipientFlag)); err != nil { + return errRecipientNotAccessible + } + + if _, err := os.Stat(viper.GetString(identityFlag)); err != nil { + return errIdentityNotAccessible } } @@ -121,13 +130,19 @@ var archiveCmd = &cobra.Command{ } pubkey := []byte{} + privkey := []byte{} if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - p, err := ioutil.ReadFile(viper.GetString(keyFlag)) + p, err := ioutil.ReadFile(viper.GetString(recipientFlag)) if err != nil { return err } pubkey = p + + privkey, err = ioutil.ReadFile(viper.GetString(identityFlag)) + if err != nil { + return err + } } if err := archive( @@ -152,6 +167,7 @@ var archiveCmd = &cobra.Command{ viper.GetBool(overwriteFlag), viper.GetString(compressionFlag), viper.GetString(encryptionFlag), + privkey, ) }, } @@ -327,6 +343,10 @@ func archive( return err } + if err := encryptHeader(hdr, encryptionFormat, pubkey); err != nil { + return err + } + if err := tw.WriteHeader(hdr); err != nil { return err } @@ -406,6 +426,21 @@ func checkCompressionLevel(compressionLevel string) error { return nil } +func encryptHeader( + hdr *tar.Header, + encryptionFormat string, + pubkey []byte, +) error { + var err error + + hdr.Name, err = encryptString(hdr.Name, encryptionFormat, pubkey) + if err != nil { + return err + } + + return nil +} + func addSuffix(name string, compressionFormat string, encryptionFormat string) (string, error) { switch compressionFormat { case compressionFormatGZipKey: @@ -458,6 +493,40 @@ func encrypt( } } +func encryptString( + src string, + encryptionFormat string, + pubkey []byte, +) (string, error) { + switch encryptionFormat { + case encryptionFormatAgeKey: + recipient, err := age.ParseX25519Recipient(string(pubkey)) + if err != nil { + return "", err + } + + out := &bytes.Buffer{} + w, err := age.Encrypt(out, recipient) + if err != nil { + return "", err + } + + if _, err := io.WriteString(w, src); err != nil { + return "", err + } + + if err := w.Close(); err != nil { + return "", err + } + + return base32.StdEncoding.EncodeToString(out.Bytes()), nil + case encryptionFormatNoneKey: + return src, nil + default: + return "", errUnsupportedEncryptionFormat + } +} + func compress( dst io.Writer, compressionFormat string, @@ -587,7 +656,8 @@ func init() { archiveCmd.PersistentFlags().StringP(srcFlag, "s", ".", "File or directory to archive") archiveCmd.PersistentFlags().BoolP(overwriteFlag, "o", false, "Start writing from the start instead of from the end of the tape or tar file") archiveCmd.PersistentFlags().StringP(compressionLevelFlag, "l", compressionLevelBalanced, fmt.Sprintf("Compression level to use (default %v, available are %v)", compressionLevelBalanced, knownCompressionLevels)) - archiveCmd.PersistentFlags().StringP(keyFlag, "k", "", "Path to public key of recipient to encrypt for") + archiveCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to public key of recipient to encrypt for") + archiveCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/delete.go b/cmd/stbak/cmd/delete.go index 110ee64..d0de0b0 100644 --- a/cmd/stbak/cmd/delete.go +++ b/cmd/stbak/cmd/delete.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bufio" "context" + "io/ioutil" "os" "github.com/pojntfx/stfs/pkg/controllers" @@ -26,6 +27,19 @@ var deleteCmd = &cobra.Command{ Use: "delete", Aliases: []string{"del", "d", "rm"}, Short: "Delete a file or directory from tape or tar file", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + if _, err := os.Stat(viper.GetString(recipientFlag)); err != nil { + return errRecipientNotAccessible + } + } + + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { return err @@ -35,70 +49,100 @@ var deleteCmd = &cobra.Command{ boil.DebugMode = true } - dirty := false - tw, _, cleanup, err := openTapeWriter(viper.GetString(tapeFlag)) - if err != nil { - return err - } - defer cleanup(&dirty) - - metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) - if err := metadataPersister.Open(); err != nil { - return err - } - - headersToDelete := []*models.Header{} - dbhdr, err := metadataPersister.GetHeader(context.Background(), viper.GetString(nameFlag)) - if err != nil { - return err - } - headersToDelete = append(headersToDelete, dbhdr) - - // If the header refers to a directory, get it's children - if dbhdr.Typeflag == tar.TypeDir { - dbhdrs, err := metadataPersister.GetHeaderChildren(context.Background(), viper.GetString(nameFlag)) + pubkey := []byte{} + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + p, err := ioutil.ReadFile(viper.GetString(recipientFlag)) if err != nil { return err } - headersToDelete = append(headersToDelete, dbhdrs...) + pubkey = p } - // Remove the headers from the index - if err := metadataPersister.DeleteHeaders(context.Background(), headersToDelete); err != nil { - return nil - } - - if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { - return err - } - - // Append deletion headers to the tape or tar file - for _, dbhdr := range headersToDelete { - hdr, err := converters.DBHeaderToTarHeader(dbhdr) - if err != nil { - return err - } - - hdr.Size = 0 // Don't try to seek after the record - hdr.PAXRecords[pax.STFSRecordVersion] = pax.STFSRecordVersion1 - hdr.PAXRecords[pax.STFSRecordAction] = pax.STFSRecordActionDelete - - if err := tw.WriteHeader(hdr); err != nil { - return err - } - - dirty = true - - if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(-1, -1, hdr)); err != nil { - return err - } - } - - return nil + return delete( + viper.GetString(tapeFlag), + viper.GetString(metadataFlag), + viper.GetString(nameFlag), + viper.GetString(encryptionFlag), + pubkey, + ) }, } +func delete( + tape string, + metadata string, + name string, + encryptionFormat string, + pubkey []byte, +) error { + dirty := false + tw, _, cleanup, err := openTapeWriter(tape) + if err != nil { + return err + } + defer cleanup(&dirty) + + metadataPersister := persisters.NewMetadataPersister(metadata) + if err := metadataPersister.Open(); err != nil { + return err + } + + headersToDelete := []*models.Header{} + dbhdr, err := metadataPersister.GetHeader(context.Background(), name) + if err != nil { + return err + } + headersToDelete = append(headersToDelete, dbhdr) + + // If the header refers to a directory, get it's children + if dbhdr.Typeflag == tar.TypeDir { + dbhdrs, err := metadataPersister.GetHeaderChildren(context.Background(), name) + if err != nil { + return err + } + + headersToDelete = append(headersToDelete, dbhdrs...) + } + + // Remove the headers from the index + if err := metadataPersister.DeleteHeaders(context.Background(), headersToDelete); err != nil { + return nil + } + + if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { + return err + } + + // Append deletion headers to the tape or tar file + for _, dbhdr := range headersToDelete { + hdr, err := converters.DBHeaderToTarHeader(dbhdr) + if err != nil { + return err + } + + hdr.Size = 0 // Don't try to seek after the record + hdr.PAXRecords[pax.STFSRecordVersion] = pax.STFSRecordVersion1 + hdr.PAXRecords[pax.STFSRecordAction] = pax.STFSRecordActionDelete + + if err := encryptHeader(hdr, encryptionFormat, pubkey); err != nil { + return err + } + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + dirty = true + + if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(-1, -1, hdr)); err != nil { + return err + } + } + + return nil +} + func openTapeWriter(tape string) (tw *tar.Writer, isRegular bool, cleanup func(dirty *bool) error, err error) { stat, err := os.Stat(tape) if err == nil { @@ -169,6 +213,7 @@ func openTapeWriter(tape string) (tw *tar.Writer, isRegular bool, cleanup func(d func init() { deleteCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") deleteCmd.PersistentFlags().StringP(nameFlag, "n", "", "Name of the file to remove") + deleteCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to public key of recipient to encrypt for") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/move.go b/cmd/stbak/cmd/move.go index c1aa016..b635366 100644 --- a/cmd/stbak/cmd/move.go +++ b/cmd/stbak/cmd/move.go @@ -3,6 +3,8 @@ package cmd import ( "archive/tar" "context" + "io/ioutil" + "os" "strings" "github.com/pojntfx/stfs/pkg/converters" @@ -19,6 +21,19 @@ var moveCmd = &cobra.Command{ Use: "move", Aliases: []string{"mov", "m", "mv"}, Short: "Move a file or directory on tape or tar file", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + if _, err := os.Stat(viper.GetString(recipientFlag)); err != nil { + return errRecipientNotAccessible + } + } + + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { return err @@ -28,76 +43,109 @@ var moveCmd = &cobra.Command{ boil.DebugMode = true } - dirty := false - tw, _, cleanup, err := openTapeWriter(viper.GetString(tapeFlag)) - if err != nil { - return err - } - defer cleanup(&dirty) - - metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) - if err := metadataPersister.Open(); err != nil { - return err - } - - headersToMove := []*models.Header{} - dbhdr, err := metadataPersister.GetHeader(context.Background(), viper.GetString(srcFlag)) - if err != nil { - return err - } - headersToMove = append(headersToMove, dbhdr) - - // If the header refers to a directory, get it's children - if dbhdr.Typeflag == tar.TypeDir { - dbhdrs, err := metadataPersister.GetHeaderChildren(context.Background(), viper.GetString(srcFlag)) + pubkey := []byte{} + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + p, err := ioutil.ReadFile(viper.GetString(recipientFlag)) if err != nil { return err } - headersToMove = append(headersToMove, dbhdrs...) + pubkey = p } - // Move the headers in the index - if err := metadataPersister.MoveHeaders(context.Background(), headersToMove, viper.GetString(srcFlag), viper.GetString(dstFlag)); err != nil { - return nil - } - - if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { - return err - } - - // Append move headers to the tape or tar file - for _, dbhdr := range headersToMove { - hdr, err := converters.DBHeaderToTarHeader(dbhdr) - if err != nil { - return err - } - - hdr.Size = 0 // Don't try to seek after the record - hdr.Name = strings.TrimSuffix(viper.GetString(dstFlag), "/") + strings.TrimPrefix(hdr.Name, strings.TrimSuffix(viper.GetString(srcFlag), "/")) - hdr.PAXRecords[pax.STFSRecordVersion] = pax.STFSRecordVersion1 - hdr.PAXRecords[pax.STFSRecordAction] = pax.STFSRecordActionUpdate - hdr.PAXRecords[pax.STFSRecordReplacesName] = dbhdr.Name - - if err := tw.WriteHeader(hdr); err != nil { - return err - } - - dirty = true - - if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(-1, -1, hdr)); err != nil { - return err - } - } - - return nil + return move( + viper.GetString(tapeFlag), + viper.GetString(metadataFlag), + viper.GetString(srcFlag), + viper.GetString(dstFlag), + viper.GetString(encryptionFlag), + pubkey, + ) }, } +func move( + tape string, + metadata string, + src string, + dst string, + encryptionFormat string, + pubkey []byte, +) error { + dirty := false + tw, _, cleanup, err := openTapeWriter(tape) + if err != nil { + return err + } + defer cleanup(&dirty) + + metadataPersister := persisters.NewMetadataPersister(metadata) + if err := metadataPersister.Open(); err != nil { + return err + } + + headersToMove := []*models.Header{} + dbhdr, err := metadataPersister.GetHeader(context.Background(), src) + if err != nil { + return err + } + headersToMove = append(headersToMove, dbhdr) + + // If the header refers to a directory, get it's children + if dbhdr.Typeflag == tar.TypeDir { + dbhdrs, err := metadataPersister.GetHeaderChildren(context.Background(), src) + if err != nil { + return err + } + + headersToMove = append(headersToMove, dbhdrs...) + } + + // Move the headers in the index + if err := metadataPersister.MoveHeaders(context.Background(), headersToMove, src, dst); err != nil { + return nil + } + + if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { + return err + } + + // Append move headers to the tape or tar file + for _, dbhdr := range headersToMove { + hdr, err := converters.DBHeaderToTarHeader(dbhdr) + if err != nil { + return err + } + + hdr.Size = 0 // Don't try to seek after the record + hdr.Name = strings.TrimSuffix(dst, "/") + strings.TrimPrefix(hdr.Name, strings.TrimSuffix(src, "/")) + hdr.PAXRecords[pax.STFSRecordVersion] = pax.STFSRecordVersion1 + hdr.PAXRecords[pax.STFSRecordAction] = pax.STFSRecordActionUpdate + hdr.PAXRecords[pax.STFSRecordReplacesName] = dbhdr.Name + + if err := encryptHeader(hdr, encryptionFormat, pubkey); err != nil { + return err + } + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + dirty = true + + if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(-1, -1, hdr)); err != nil { + return err + } + } + + return nil +} + func init() { moveCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") moveCmd.PersistentFlags().StringP(srcFlag, "s", "", "Current path of the file or directory to move") moveCmd.PersistentFlags().StringP(dstFlag, "d", "", "Path to move the file or directory to") + moveCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to public key of recipient to encrypt for") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/recovery_fetch.go b/cmd/stbak/cmd/recovery_fetch.go index bc37730..3c81cec 100644 --- a/cmd/stbak/cmd/recovery_fetch.go +++ b/cmd/stbak/cmd/recovery_fetch.go @@ -3,8 +3,10 @@ package cmd import ( "archive/tar" "bufio" + "bytes" "compress/gzip" "context" + "encoding/base32" "io" "io/ioutil" "os" @@ -40,8 +42,8 @@ var recoveryFetchCmd = &cobra.Command{ } if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - if _, err := os.Stat(viper.GetString(keyFlag)); err != nil { - return errKeyNotAccessible + if _, err := os.Stat(viper.GetString(identityFlag)); err != nil { + return errIdentityNotAccessible } } @@ -58,7 +60,7 @@ var recoveryFetchCmd = &cobra.Command{ privkey := []byte{} if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - p, err := ioutil.ReadFile(viper.GetString(keyFlag)) + p, err := ioutil.ReadFile(viper.GetString(identityFlag)) if err != nil { return err } @@ -127,6 +129,10 @@ func restoreFromRecordAndBlock( return err } + if err := decryptHeader(hdr, encryptionFormat, privkey); err != nil { + return err + } + if showHeader { if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { return err @@ -238,6 +244,56 @@ func decompress( } } +func decryptHeader( + hdr *tar.Header, + encryptionFormat string, + privkey []byte, +) error { + var err error + + hdr.Name, err = decryptString(hdr.Name, encryptionFormat, privkey) + if err != nil { + return err + } + + return nil +} + +func decryptString( + src string, + encryptionFormat string, + privkey []byte, +) (string, error) { + switch encryptionFormat { + case encryptionFormatAgeKey: + identity, err := age.ParseX25519Identity(string(privkey)) + if err != nil { + return "", err + } + + decoded, err := base32.StdEncoding.DecodeString(src) + if err != nil { + return "", err + } + + r, err := age.Decrypt(bytes.NewBufferString(string(decoded)), identity) + if err != nil { + return "", err + } + + out := &bytes.Buffer{} + if _, err := io.Copy(out, r); err != nil { + return "", err + } + + return out.String(), nil + case encryptionFormatNoneKey: + return src, nil + default: + return "", errUnsupportedEncryptionFormat + } +} + func decrypt( src io.Reader, encryptionFormat string, @@ -265,11 +321,11 @@ func decrypt( func init() { recoveryFetchCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") - recoveryFetchCmd.PersistentFlags().IntP(recordFlag, "r", 0, "Record to seek too") + recoveryFetchCmd.PersistentFlags().IntP(recordFlag, "k", 0, "Record to seek too") recoveryFetchCmd.PersistentFlags().IntP(blockFlag, "b", 0, "Block in record to seek too") recoveryFetchCmd.PersistentFlags().StringP(dstFlag, "d", "", "File to restore to (archived name by default)") recoveryFetchCmd.PersistentFlags().BoolP(previewFlag, "p", false, "Only read the header") - recoveryFetchCmd.PersistentFlags().StringP(keyFlag, "k", "", "Path to private key of recipient that has been encrypted for") + recoveryFetchCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/recovery_index.go b/cmd/stbak/cmd/recovery_index.go index 2273293..ce4b08e 100644 --- a/cmd/stbak/cmd/recovery_index.go +++ b/cmd/stbak/cmd/recovery_index.go @@ -26,6 +26,19 @@ import ( var recoveryIndexCmd = &cobra.Command{ Use: "index", Short: "Index contents of tape or tar file", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + if _, err := os.Stat(viper.GetString(identityFlag)); err != nil { + return errIdentityNotAccessible + } + } + + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { return err @@ -35,6 +48,16 @@ var recoveryIndexCmd = &cobra.Command{ boil.DebugMode = true } + privkey := []byte{} + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + p, err := ioutil.ReadFile(viper.GetString(identityFlag)) + if err != nil { + return err + } + + privkey = p + } + return index( viper.GetString(tapeFlag), viper.GetString(metadataFlag), @@ -44,6 +67,7 @@ var recoveryIndexCmd = &cobra.Command{ viper.GetBool(overwriteFlag), viper.GetString(compressionFlag), viper.GetString(encryptionFlag), + privkey, ) }, } @@ -57,6 +81,7 @@ func index( overwrite bool, compressionFormat string, encryptionFormat string, + privkey []byte, ) error { if overwrite { f, err := os.OpenFile(metadata, os.O_WRONLY|os.O_CREATE, 0600) @@ -105,19 +130,19 @@ func index( } nextTotalBlocks := math.Ceil(float64((curr)) / float64(controllers.BlockSize)) - record = int64(nextTotalBlocks) / int64(viper.GetInt(recordSizeFlag)) - block = int64(nextTotalBlocks) - (record * int64(viper.GetInt(recordSizeFlag))) + record = int64(nextTotalBlocks) / int64(recordSize) + block = int64(nextTotalBlocks) - (record * int64(recordSize)) if block < 0 { record-- - block = int64(viper.GetInt(recordSizeFlag)) - 1 - } else if block >= int64(viper.GetInt(recordSizeFlag)) { + block = int64(recordSize) - 1 + } else if block >= int64(recordSize) { record++ block = 0 } // Seek to record and block - if _, err := f.Seek(int64((viper.GetInt(recordSizeFlag)*controllers.BlockSize*int(record))+int(block)*controllers.BlockSize), io.SeekStart); err != nil { + if _, err := f.Seek(int64((recordSize*controllers.BlockSize*int(record))+int(block)*controllers.BlockSize), io.SeekStart); err != nil { return err } @@ -144,6 +169,10 @@ func index( break } + if err := decryptHeader(hdr, encryptionFormat, privkey); err != nil { + return err + } + if err := indexHeader(record, block, hdr, metadataPersister, compressionFormat, encryptionFormat); err != nil { return nil } @@ -217,6 +246,10 @@ func index( } } + if err := decryptHeader(hdr, encryptionFormat, privkey); err != nil { + return err + } + if err := indexHeader(record, block, hdr, metadataPersister, compressionFormat, encryptionFormat); err != nil { return nil } @@ -245,9 +278,10 @@ func index( func init() { recoveryIndexCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") - recoveryIndexCmd.PersistentFlags().IntP(recordFlag, "r", 0, "Record to seek too before counting") + 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") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/recovery_query.go b/cmd/stbak/cmd/recovery_query.go index e6b9e86..729ade7 100644 --- a/cmd/stbak/cmd/recovery_query.go +++ b/cmd/stbak/cmd/recovery_query.go @@ -6,6 +6,7 @@ import ( "io" "io/ioutil" "math" + "os" "github.com/pojntfx/stfs/pkg/controllers" "github.com/pojntfx/stfs/pkg/counters" @@ -18,6 +19,19 @@ import ( var recoveryQueryCmd = &cobra.Command{ Use: "query", Short: "Query contents of tape or tar file without the index", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + if _, err := os.Stat(viper.GetString(identityFlag)); err != nil { + return errIdentityNotAccessible + } + } + + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { return err @@ -27,186 +41,223 @@ var recoveryQueryCmd = &cobra.Command{ boil.DebugMode = true } - f, isRegular, err := openTapeReadOnly(viper.GetString(tapeFlag)) - if err != nil { + privkey := []byte{} + if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { + p, err := ioutil.ReadFile(viper.GetString(identityFlag)) + if err != nil { + return err + } + + privkey = p + } + + return query( + viper.GetString(tapeFlag), + viper.GetInt(recordFlag), + viper.GetInt(blockFlag), + viper.GetInt(recordSizeFlag), + viper.GetString(encryptionFlag), + privkey, + ) + }, +} + +func query( + tape string, + record int, + block int, + recordSize int, + encryptionFormat string, + privkey []byte, +) error { + f, isRegular, err := openTapeReadOnly(tape) + if err != nil { + return err + } + defer f.Close() + + if isRegular { + // Seek to record and block + if _, err := f.Seek(int64((recordSize*controllers.BlockSize*record)+block*controllers.BlockSize), 0); err != nil { return err } - defer f.Close() - if isRegular { - // Seek to record and block - if _, err := f.Seek(int64((viper.GetInt(recordSizeFlag)*controllers.BlockSize*viper.GetInt(recordFlag))+viper.GetInt(blockFlag)*controllers.BlockSize), 0); err != nil { - return err - } + tr := tar.NewReader(f) - tr := tar.NewReader(f) + record := int64(record) + block := int64(block) - record := viper.GetInt64(recordFlag) - block := viper.GetInt64(blockFlag) - - for { - hdr, err := tr.Next() - if err != nil { - for { - curr, err := f.Seek(0, io.SeekCurrent) - if err != nil { - return err - } - - nextTotalBlocks := math.Ceil(float64((curr)) / float64(controllers.BlockSize)) - record = int64(nextTotalBlocks) / int64(viper.GetInt(recordSizeFlag)) - block = int64(nextTotalBlocks) - (record * int64(viper.GetInt(recordSizeFlag))) - - if block < 0 { - record-- - block = int64(viper.GetInt(recordSizeFlag)) - 1 - } else if block >= int64(viper.GetInt(recordSizeFlag)) { - record++ - block = 0 - } - - // Seek to record and block - if _, err := f.Seek(int64((viper.GetInt(recordSizeFlag)*controllers.BlockSize*int(record))+int(block)*controllers.BlockSize), io.SeekStart); err != nil { - return err - } - - tr = tar.NewReader(f) - - hdr, err = tr.Next() - if err != nil { - if err == io.EOF { - // EOF - - break - } - - continue - } - - break - } - } - - if hdr == nil { - // EOF - - break - } - - if record == 0 && block == 0 { - if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { + for { + hdr, err := tr.Next() + if err != nil { + for { + curr, err := f.Seek(0, io.SeekCurrent) + if err != nil { return err } - } - if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(record, block, hdr)); err != nil { - return err - } + nextTotalBlocks := math.Ceil(float64((curr)) / float64(controllers.BlockSize)) + record = int64(nextTotalBlocks) / int64(recordSize) + block = int64(nextTotalBlocks) - (record * int64(recordSize)) - curr, err := f.Seek(0, io.SeekCurrent) - if err != nil { - return err - } + if block < 0 { + record-- + block = int64(recordSize) - 1 + } else if block >= int64(recordSize) { + record++ + block = 0 + } - if _, err := io.Copy(ioutil.Discard, tr); err != nil { - return err - } + // Seek to record and block + if _, err := f.Seek(int64((recordSize*controllers.BlockSize*int(record))+int(block)*controllers.BlockSize), io.SeekStart); err != nil { + return err + } - currAndSize, err := f.Seek(0, io.SeekCurrent) - if err != nil { - return err - } + tr = tar.NewReader(f) - nextTotalBlocks := math.Ceil(float64(curr+(currAndSize-curr)) / float64(controllers.BlockSize)) - record = int64(nextTotalBlocks) / int64(viper.GetInt(recordSizeFlag)) - block = int64(nextTotalBlocks) - (record * int64(viper.GetInt(recordSizeFlag))) - - if block > int64(viper.GetInt(recordSizeFlag)) { - record++ - block = 0 - } - } - } else { - // Seek to record - if err := controllers.SeekToRecordOnTape(f, int32(viper.GetInt(recordFlag))); err != nil { - return err - } - - // Seek to block - br := bufio.NewReaderSize(f, controllers.BlockSize*viper.GetInt(recordSizeFlag)) - if _, err := br.Read(make([]byte, viper.GetInt(blockFlag)*controllers.BlockSize)); err != nil { - return err - } - - record := viper.GetInt64(recordFlag) - block := viper.GetInt64(blockFlag) - - curr := int64((viper.GetInt(recordSizeFlag) * controllers.BlockSize * viper.GetInt(recordFlag)) + (viper.GetInt(blockFlag) * controllers.BlockSize)) - counter := &counters.CounterReader{Reader: br, BytesRead: int(curr)} - - tr := tar.NewReader(counter) - for { - hdr, err := tr.Next() - if err != nil { - if err == io.EOF { - if err := controllers.GoToNextFileOnTape(f); err != nil { - // EOD + hdr, err = tr.Next() + if err != nil { + if err == io.EOF { + // EOF break } - record, err = controllers.GetCurrentRecordFromTape(f) - if err != nil { - return err - } - block = 0 - - br = bufio.NewReaderSize(f, controllers.BlockSize*viper.GetInt(recordSizeFlag)) - curr := int64(int64(viper.GetInt(recordSizeFlag)) * controllers.BlockSize * record) - counter := &counters.CounterReader{Reader: br, BytesRead: int(curr)} - tr = tar.NewReader(counter) - continue - } else { - return err } - } - if record == 0 && block == 0 { - if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { - return err - } - } - - if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(record, block, hdr)); err != nil { - return err - } - - if _, err := io.Copy(ioutil.Discard, tr); err != nil { - return err - } - - currAndSize := int64(counter.BytesRead) - - nextTotalBlocks := math.Ceil(float64(curr+(currAndSize-curr)) / float64(controllers.BlockSize)) - record = int64(nextTotalBlocks) / int64(viper.GetInt(recordSizeFlag)) - block = int64(nextTotalBlocks) - (record * int64(viper.GetInt(recordSizeFlag))) - - if block > int64(viper.GetInt(recordSizeFlag)) { - record++ - block = 0 + break } } + + if hdr == nil { + // EOF + + break + } + + if err := decryptHeader(hdr, encryptionFormat, privkey); err != nil { + return err + } + + if record == 0 && block == 0 { + if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { + return err + } + } + + if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(record, block, hdr)); err != nil { + return err + } + + curr, err := f.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + + if _, err := io.Copy(ioutil.Discard, tr); err != nil { + return err + } + + currAndSize, err := f.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + + nextTotalBlocks := math.Ceil(float64(curr+(currAndSize-curr)) / float64(controllers.BlockSize)) + record = int64(nextTotalBlocks) / int64(recordSize) + block = int64(nextTotalBlocks) - (record * int64(recordSize)) + + if block > int64(recordSize) { + record++ + block = 0 + } + } + } else { + // Seek to record + if err := controllers.SeekToRecordOnTape(f, int32(record)); err != nil { + return err } - return nil - }, + // Seek to block + br := bufio.NewReaderSize(f, controllers.BlockSize*recordSize) + if _, err := br.Read(make([]byte, block*controllers.BlockSize)); err != nil { + return err + } + + record := int64(record) + block := int64(block) + + curr := int64((recordSize * controllers.BlockSize * int(record)) + (int(block) * controllers.BlockSize)) + counter := &counters.CounterReader{Reader: br, BytesRead: int(curr)} + + tr := tar.NewReader(counter) + for { + hdr, err := tr.Next() + if err != nil { + if err == io.EOF { + if err := controllers.GoToNextFileOnTape(f); err != nil { + // EOD + + break + } + + record, err = controllers.GetCurrentRecordFromTape(f) + if err != nil { + return err + } + block = 0 + + br = bufio.NewReaderSize(f, controllers.BlockSize*recordSize) + curr := int64(int64(recordSize) * controllers.BlockSize * record) + counter := &counters.CounterReader{Reader: br, BytesRead: int(curr)} + tr = tar.NewReader(counter) + + continue + } else { + return err + } + } + + if err := decryptHeader(hdr, encryptionFormat, privkey); err != nil { + return err + } + + if record == 0 && block == 0 { + if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { + return err + } + } + + if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(record, block, hdr)); err != nil { + return err + } + + if _, err := io.Copy(ioutil.Discard, tr); err != nil { + return err + } + + currAndSize := int64(counter.BytesRead) + + nextTotalBlocks := math.Ceil(float64(curr+(currAndSize-curr)) / float64(controllers.BlockSize)) + record = int64(nextTotalBlocks) / int64(recordSize) + block = int64(nextTotalBlocks) - (record * int64(recordSize)) + + if block > int64(recordSize) { + record++ + block = 0 + } + } + } + + return nil } func init() { recoveryQueryCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") - recoveryQueryCmd.PersistentFlags().IntP(recordFlag, "r", 0, "Record to seek too before counting") + recoveryQueryCmd.PersistentFlags().IntP(recordFlag, "k", 0, "Record to seek too before counting") recoveryQueryCmd.PersistentFlags().IntP(blockFlag, "b", 0, "Block in record to seek too before counting") + recoveryQueryCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/restore.go b/cmd/stbak/cmd/restore.go index 7a3a744..bb6f2f4 100644 --- a/cmd/stbak/cmd/restore.go +++ b/cmd/stbak/cmd/restore.go @@ -33,8 +33,8 @@ var restoreCmd = &cobra.Command{ } if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - if _, err := os.Stat(viper.GetString(keyFlag)); err != nil { - return errKeyNotAccessible + if _, err := os.Stat(viper.GetString(identityFlag)); err != nil { + return errIdentityNotAccessible } } @@ -56,7 +56,7 @@ var restoreCmd = &cobra.Command{ privkey := []byte{} if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - p, err := ioutil.ReadFile(viper.GetString(keyFlag)) + p, err := ioutil.ReadFile(viper.GetString(identityFlag)) if err != nil { return err } @@ -145,7 +145,7 @@ func init() { restoreCmd.PersistentFlags().StringP(srcFlag, "s", "", "File or directory to restore") restoreCmd.PersistentFlags().StringP(dstFlag, "d", "", "File or directory restore to (archived name by default)") restoreCmd.PersistentFlags().BoolP(flattenFlag, "f", false, "Ignore the folder hierarchy on the tape or tar file") - restoreCmd.PersistentFlags().StringP(keyFlag, "k", "", "Path to private key of recipient that has been encrypted for") + restoreCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") viper.AutomaticEnv() diff --git a/cmd/stbak/cmd/update.go b/cmd/stbak/cmd/update.go index ea9526f..31148b0 100644 --- a/cmd/stbak/cmd/update.go +++ b/cmd/stbak/cmd/update.go @@ -36,8 +36,12 @@ var updateCmd = &cobra.Command{ } if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - if _, err := os.Stat(viper.GetString(keyFlag)); err != nil { - return errKeyNotAccessible + if _, err := os.Stat(viper.GetString(recipientFlag)); err != nil { + return errRecipientNotAccessible + } + + if _, err := os.Stat(viper.GetString(identityFlag)); err != nil { + return errIdentityNotAccessible } } @@ -63,13 +67,19 @@ var updateCmd = &cobra.Command{ } pubkey := []byte{} + privkey := []byte{} if viper.GetString(encryptionFlag) != encryptionFormatNoneKey { - p, err := ioutil.ReadFile(viper.GetString(keyFlag)) + p, err := ioutil.ReadFile(viper.GetString(recipientFlag)) if err != nil { return err } pubkey = p + + privkey, err = ioutil.ReadFile(viper.GetString(identityFlag)) + if err != nil { + return err + } } if err := update( @@ -94,6 +104,7 @@ var updateCmd = &cobra.Command{ false, viper.GetString(compressionFlag), viper.GetString(encryptionFlag), + privkey, ) }, } @@ -226,6 +237,10 @@ func update( return err } + if err := encryptHeader(hdr, encryptionFormat, pubkey); err != nil { + return err + } + if err := tw.WriteHeader(hdr); err != nil { return err } @@ -289,6 +304,10 @@ func update( return err } + if err := encryptHeader(hdr, encryptionFormat, pubkey); err != nil { + return err + } + if err := tw.WriteHeader(hdr); err != nil { return err } @@ -305,7 +324,8 @@ func init() { updateCmd.PersistentFlags().StringP(srcFlag, "s", "", "Path of the file or directory to update") updateCmd.PersistentFlags().BoolP(overwriteFlag, "o", false, "Replace the content on the tape or tar file") updateCmd.PersistentFlags().StringP(compressionLevelFlag, "l", compressionLevelBalanced, fmt.Sprintf("Compression level to use (default %v, available are %v)", compressionLevelBalanced, knownCompressionLevels)) - updateCmd.PersistentFlags().StringP(keyFlag, "k", "", "Path to public key of recipient to encrypt for") + updateCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to public key of recipient to encrypt for") + updateCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") viper.AutomaticEnv()