storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bitrot.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "crypto/sha256" 22 "encoding/hex" 23 "errors" 24 "fmt" 25 "hash" 26 "io" 27 28 "github.com/minio/highwayhash" 29 "golang.org/x/crypto/blake2b" 30 31 "storj.io/minio/cmd/logger" 32 ) 33 34 // magic HH-256 key as HH-256 hash of the first 100 decimals of π as utf-8 string with a zero key. 35 var magicHighwayHash256Key = []byte("\x4b\xe7\x34\xfa\x8e\x23\x8a\xcd\x26\x3e\x83\xe6\xbb\x96\x85\x52\x04\x0f\x93\x5d\xa3\x9f\x44\x14\x97\xe0\x9d\x13\x22\xde\x36\xa0") 36 37 var bitrotAlgorithms = map[BitrotAlgorithm]string{ 38 SHA256: "sha256", 39 BLAKE2b512: "blake2b", 40 HighwayHash256: "highwayhash256", 41 HighwayHash256S: "highwayhash256S", 42 } 43 44 // New returns a new hash.Hash calculating the given bitrot algorithm. 45 func (a BitrotAlgorithm) New() hash.Hash { 46 switch a { 47 case SHA256: 48 return sha256.New() 49 case BLAKE2b512: 50 b2, _ := blake2b.New512(nil) // New512 never returns an error if the key is nil 51 return b2 52 case HighwayHash256: 53 hh, _ := highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit 54 return hh 55 case HighwayHash256S: 56 hh, _ := highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit 57 return hh 58 default: 59 logger.CriticalIf(GlobalContext, errors.New("Unsupported bitrot algorithm")) 60 return nil 61 } 62 } 63 64 // Available reports whether the given algorithm is available. 65 func (a BitrotAlgorithm) Available() bool { 66 _, ok := bitrotAlgorithms[a] 67 return ok 68 } 69 70 // String returns the string identifier for a given bitrot algorithm. 71 // If the algorithm is not supported String panics. 72 func (a BitrotAlgorithm) String() string { 73 name, ok := bitrotAlgorithms[a] 74 if !ok { 75 logger.CriticalIf(GlobalContext, errors.New("Unsupported bitrot algorithm")) 76 } 77 return name 78 } 79 80 // NewBitrotVerifier returns a new BitrotVerifier implementing the given algorithm. 81 func NewBitrotVerifier(algorithm BitrotAlgorithm, checksum []byte) *BitrotVerifier { 82 return &BitrotVerifier{algorithm, checksum} 83 } 84 85 // BitrotVerifier can be used to verify protected data. 86 type BitrotVerifier struct { 87 algorithm BitrotAlgorithm 88 sum []byte 89 } 90 91 // BitrotAlgorithmFromString returns a bitrot algorithm from the given string representation. 92 // It returns 0 if the string representation does not match any supported algorithm. 93 // The zero value of a bitrot algorithm is never supported. 94 func BitrotAlgorithmFromString(s string) (a BitrotAlgorithm) { 95 for alg, name := range bitrotAlgorithms { 96 if name == s { 97 return alg 98 } 99 } 100 return 101 } 102 103 func newBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64, heal bool) io.Writer { 104 if algo == HighwayHash256S { 105 return newStreamingBitrotWriter(disk, volume, filePath, length, algo, shardSize, heal) 106 } 107 return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize) 108 } 109 110 func newBitrotReader(disk StorageAPI, data []byte, bucket string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) io.ReaderAt { 111 if algo == HighwayHash256S { 112 return newStreamingBitrotReader(disk, data, bucket, filePath, tillOffset, algo, shardSize) 113 } 114 return newWholeBitrotReader(disk, bucket, filePath, algo, tillOffset, sum) 115 } 116 117 // Close all the readers. 118 func closeBitrotReaders(rs []io.ReaderAt) { 119 for _, r := range rs { 120 if br, ok := r.(io.Closer); ok { 121 br.Close() 122 } 123 } 124 } 125 126 // Close all the writers. 127 func closeBitrotWriters(ws []io.Writer) { 128 for _, w := range ws { 129 if bw, ok := w.(io.Closer); ok { 130 bw.Close() 131 } 132 } 133 } 134 135 // Returns hash sum for whole-bitrot, nil for streaming-bitrot. 136 func bitrotWriterSum(w io.Writer) []byte { 137 if bw, ok := w.(*wholeBitrotWriter); ok { 138 return bw.Sum(nil) 139 } 140 return nil 141 } 142 143 // Returns the size of the file with bitrot protection 144 func bitrotShardFileSize(size int64, shardSize int64, algo BitrotAlgorithm) int64 { 145 if algo != HighwayHash256S { 146 return size 147 } 148 return ceilFrac(size, shardSize)*int64(algo.New().Size()) + size 149 } 150 151 // bitrotVerify a single stream of data. 152 func bitrotVerify(r io.Reader, wantSize, partSize int64, algo BitrotAlgorithm, want []byte, shardSize int64) error { 153 if algo != HighwayHash256S { 154 h := algo.New() 155 if n, err := io.Copy(h, r); err != nil || n != wantSize { 156 // Premature failure in reading the object, file is corrupt. 157 return errFileCorrupt 158 } 159 if !bytes.Equal(h.Sum(nil), want) { 160 return errFileCorrupt 161 } 162 return nil 163 } 164 165 h := algo.New() 166 hashBuf := make([]byte, h.Size()) 167 buf := make([]byte, shardSize) 168 left := wantSize 169 170 // Calculate the size of the bitrot file and compare 171 // it with the actual file size. 172 if left != bitrotShardFileSize(partSize, shardSize, algo) { 173 return errFileCorrupt 174 } 175 176 for left > 0 { 177 // Read expected hash... 178 h.Reset() 179 n, err := io.ReadFull(r, hashBuf) 180 if err != nil { 181 // Read's failed for object with right size, file is corrupt. 182 return err 183 } 184 // Subtract hash length.. 185 left -= int64(n) 186 if left < shardSize { 187 shardSize = left 188 } 189 read, err := io.CopyBuffer(h, io.LimitReader(r, shardSize), buf) 190 if err != nil { 191 // Read's failed for object with right size, at different offsets. 192 return err 193 } 194 left -= read 195 if !bytes.Equal(h.Sum(nil), hashBuf) { 196 return errFileCorrupt 197 } 198 } 199 return nil 200 } 201 202 // bitrotSelfTest performs a self-test to ensure that bitrot 203 // algorithms compute correct checksums. If any algorithm 204 // produces an incorrect checksum it fails with a hard error. 205 // 206 // bitrotSelfTest tries to catch any issue in the bitrot implementation 207 // early instead of silently corrupting data. 208 func bitrotSelfTest() { 209 var checksums = map[BitrotAlgorithm]string{ 210 SHA256: "a7677ff19e0182e4d52e3a3db727804abc82a5818749336369552e54b838b004", 211 BLAKE2b512: "e519b7d84b1c3c917985f544773a35cf265dcab10948be3550320d156bab612124a5ae2ae5a8c73c0eea360f68b0e28136f26e858756dbfe7375a7389f26c669", 212 HighwayHash256: "39c0407ed3f01b18d22c85db4aeff11e060ca5f43131b0126731ca197cd42313", 213 HighwayHash256S: "39c0407ed3f01b18d22c85db4aeff11e060ca5f43131b0126731ca197cd42313", 214 } 215 for algorithm := range bitrotAlgorithms { 216 if !algorithm.Available() { 217 continue 218 } 219 220 checksum, err := hex.DecodeString(checksums[algorithm]) 221 if err != nil { 222 logger.Fatal(errSelfTestFailure, fmt.Sprintf("bitrot: failed to decode %v checksum %s for selftest: %v", algorithm, checksums[algorithm], err)) 223 } 224 var ( 225 hash = algorithm.New() 226 msg = make([]byte, 0, hash.Size()*hash.BlockSize()) 227 sum = make([]byte, 0, hash.Size()) 228 ) 229 for i := 0; i < hash.Size()*hash.BlockSize(); i += hash.Size() { 230 hash.Write(msg) 231 sum = hash.Sum(sum[:0]) 232 msg = append(msg, sum...) 233 hash.Reset() 234 } 235 if !bytes.Equal(sum, checksum) { 236 logger.Fatal(errSelfTestFailure, fmt.Sprintf("bitrot: %v selftest checksum mismatch: got %x - want %x", algorithm, sum, checksum)) 237 } 238 } 239 }