From b312f134736a573ab2c3a5440e86a141d85ec436 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 17 Feb 2025 09:09:42 -0800 Subject: [PATCH] Extract all files from encrypted stream with inspect (#20937) Allow multiple private keys and extract all files from streams. Place files in the folder with `.enc` removed. Do basic checks so streams cannot traverse outside of the folder. --- docs/debugging/inspect/decrypt-v2.go | 53 +++++++++++----------- docs/debugging/inspect/main.go | 67 ++++++++++++++++++---------- 2 files changed, 70 insertions(+), 50 deletions(-) diff --git a/docs/debugging/inspect/decrypt-v2.go b/docs/debugging/inspect/decrypt-v2.go index a38aae4a5..13da6c3cf 100644 --- a/docs/debugging/inspect/decrypt-v2.go +++ b/docs/debugging/inspect/decrypt-v2.go @@ -22,6 +22,9 @@ import ( "fmt" "io" "os" + "path/filepath" + "strings" + "unicode/utf8" "github.com/minio/madmin-go/v3/estream" ) @@ -30,18 +33,18 @@ type keepFileErr struct { error } -func extractInspectV2(pk []byte, r io.Reader, w io.Writer, okMsg string) error { - privKey, err := bytesToPrivateKey(pk) - if err != nil { - return fmt.Errorf("decoding key returned: %w", err) - } - +func extractInspectV2(pks [][]byte, r io.Reader, extractDir string) error { sr, err := estream.NewReader(r) if err != nil { return err } - - sr.SetPrivateKey(privKey) + for _, pk := range pks { + privKey, err := bytesToPrivateKey(pk) + if err != nil { + return fmt.Errorf("decoding key returned: %w", err) + } + sr.SetPrivateKey(privKey) + } sr.ReturnNonDecryptable(true) // Debug corrupted streams. @@ -60,33 +63,29 @@ func extractInspectV2(pk []byte, r io.Reader, w io.Writer, okMsg string) error { return errors.New("no data found on stream") } if errors.Is(err, estream.ErrNoKey) { - if stream.Name == "inspect.zip" { - return errors.New("incorrect private key") - } + fmt.Println("Skipping", stream.Name, "no private key") if err := stream.Skip(); err != nil { return fmt.Errorf("stream skip: %w", err) } continue } - if extracted { - return keepFileErr{fmt.Errorf("next stream: %w", err)} - } return fmt.Errorf("next stream: %w", err) } - if stream.Name == "inspect.zip" { - if extracted { - return keepFileErr{errors.New("multiple inspect.zip streams found")} - } - _, err := io.Copy(w, stream) - if err != nil { - return fmt.Errorf("reading inspect stream: %w", err) - } - fmt.Println(okMsg) - extracted = true - continue + if strings.Contains(stream.Name, "..") || !utf8.ValidString(stream.Name) { + return fmt.Errorf("invalid stream name: %q", stream.Name) } - if err := stream.Skip(); err != nil { - return fmt.Errorf("stream skip: %w", err) + + dst := filepath.Join(extractDir, stream.Name) + os.Mkdir(extractDir, 0o755) + w, err := os.Create(dst) + if err != nil { + return fmt.Errorf("creating output file: %w", err) } + _, err = io.Copy(w, stream) + if err != nil { + return fmt.Errorf("reading inspect stream: %w", err) + } + fmt.Printf("Extracted: %s\n", dst) + extracted = true } } diff --git a/docs/debugging/inspect/main.go b/docs/debugging/inspect/main.go index 14a6a6878..77a6b6f4b 100644 --- a/docs/debugging/inspect/main.go +++ b/docs/debugging/inspect/main.go @@ -51,15 +51,34 @@ func main() { generateKeys() os.Exit(0) } - var privateKey []byte + var privateKeys [][]byte if *keyHex == "" { - if b, err := os.ReadFile(*privKeyPath); err == nil { - privateKey = b - fmt.Println("Using private key from", *privKeyPath) + // Attempt to load private key(s) + n := 1 + var base, ext string + base = *privKeyPath + if idx := strings.LastIndexByte(base, '.'); idx != -1 { + ext = base[idx:] + base = base[:idx] + } + for { + // Automatically read "file.ext", "file-2.ext", "file-3.ext"... + fn := base + ext + if n > 1 { + fn = fmt.Sprintf("%s-%d%s", base, n, ext) + } + + if b, err := os.ReadFile(fn); err == nil { + privateKeys = append(privateKeys, b) + fmt.Println("Added private key from", fn) + } else { + break + } + n++ } // Prompt for decryption key if no --key or --private-key are provided - if len(privateKey) == 0 && !*stdin { + if len(privateKeys) == 0 && !*stdin { reader := bufio.NewReader(os.Stdin) fmt.Print("Enter Decryption Key: ") @@ -91,17 +110,20 @@ func main() { var err error inputs, err = filepathx.Glob(flag.Args()[0]) fatalErr(err) + if len(inputs) == 0 { + fmt.Println("Usage: No input found") + } default: flag.Usage() fatalIf(true, "Only 1 file can be decrypted") os.Exit(1) } for _, input := range inputs { - processFile(input, privateKey) + processFile(input, privateKeys) } } -func processFile(inputFileName string, privateKey []byte) { +func processFile(inputFileName string, privateKeys [][]byte) { // Calculate the output file name var outputFileName string switch { @@ -115,32 +137,31 @@ func processFile(inputFileName string, privateKey []byte) { outputFileName = inputFileName + ".decrypted" } - // Backup any already existing output file - _, err := os.Stat(outputFileName) - if err == nil { - err := os.Rename(outputFileName, outputFileName+"."+time.Now().Format("20060102150405")) - if err != nil { - fatalErr(err) - } - } - // Open the input and create the output file input, err := os.Open(inputFileName) fatalErr(err) defer input.Close() - output, err := os.Create(outputFileName) - fatalErr(err) // Decrypt the inspect data - msg := fmt.Sprintf("output written to %s", outputFileName) - switch { case *keyHex != "": + // Backup any already existing output file + _, err := os.Stat(outputFileName) + if err == nil { + err := os.Rename(outputFileName, outputFileName+"."+time.Now().Format("20060102150405")) + if err != nil { + fatalErr(err) + } + } + output, err := os.Create(outputFileName) + fatalErr(err) + msg := fmt.Sprintf("output written to %s", outputFileName) err = extractInspectV1(*keyHex, input, output, msg) - case len(privateKey) != 0: - err = extractInspectV2(privateKey, input, output, msg) + output.Close() + case len(privateKeys) != 0: + outputFileName := strings.TrimSuffix(outputFileName, ".zip") + err = extractInspectV2(privateKeys, input, outputFileName) } - output.Close() if err != nil { var keep keepFileErr