1package commands 2 3import ( 4 "crypto/rand" 5 "encoding/json" 6 "errors" 7 "flag" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/jesseduffield/horcrux/pkg/multiplexing" 17 "github.com/jesseduffield/horcrux/pkg/shamir" 18) 19 20func SplitWithPrompt(path string) error { 21 total, threshold, err := obtainTotalAndThreshold() 22 if err != nil { 23 return err 24 } 25 26 return Split(path, path, total, threshold) 27} 28 29func Split(path string, destination string, total int, threshold int) error { 30 key, err := generateKey() 31 if err != nil { 32 return err 33 } 34 35 keyFragments, err := shamir.Split(key, total, threshold) 36 if err != nil { 37 return err 38 } 39 40 timestamp := time.Now().Unix() 41 42 file, err := os.Open(path) 43 if err != nil { 44 return err 45 } 46 originalFilename := filepath.Base(path) 47 48 // create destination directory if it does not already exist. 49 stat, err := os.Stat(destination) 50 if err != nil { 51 if !os.IsNotExist(err) { 52 return err 53 } 54 if err := os.MkdirAll(destination, os.ModePerm); err != nil { 55 return err 56 } 57 } else { 58 if !stat.IsDir() { 59 return errors.New("Destination must be a directory") 60 } 61 } 62 63 horcruxFiles := make([]*os.File, total) 64 for i := range horcruxFiles { 65 index := i + 1 66 67 headerBytes, err := json.Marshal(&HorcruxHeader{ 68 OriginalFilename: originalFilename, 69 Timestamp: timestamp, 70 Index: index, 71 Total: total, 72 KeyFragment: keyFragments[i], 73 Threshold: threshold, 74 }) 75 if err != nil { 76 return err 77 } 78 79 originalFilenameWithoutExt := strings.TrimSuffix(originalFilename, filepath.Ext(originalFilename)) 80 horcruxFilename := fmt.Sprintf("%s_%d_of_%d.horcrux", originalFilenameWithoutExt, index, total) 81 horcruxPath := filepath.Join(destination, horcruxFilename) 82 fmt.Printf("creating %s\n", horcruxPath) 83 84 // clearing file in case it already existed 85 _ = os.Truncate(horcruxPath, 0) 86 87 horcruxFile, err := os.OpenFile(horcruxPath, os.O_WRONLY|os.O_CREATE, 0644) 88 if err != nil { 89 return err 90 } 91 defer horcruxFile.Close() 92 93 if _, err := horcruxFile.WriteString(header(index, total, headerBytes)); err != nil { 94 return err 95 } 96 97 horcruxFiles[i] = horcruxFile 98 } 99 100 // wrap file reader in an encryption stream 101 var fileReader io.Reader = file 102 reader := cryptoReader(fileReader, key) 103 104 var writer io.Writer 105 if threshold == total { 106 // because we need all horcruxes to reconstitute the original file, 107 // we'll use a multiplexer to divide the encrypted content evenly between 108 // the horcruxes 109 writer = &multiplexing.Demultiplexer{Writers: horcruxFiles} 110 } else { 111 writers := make([]io.Writer, len(horcruxFiles)) 112 for i := range writers { 113 writers[i] = horcruxFiles[i] 114 } 115 116 writer = io.MultiWriter(writers...) 117 } 118 119 _, err = io.Copy(writer, reader) 120 if err != nil { 121 return err 122 } 123 124 fmt.Println("Done!") 125 126 return nil 127} 128 129func obtainTotalAndThreshold() (int, int, error) { 130 totalPtr := flag.Int("n", 0, "number of horcruxes to make") 131 thresholdPtr := flag.Int("t", 0, "number of horcruxes required to resurrect the original file") 132 flag.Parse() 133 134 total := *totalPtr 135 threshold := *thresholdPtr 136 137 if total == 0 { 138 totalStr := Prompt("How many horcruxes do you want to split this file into? (2-99): ") 139 var err error 140 total, err = strconv.Atoi(totalStr) 141 if err != nil { 142 return 0, 0, err 143 } 144 } 145 146 if threshold == 0 { 147 thresholdStr := Prompt("How many horcruxes should be required to reconstitute the original file? If you require all horcruxes, the resulting files will take up less space, but it will feel less magical (2-99): ") 148 var err error 149 threshold, err = strconv.Atoi(thresholdStr) 150 if err != nil { 151 return 0, 0, err 152 } 153 } 154 155 return total, threshold, nil 156} 157 158func header(index int, total int, headerBytes []byte) string { 159 return fmt.Sprintf(`# THIS FILE IS A HORCRUX. 160# IT IS ONE OF %d HORCRUXES THAT EACH CONTAIN PART OF AN ORIGINAL FILE. 161# THIS IS HORCRUX NUMBER %d. 162# IN ORDER TO RESURRECT THIS ORIGINAL FILE YOU MUST FIND THE OTHER %d HORCRUX(ES) AND THEN BIND THEM USING THE PROGRAM FOUND AT THE FOLLOWING URL 163# https://github.com/jesseduffield/horcrux 164 165-- HEADER -- 166%s 167-- BODY -- 168`, total, index, total-1, headerBytes) 169} 170 171func generateKey() ([]byte, error) { 172 key := make([]byte, 32) 173 _, err := rand.Read(key) 174 return key, err 175} 176