github.com/minio/madmin-go/v2@v2.2.1/estream/writer.go (about)

     1  //
     2  // Copyright (c) 2015-2022 MinIO, Inc.
     3  //
     4  // This file is part of MinIO Object Storage stack
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Affero General Public License as
     8  // published by the Free Software Foundation, either version 3 of the
     9  // License, or (at your option) any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU Affero General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Affero General Public License
    17  // along with this program. If not, see <http://www.gnu.org/licenses/>.
    18  //
    19  
    20  package estream
    21  
    22  import (
    23  	"bytes"
    24  	crand "crypto/rand"
    25  	"crypto/rsa"
    26  	"crypto/sha512"
    27  	"crypto/x509"
    28  	"encoding/binary"
    29  	"errors"
    30  	"io"
    31  	"math"
    32  
    33  	"github.com/cespare/xxhash/v2"
    34  	"github.com/secure-io/sio-go"
    35  	"github.com/tinylib/msgp/msgp"
    36  )
    37  
    38  // Writer provides a stream writer.
    39  // Streams can optionally be encrypted.
    40  // All streams have checksum verification.
    41  type Writer struct {
    42  	up    io.Writer
    43  	err   error
    44  	key   *[32]byte
    45  	bw    blockWriter
    46  	nonce uint64
    47  }
    48  
    49  const (
    50  	writerMajorVersion = 2
    51  	writerMinorVersion = 1
    52  )
    53  
    54  // NewWriter will return a writer that allows to add encrypted and non-encrypted data streams.
    55  func NewWriter(w io.Writer) *Writer {
    56  	_, err := w.Write([]byte{writerMajorVersion, writerMinorVersion})
    57  	writer := &Writer{err: err, up: w}
    58  	writer.bw.init(w)
    59  	return writer
    60  }
    61  
    62  // Close will flush and close the output stream.
    63  func (w *Writer) Close() error {
    64  	if w.err != nil {
    65  		return w.err
    66  	}
    67  	w.addBlock(blockEOF)
    68  	return w.sendBlock()
    69  }
    70  
    71  // AddKeyEncrypted will create a new encryption key and add it to the stream.
    72  // The key will be encrypted with the public key provided.
    73  // All following files will be encrypted with this key.
    74  func (w *Writer) AddKeyEncrypted(publicKey *rsa.PublicKey) error {
    75  	if w.err != nil {
    76  		return w.err
    77  	}
    78  	var key [32]byte
    79  	_, err := io.ReadFull(crand.Reader, key[:])
    80  	if err != nil {
    81  		return w.setErr(err)
    82  	}
    83  	w.key = &key
    84  	cipherKey, err := rsa.EncryptOAEP(sha512.New(), crand.Reader, publicKey, key[:], nil)
    85  	if err != nil {
    86  		return w.setErr(err)
    87  	}
    88  
    89  	mw := w.addBlock(blockEncryptedKey)
    90  
    91  	// Write public key...
    92  	if err := mw.WriteBytes(x509.MarshalPKCS1PublicKey(publicKey)); err != nil {
    93  		return w.setErr(err)
    94  	}
    95  
    96  	// Write encrypted cipher key
    97  	w.setErr(mw.WriteBytes(cipherKey))
    98  	return w.sendBlock()
    99  }
   100  
   101  // AddKeyPlain will create a new encryption key and add it to the stream.
   102  // The key will be stored without any encryption.
   103  // All calls to AddEncryptedStream will use this key
   104  func (w *Writer) AddKeyPlain() error {
   105  	if w.err != nil {
   106  		return w.err
   107  	}
   108  	var key [32]byte
   109  	_, err := io.ReadFull(crand.Reader, key[:])
   110  	if err != nil {
   111  		return w.setErr(err)
   112  	}
   113  	w.key = &key
   114  
   115  	mw := w.addBlock(blockPlainKey)
   116  	w.setErr(mw.WriteBytes(key[:]))
   117  
   118  	return w.sendBlock()
   119  }
   120  
   121  // AddError will indicate the writer encountered an error
   122  // and the reader should abort the stream.
   123  // The message will be returned as an error.
   124  func (w *Writer) AddError(msg string) error {
   125  	if w.err != nil {
   126  		return w.err
   127  	}
   128  	mw := w.addBlock(blockError)
   129  	w.setErr(mw.WriteString(msg))
   130  	return w.sendBlock()
   131  }
   132  
   133  // AddUnencryptedStream adds a named stream.
   134  // Extra data can be added, which is added without encryption or checksums.
   135  func (w *Writer) AddUnencryptedStream(name string, extra []byte) (io.WriteCloser, error) {
   136  	if w.err != nil {
   137  		return nil, w.err
   138  	}
   139  
   140  	mw := w.addBlock(blockPlainStream)
   141  
   142  	// Write metadata...
   143  	w.setErr(mw.WriteString(name))
   144  	w.setErr(mw.WriteBytes(extra))
   145  	w.setErr(mw.WriteUint8(uint8(checksumTypeXxhash)))
   146  	if err := w.sendBlock(); err != nil {
   147  		return nil, err
   148  	}
   149  	return w.newStreamWriter(), nil
   150  }
   151  
   152  // AddEncryptedStream adds a named encrypted stream.
   153  // AddKeyEncrypted must have been called before this, but
   154  // multiple streams can safely use the same key.
   155  // Extra data can be added, which is added without encryption or checksums.
   156  func (w *Writer) AddEncryptedStream(name string, extra []byte) (io.WriteCloser, error) {
   157  	if w.err != nil {
   158  		return nil, w.err
   159  	}
   160  
   161  	if w.key == nil {
   162  		return nil, errors.New("AddEncryptedStream: No key on stream")
   163  	}
   164  	mw := w.addBlock(blockEncStream)
   165  
   166  	// Write metadata...
   167  	w.setErr(mw.WriteString(name))
   168  	w.setErr(mw.WriteBytes(extra))
   169  	w.setErr(mw.WriteUint8(uint8(checksumTypeXxhash)))
   170  
   171  	stream, err := sio.AES_256_GCM.Stream(w.key[:])
   172  	if err != nil {
   173  		return nil, w.setErr(err)
   174  	}
   175  
   176  	// Get nonce for stream.
   177  	nonce := make([]byte, stream.NonceSize())
   178  	binary.LittleEndian.PutUint64(nonce, w.nonce)
   179  	w.nonce++
   180  
   181  	// Write nonce as bin array.
   182  	w.setErr(mw.WriteBytes(nonce))
   183  
   184  	if err := w.sendBlock(); err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	// Send output as blocks.
   189  	sw := w.newStreamWriter()
   190  	encw := stream.EncryptWriter(sw, nonce, nil)
   191  
   192  	return &closeWrapper{
   193  		up: encw,
   194  		after: func() error {
   195  			return sw.Close()
   196  		},
   197  	}, nil
   198  }
   199  
   200  // addBlock initializes a new block.
   201  // Block content should be written to the returned writer.
   202  // When done call sendBlock.
   203  func (w *Writer) addBlock(id blockID) *msgp.Writer {
   204  	return w.bw.newBlock(id)
   205  }
   206  
   207  // sendBlock sends the queued block.
   208  func (w *Writer) sendBlock() error {
   209  	if w.err != nil {
   210  		return w.err
   211  	}
   212  	return w.setErr(w.bw.send())
   213  }
   214  
   215  // newStreamWriter creates a new stream writer
   216  func (w *Writer) newStreamWriter() *streamWriter {
   217  	sw := &streamWriter{w: w}
   218  	sw.h.Reset()
   219  	return sw
   220  }
   221  
   222  // setErr will set a stateful error on w.
   223  // If an error has already been set that is returned instead.
   224  func (w *Writer) setErr(err error) error {
   225  	if w.err != nil {
   226  		return w.err
   227  	}
   228  	if err == nil {
   229  		return err
   230  	}
   231  	w.err = err
   232  	return err
   233  }
   234  
   235  // streamWriter will send each individual write as a block on stream.
   236  // Close must be called when writes have completed to send hashes.
   237  type streamWriter struct {
   238  	w          *Writer
   239  	h          xxhash.Digest
   240  	eosWritten bool
   241  }
   242  
   243  // Write satisfies the io.Writer interface.
   244  // Each write is sent as a separate block.
   245  func (w *streamWriter) Write(b []byte) (int, error) {
   246  	mw := w.w.addBlock(blockDatablock)
   247  
   248  	// Update hash.
   249  	w.h.Write(b)
   250  
   251  	// Write as messagepack bin array.
   252  	if err := mw.WriteBytes(b); err != nil {
   253  		return 0, w.w.setErr(err)
   254  	}
   255  	// Write data as binary array.
   256  	return len(b), w.w.sendBlock()
   257  }
   258  
   259  // Close satisfies the io.Closer interface.
   260  func (w *streamWriter) Close() error {
   261  	// Write EOS only once.
   262  	if !w.eosWritten {
   263  		mw := w.w.addBlock(blockEOS)
   264  		sum := w.h.Sum(nil)
   265  		w.w.setErr(mw.WriteBytes(sum))
   266  		w.eosWritten = true
   267  		return w.w.sendBlock()
   268  	}
   269  	return nil
   270  }
   271  
   272  type closeWrapper struct {
   273  	before, after func() error
   274  	up            io.WriteCloser
   275  }
   276  
   277  func (w *closeWrapper) Write(b []byte) (int, error) {
   278  	return w.up.Write(b)
   279  }
   280  
   281  // Close satisfies the io.Closer interface.
   282  func (w *closeWrapper) Close() error {
   283  	if w.before != nil {
   284  		if err := w.before(); err != nil {
   285  			return err
   286  		}
   287  		w.before = nil
   288  	}
   289  	if w.up != nil {
   290  		if err := w.up.Close(); err != nil {
   291  			return err
   292  		}
   293  		w.up = nil
   294  	}
   295  	if w.after != nil {
   296  		if err := w.after(); err != nil {
   297  			return err
   298  		}
   299  		w.after = nil
   300  	}
   301  	return nil
   302  }
   303  
   304  type blockWriter struct {
   305  	id  blockID
   306  	w   io.Writer
   307  	wr  *msgp.Writer
   308  	buf bytes.Buffer
   309  	hdr [8 + 5]byte
   310  }
   311  
   312  // init the blockwriter
   313  // blocks will be written to w.
   314  func (b *blockWriter) init(w io.Writer) {
   315  	b.w = w
   316  	b.buf.Grow(1 << 10)
   317  	b.buf.Reset()
   318  	b.wr = msgp.NewWriter(&b.buf)
   319  }
   320  
   321  // newBlock starts a new block with the specified id.
   322  // Content should be written to the returned writer.
   323  func (b *blockWriter) newBlock(id blockID) *msgp.Writer {
   324  	b.id = id
   325  	b.buf.Reset()
   326  	b.wr.Reset(&b.buf)
   327  	return b.wr
   328  }
   329  
   330  func (b *blockWriter) send() error {
   331  	if b.id == 0 {
   332  		return errors.New("blockWriter: no block started")
   333  	}
   334  
   335  	// Flush block data into b.buf
   336  	if err := b.wr.Flush(); err != nil {
   337  		return err
   338  	}
   339  	// Add block id
   340  	hdr := msgp.AppendInt8(b.hdr[:0], int8(b.id))
   341  	if uint32(b.buf.Len()) > math.MaxUint32 {
   342  		return errors.New("max block size exceeded")
   343  	}
   344  	// Add block length.
   345  	hdr = msgp.AppendUint32(hdr, uint32(b.buf.Len()))
   346  	if _, err := b.w.Write(hdr); err != nil {
   347  		return err
   348  	}
   349  	// Write block.
   350  	_, err := b.w.Write(b.buf.Bytes())
   351  
   352  	// Reset for new block.
   353  	b.buf.Reset()
   354  	b.id = 0
   355  	return err
   356  }