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