From dff173efe56eba9440de30d1e927e770ce982323 Mon Sep 17 00:00:00 2001 From: alexmullins Date: Wed, 2 Dec 2015 00:58:01 -0600 Subject: [PATCH] Add password protected writing --- crypto.go | 105 ++++++++++++++++++++++++++++++++++++++++++++++--- crypto_test.go | 51 +++++++++++++++++++++++- struct.go | 10 ++++- writer.go | 42 ++++++++++++++++++-- zipwriters.png | Bin 0 -> 20303 bytes 5 files changed, 196 insertions(+), 12 deletions(-) create mode 100644 zipwriters.png diff --git a/crypto.go b/crypto.go index 038a8be..535bbac 100644 --- a/crypto.go +++ b/crypto.go @@ -9,6 +9,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/hmac" + "crypto/rand" "crypto/sha1" "crypto/subtle" "errors" @@ -212,7 +213,7 @@ func (a *bufferedAuthReader) Read(b []byte) (int, error) { a.err = io.ErrUnexpectedEOF return 0, a.err } - ab := new(bytes.Buffer) + ab := new(bytes.Buffer) // remove this buffer and io.Copy to mac nn, err := io.Copy(ab, a.adata) if err != nil || nn != 10 { a.err = io.ErrUnexpectedEOF @@ -266,10 +267,6 @@ func newDecryptionReader(r *io.SectionReader, f *File) (io.Reader, error) { if saltLen == 0 { return nil, ErrDecryption } - // Change to a streaming implementation - // Maybe not such a good idea after all. See: - // https://www.imperialviolet.org/2014/06/27/streamingencryption.html - // https://www.imperialviolet.org/2015/05/16/aeads.html // grab the salt, pwvv, data, and authcode saltpwvv := make([]byte, saltLen+2) if _, err := r.Read(saltpwvv); err != nil { @@ -328,3 +325,101 @@ func aesKeyLen(strength byte) int { return 0 } } + +type authWriter struct { + hmac hash.Hash // from fw.hmac + w io.Writer // this will be the compCount writer +} + +func (aw *authWriter) Write(p []byte) (int, error) { + _, err := aw.hmac.Write(p) + if err != nil { + return 0, err + } + return aw.w.Write(p) +} + +// writes out the salt, pwv, and then the encrypted file data +type encryptionWriter struct { + pwv []byte // password verification code to be written + salt []byte // salt to be written + w io.Writer // where to write the salt + pwv + es io.Writer // where to write encrypted file data + first bool // first write? + err error // last error +} + +func (ew *encryptionWriter) Write(p []byte) (int, error) { + if ew.err != nil { + return 0, ew.err + } + if ew.first { + // if our first time writing + // must write out the salt and pwv first unencrypted + _, err1 := ew.w.Write(ew.salt) + _, err2 := ew.w.Write(ew.pwv) + if err1 != nil || err2 != nil { + ew.err = errors.New("zip: error writing salt or pwv") + return 0, ew.err + } + ew.first = false + } + // now just pass on to the encryption stream + return ew.es.Write(p) +} + +// newEncryptionWriter returns a io.Writer that when written to, 1. writes +// out the salt, 2. writes out pwv, 3. writes out encrypted the data, and finally +// 4. will write to hmac. +func newEncryptionWriter(w io.Writer, fh *FileHeader, fw *fileWriter) (io.Writer, error) { + var salt [16]byte + _, err := rand.Read(salt[:]) + if err != nil { + return nil, errors.New("zip: unable to generate random salt") + } + ekey, akey, pwv := generateKeys(fh.Password(), salt[:], aes256) + fw.hmac = hmac.New(sha1.New, akey) + aw := &authWriter{ + hmac: fw.hmac, + w: w, + } + es, err := encryptStream(ekey, aw) + if err != nil { + return nil, err + } + ew := &encryptionWriter{ + pwv: pwv, + salt: salt[:], + w: w, + es: es, + first: true, + } + return ew, nil +} + +func encryptStream(key []byte, w io.Writer) (io.Writer, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, errors.New("zip: couldn't create AES cipher") + } + stream := newWinZipCTR(block) + writer := &cipher.StreamWriter{S: stream, W: w} + return writer, nil +} + +func (fh *FileHeader) writeWinZipExtra() { + // total size is 11 bytes + var buf [11]byte + eb := writeBuf(buf[:]) + eb.uint16(winzipAesExtraId) + eb.uint16(7) // following data size is 7 + eb.uint16(2) // ae 2 + eb.uint16(0x4541) // "AE" + eb.uint8(3) // aes256 + eb.uint16(fh.Method) // original compression method + fh.Extra = append(fh.Extra, buf[:]...) +} + +func (fh *FileHeader) setEncryptionBit() { + fh.Flags |= 0x1 +} diff --git a/crypto_test.go b/crypto_test.go index 787cedf..5cb6cc1 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -12,7 +12,7 @@ func pwFn() []byte { } // Test simple password reading. -func TestPasswordSimple(t *testing.T) { +func TestPasswordReadSimple(t *testing.T) { file := "hello-aes.zip" var buf bytes.Buffer r, err := OpenReader(filepath.Join("testdata", file)) @@ -173,3 +173,52 @@ func TestPasswordTamperedData(t *testing.T) { } } } + +func TestPasswordWriteSimple(t *testing.T) { + contents := []byte("Hello World") + conLen := len(contents) + + // Write a zip + fh := &FileHeader{ + Name: "hello.txt", + Password: pwFn, + } + raw := new(bytes.Buffer) + zipw := NewWriter(raw) + w, err := zipw.CreateHeader(fh) + if err != nil { + t.Errorf("Expected to create a new FileHeader") + } + n, err := io.Copy(w, bytes.NewReader(contents)) + if err != nil || n != int64(conLen) { + t.Errorf("Expected to write the full contents to the writer.") + } + zipw.Close() + + // Read the zip + buf := new(bytes.Buffer) + zipr, err := NewReader(bytes.NewReader(raw.Bytes()), int64(raw.Len())) + if err != nil { + t.Errorf("Expected to open a new zip reader: %v", err) + } + nn := len(zipr.File) + if nn != 1 { + t.Errorf("Expected to have one file in the zip archive, but has %d files", nn) + } + z := zipr.File[0] + z.Password = pwFn + rr, err := z.Open() + if err != nil { + t.Errorf("Expected to open the readcloser: %v", err) + } + n, err = io.Copy(buf, rr) + if err != nil { + t.Errorf("Expected to write to temporary buffer: %v", err) + } + if n != int64(conLen) { + t.Errorf("Expected to copy %d bytes to temp buffer, but copied %d bytes instead", conLen, n) + } + if !bytes.Equal(contents, buf.Bytes()) { + t.Errorf("Expected the unzipped contents to equal '%s', but was '%s' instead", contents, buf.Bytes()) + } +} diff --git a/struct.go b/struct.go index 88c6219..64a0497 100644 --- a/struct.go +++ b/struct.go @@ -93,8 +93,14 @@ type FileHeader struct { ExternalAttrs uint32 // Meaning depends on CreatorVersion Comment string - // encryption fields - Password PasswordFn // The password to use when reading/writing + // DeferAuth determines whether hmac checks happen before + // any ciphertext is decrypted. It is recommended to leave this + // set to false. For more detail: + // https://www.imperialviolet.org/2014/06/27/streamingencryption.html + // https://www.imperialviolet.org/2015/05/16/aeads.html + DeferAuth bool + + Password PasswordFn // Returns the password to use when reading/writing ae uint16 aesStrength byte } diff --git a/writer.go b/writer.go index 3be2b5f..8812787 100644 --- a/writer.go +++ b/writer.go @@ -211,7 +211,8 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { } fh.Flags |= 0x8 // we will write a data descriptor - + // TODO(alex): Look at spec and see if these need to be changed + // when using encryption. fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte fh.ReaderVersion = zipVersion20 @@ -220,12 +221,27 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { compCount: &countWriter{w: w.cw}, crc32: crc32.NewIEEE(), } + // Get the compressor before possibly changing Method to 99 due to password comp := compressor(fh.Method) if comp == nil { return nil, ErrAlgorithm } + // check for password + var sw io.Writer = fw.compCount + if fh.Password != nil { + // we have a password and need to encrypt. + // 1. Set encryption bit in fh.Flags + fh.setEncryptionBit() + fh.writeWinZipExtra() + fh.Method = 99 // ok to change, we've gotten the comp and wrote extra + ew, err := newEncryptionWriter(sw, fh, fw) + if err != nil { + return nil, errors.New("zip: unable to create an encryption writer") + } + sw = ew + } var err error - fw.comp, err = comp(fw.compCount) + fw.comp, err = comp(sw) if err != nil { return nil, err } @@ -278,6 +294,8 @@ type fileWriter struct { compCount *countWriter crc32 hash.Hash32 closed bool + + hmac hash.Hash // possible hmac used for authentication when encrypting } func (w *fileWriter) Write(p []byte) (int, error) { @@ -296,10 +314,21 @@ func (w *fileWriter) close() error { if err := w.comp.Close(); err != nil { return err } - + // if encrypted grab the hmac and write it out + if w.header.IsEncrypted() { + authCode := w.hmac.Sum(nil) + authCode = authCode[:10] + _, err := w.compCount.Write(authCode) + if err != nil { + return errors.New("zip: error writing authcode") + } + } // update FileHeader fh := w.header.FileHeader - fh.CRC32 = w.crc32.Sum32() + // ae-2 we don't write out CRC + if !fh.IsEncrypted() { + fh.CRC32 = w.crc32.Sum32() + } fh.CompressedSize64 = uint64(w.compCount.count) fh.UncompressedSize64 = uint64(w.rawCount.count) @@ -358,6 +387,11 @@ func (w nopCloser) Close() error { type writeBuf []byte +func (b *writeBuf) uint8(v uint8) { + (*b)[0] = v + *b = (*b)[1:] +} + func (b *writeBuf) uint16(v uint16) { binary.LittleEndian.PutUint16(*b, v) *b = (*b)[2:] diff --git a/zipwriters.png b/zipwriters.png new file mode 100644 index 0000000000000000000000000000000000000000..54c409e3e09d8051ff010e282680348cccd9e269 GIT binary patch literal 20303 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 literal 0 HcmV?d00001