| // Copyright 2011 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package packet | |
| import ( | |
| "crypto/cipher" | |
| "crypto/openpgp/error" | |
| "crypto/rand" | |
| "crypto/sha1" | |
| "crypto/subtle" | |
| "hash" | |
| "io" | |
| "os" | |
| "strconv" | |
| ) | |
| // SymmetricallyEncrypted represents a symmetrically encrypted byte string. The | |
| // encrypted contents will consist of more OpenPGP packets. See RFC 4880, | |
| // sections 5.7 and 5.13. | |
| type SymmetricallyEncrypted struct { | |
| MDC bool // true iff this is a type 18 packet and thus has an embedded MAC. | |
| contents io.Reader | |
| prefix []byte | |
| } | |
| const symmetricallyEncryptedVersion = 1 | |
| func (se *SymmetricallyEncrypted) parse(r io.Reader) os.Error { | |
| if se.MDC { | |
| // See RFC 4880, section 5.13. | |
| var buf [1]byte | |
| _, err := readFull(r, buf[:]) | |
| if err != nil { | |
| return err | |
| } | |
| if buf[0] != symmetricallyEncryptedVersion { | |
| return error.UnsupportedError("unknown SymmetricallyEncrypted version") | |
| } | |
| } | |
| se.contents = r | |
| return nil | |
| } | |
| // Decrypt returns a ReadCloser, from which the decrypted contents of the | |
| // packet can be read. An incorrect key can, with high probability, be detected | |
| // immediately and this will result in a KeyIncorrect error being returned. | |
| func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, os.Error) { | |
| keySize := c.keySize() | |
| if keySize == 0 { | |
| return nil, error.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c))) | |
| } | |
| if len(key) != keySize { | |
| return nil, error.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length") | |
| } | |
| if se.prefix == nil { | |
| se.prefix = make([]byte, c.blockSize()+2) | |
| _, err := readFull(se.contents, se.prefix) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } else if len(se.prefix) != c.blockSize()+2 { | |
| return nil, error.InvalidArgumentError("can't try ciphers with different block lengths") | |
| } | |
| ocfbResync := cipher.OCFBResync | |
| if se.MDC { | |
| // MDC packets use a different form of OCFB mode. | |
| ocfbResync = cipher.OCFBNoResync | |
| } | |
| s := cipher.NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync) | |
| if s == nil { | |
| return nil, error.KeyIncorrectError | |
| } | |
| plaintext := cipher.StreamReader{S: s, R: se.contents} | |
| if se.MDC { | |
| // MDC packets have an embedded hash that we need to check. | |
| h := sha1.New() | |
| h.Write(se.prefix) | |
| return &seMDCReader{in: plaintext, h: h}, nil | |
| } | |
| // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser. | |
| return seReader{plaintext}, nil | |
| } | |
| // seReader wraps an io.Reader with a no-op Close method. | |
| type seReader struct { | |
| in io.Reader | |
| } | |
| func (ser seReader) Read(buf []byte) (int, os.Error) { | |
| return ser.in.Read(buf) | |
| } | |
| func (ser seReader) Close() os.Error { | |
| return nil | |
| } | |
| const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size | |
| // An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold | |
| // of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an | |
| // MDC packet containing a hash of the previous contents which is checked | |
| // against the running hash. See RFC 4880, section 5.13. | |
| type seMDCReader struct { | |
| in io.Reader | |
| h hash.Hash | |
| trailer [mdcTrailerSize]byte | |
| scratch [mdcTrailerSize]byte | |
| trailerUsed int | |
| error bool | |
| eof bool | |
| } | |
| func (ser *seMDCReader) Read(buf []byte) (n int, err os.Error) { | |
| if ser.error { | |
| err = io.ErrUnexpectedEOF | |
| return | |
| } | |
| if ser.eof { | |
| err = os.EOF | |
| return | |
| } | |
| // If we haven't yet filled the trailer buffer then we must do that | |
| // first. | |
| for ser.trailerUsed < mdcTrailerSize { | |
| n, err = ser.in.Read(ser.trailer[ser.trailerUsed:]) | |
| ser.trailerUsed += n | |
| if err == os.EOF { | |
| if ser.trailerUsed != mdcTrailerSize { | |
| n = 0 | |
| err = io.ErrUnexpectedEOF | |
| ser.error = true | |
| return | |
| } | |
| ser.eof = true | |
| n = 0 | |
| return | |
| } | |
| if err != nil { | |
| n = 0 | |
| return | |
| } | |
| } | |
| // If it's a short read then we read into a temporary buffer and shift | |
| // the data into the caller's buffer. | |
| if len(buf) <= mdcTrailerSize { | |
| n, err = readFull(ser.in, ser.scratch[:len(buf)]) | |
| copy(buf, ser.trailer[:n]) | |
| ser.h.Write(buf[:n]) | |
| copy(ser.trailer[:], ser.trailer[n:]) | |
| copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:]) | |
| if n < len(buf) { | |
| ser.eof = true | |
| err = os.EOF | |
| } | |
| return | |
| } | |
| n, err = ser.in.Read(buf[mdcTrailerSize:]) | |
| copy(buf, ser.trailer[:]) | |
| ser.h.Write(buf[:n]) | |
| copy(ser.trailer[:], buf[n:]) | |
| if err == os.EOF { | |
| ser.eof = true | |
| } | |
| return | |
| } | |
| // This is a new-format packet tag byte for a type 19 (MDC) packet. | |
| const mdcPacketTagByte = byte(0x80) | 0x40 | 19 | |
| func (ser *seMDCReader) Close() os.Error { | |
| if ser.error { | |
| return error.SignatureError("error during reading") | |
| } | |
| for !ser.eof { | |
| // We haven't seen EOF so we need to read to the end | |
| var buf [1024]byte | |
| _, err := ser.Read(buf[:]) | |
| if err == os.EOF { | |
| break | |
| } | |
| if err != nil { | |
| return error.SignatureError("error during reading") | |
| } | |
| } | |
| if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size { | |
| return error.SignatureError("MDC packet not found") | |
| } | |
| ser.h.Write(ser.trailer[:2]) | |
| final := ser.h.Sum() | |
| if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 { | |
| return error.SignatureError("hash mismatch") | |
| } | |
| return nil | |
| } | |
| // An seMDCWriter writes through to an io.WriteCloser while maintains a running | |
| // hash of the data written. On close, it emits an MDC packet containing the | |
| // running hash. | |
| type seMDCWriter struct { | |
| w io.WriteCloser | |
| h hash.Hash | |
| } | |
| func (w *seMDCWriter) Write(buf []byte) (n int, err os.Error) { | |
| w.h.Write(buf) | |
| return w.w.Write(buf) | |
| } | |
| func (w *seMDCWriter) Close() (err os.Error) { | |
| var buf [mdcTrailerSize]byte | |
| buf[0] = mdcPacketTagByte | |
| buf[1] = sha1.Size | |
| w.h.Write(buf[:2]) | |
| digest := w.h.Sum() | |
| copy(buf[2:], digest) | |
| _, err = w.w.Write(buf[:]) | |
| if err != nil { | |
| return | |
| } | |
| return w.w.Close() | |
| } | |
| // noOpCloser is like an ioutil.NopCloser, but for an io.Writer. | |
| type noOpCloser struct { | |
| w io.Writer | |
| } | |
| func (c noOpCloser) Write(data []byte) (n int, err os.Error) { | |
| return c.w.Write(data) | |
| } | |
| func (c noOpCloser) Close() os.Error { | |
| return nil | |
| } | |
| // SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet | |
| // to w and returns a WriteCloser to which the to-be-encrypted packets can be | |
| // written. | |
| func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte) (contents io.WriteCloser, err os.Error) { | |
| if c.keySize() != len(key) { | |
| return nil, error.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length") | |
| } | |
| writeCloser := noOpCloser{w} | |
| ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC) | |
| if err != nil { | |
| return | |
| } | |
| _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion}) | |
| if err != nil { | |
| return | |
| } | |
| block := c.new(key) | |
| blockSize := block.BlockSize() | |
| iv := make([]byte, blockSize) | |
| _, err = rand.Reader.Read(iv) | |
| if err != nil { | |
| return | |
| } | |
| s, prefix := cipher.NewOCFBEncrypter(block, iv, cipher.OCFBNoResync) | |
| _, err = ciphertext.Write(prefix) | |
| if err != nil { | |
| return | |
| } | |
| plaintext := cipher.StreamWriter{S: s, W: ciphertext} | |
| h := sha1.New() | |
| h.Write(iv) | |
| h.Write(iv[blockSize-2:]) | |
| contents = &seMDCWriter{w: plaintext, h: h} | |
| return | |
| } |