github.com/minio/madmin-go/v3@v3.0.51/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 }