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  }