400 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2012 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 clearsign generates and processes OpenPGP, clear-signed data. See
 | |
| // RFC 4880, section 7.
 | |
| //
 | |
| // Clearsigned messages are cryptographically signed, but the contents of the
 | |
| // message are kept in plaintext so that it can be read without special tools.
 | |
| package clearsign // import "golang.org/x/crypto/openpgp/clearsign"
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"crypto"
 | |
| 	"fmt"
 | |
| 	"hash"
 | |
| 	"io"
 | |
| 	"net/textproto"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"golang.org/x/crypto/openpgp/armor"
 | |
| 	"golang.org/x/crypto/openpgp/errors"
 | |
| 	"golang.org/x/crypto/openpgp/packet"
 | |
| )
 | |
| 
 | |
| // A Block represents a clearsigned message. A signature on a Block can
 | |
| // be checked by passing Bytes into openpgp.CheckDetachedSignature.
 | |
| type Block struct {
 | |
| 	Headers          textproto.MIMEHeader // Optional message headers
 | |
| 	Plaintext        []byte               // The original message text
 | |
| 	Bytes            []byte               // The signed message
 | |
| 	ArmoredSignature *armor.Block         // The signature block
 | |
| }
 | |
| 
 | |
| // start is the marker which denotes the beginning of a clearsigned message.
 | |
| var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----")
 | |
| 
 | |
| // dashEscape is prefixed to any lines that begin with a hyphen so that they
 | |
| // can't be confused with endText.
 | |
| var dashEscape = []byte("- ")
 | |
| 
 | |
| // endText is a marker which denotes the end of the message and the start of
 | |
| // an armored signature.
 | |
| var endText = []byte("-----BEGIN PGP SIGNATURE-----")
 | |
| 
 | |
| // end is a marker which denotes the end of the armored signature.
 | |
| var end = []byte("\n-----END PGP SIGNATURE-----")
 | |
| 
 | |
| var crlf = []byte("\r\n")
 | |
| var lf = byte('\n')
 | |
| 
 | |
| // getLine returns the first \r\n or \n delineated line from the given byte
 | |
| // array. The line does not include the \r\n or \n. The remainder of the byte
 | |
| // array (also not including the new line bytes) is also returned and this will
 | |
| // always be smaller than the original argument.
 | |
| func getLine(data []byte) (line, rest []byte) {
 | |
| 	i := bytes.Index(data, []byte{'\n'})
 | |
| 	var j int
 | |
| 	if i < 0 {
 | |
| 		i = len(data)
 | |
| 		j = i
 | |
| 	} else {
 | |
| 		j = i + 1
 | |
| 		if i > 0 && data[i-1] == '\r' {
 | |
| 			i--
 | |
| 		}
 | |
| 	}
 | |
| 	return data[0:i], data[j:]
 | |
| }
 | |
| 
 | |
| // Decode finds the first clearsigned message in data and returns it, as well
 | |
| // as the suffix of data which remains after the message.
 | |
| func Decode(data []byte) (b *Block, rest []byte) {
 | |
| 	// start begins with a newline. However, at the very beginning of
 | |
| 	// the byte array, we'll accept the start string without it.
 | |
| 	rest = data
 | |
| 	if bytes.HasPrefix(data, start[1:]) {
 | |
| 		rest = rest[len(start)-1:]
 | |
| 	} else if i := bytes.Index(data, start); i >= 0 {
 | |
| 		rest = rest[i+len(start):]
 | |
| 	} else {
 | |
| 		return nil, data
 | |
| 	}
 | |
| 
 | |
| 	// Consume the start line.
 | |
| 	_, rest = getLine(rest)
 | |
| 
 | |
| 	var line []byte
 | |
| 	b = &Block{
 | |
| 		Headers: make(textproto.MIMEHeader),
 | |
| 	}
 | |
| 
 | |
| 	// Next come a series of header lines.
 | |
| 	for {
 | |
| 		// This loop terminates because getLine's second result is
 | |
| 		// always smaller than its argument.
 | |
| 		if len(rest) == 0 {
 | |
| 			return nil, data
 | |
| 		}
 | |
| 		// An empty line marks the end of the headers.
 | |
| 		if line, rest = getLine(rest); len(line) == 0 {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		i := bytes.Index(line, []byte{':'})
 | |
| 		if i == -1 {
 | |
| 			return nil, data
 | |
| 		}
 | |
| 
 | |
| 		key, val := line[0:i], line[i+1:]
 | |
| 		key = bytes.TrimSpace(key)
 | |
| 		val = bytes.TrimSpace(val)
 | |
| 		b.Headers.Add(string(key), string(val))
 | |
| 	}
 | |
| 
 | |
| 	firstLine := true
 | |
| 	for {
 | |
| 		start := rest
 | |
| 
 | |
| 		line, rest = getLine(rest)
 | |
| 		if len(line) == 0 && len(rest) == 0 {
 | |
| 			// No armored data was found, so this isn't a complete message.
 | |
| 			return nil, data
 | |
| 		}
 | |
| 		if bytes.Equal(line, endText) {
 | |
| 			// Back up to the start of the line because armor expects to see the
 | |
| 			// header line.
 | |
| 			rest = start
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		// The final CRLF isn't included in the hash so we don't write it until
 | |
| 		// we've seen the next line.
 | |
| 		if firstLine {
 | |
| 			firstLine = false
 | |
| 		} else {
 | |
| 			b.Bytes = append(b.Bytes, crlf...)
 | |
| 		}
 | |
| 
 | |
| 		if bytes.HasPrefix(line, dashEscape) {
 | |
| 			line = line[2:]
 | |
| 		}
 | |
| 		line = bytes.TrimRight(line, " \t")
 | |
| 		b.Bytes = append(b.Bytes, line...)
 | |
| 
 | |
| 		b.Plaintext = append(b.Plaintext, line...)
 | |
| 		b.Plaintext = append(b.Plaintext, lf)
 | |
| 	}
 | |
| 
 | |
| 	// We want to find the extent of the armored data (including any newlines at
 | |
| 	// the end).
 | |
| 	i := bytes.Index(rest, end)
 | |
| 	if i == -1 {
 | |
| 		return nil, data
 | |
| 	}
 | |
| 	i += len(end)
 | |
| 	for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') {
 | |
| 		i++
 | |
| 	}
 | |
| 	armored := rest[:i]
 | |
| 	rest = rest[i:]
 | |
| 
 | |
| 	var err error
 | |
| 	b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored))
 | |
| 	if err != nil {
 | |
| 		return nil, data
 | |
| 	}
 | |
| 
 | |
| 	return b, rest
 | |
| }
 | |
| 
 | |
| // A dashEscaper is an io.WriteCloser which processes the body of a clear-signed
 | |
| // message. The clear-signed message is written to buffered and a hash, suitable
 | |
| // for signing, is maintained in h.
 | |
| //
 | |
| // When closed, an armored signature is created and written to complete the
 | |
| // message.
 | |
| type dashEscaper struct {
 | |
| 	buffered *bufio.Writer
 | |
| 	hashers  []hash.Hash // one per key in privateKeys
 | |
| 	hashType crypto.Hash
 | |
| 	toHash   io.Writer // writes to all the hashes in hashers
 | |
| 
 | |
| 	atBeginningOfLine bool
 | |
| 	isFirstLine       bool
 | |
| 
 | |
| 	whitespace []byte
 | |
| 	byteBuf    []byte // a one byte buffer to save allocations
 | |
| 
 | |
| 	privateKeys []*packet.PrivateKey
 | |
| 	config      *packet.Config
 | |
| }
 | |
| 
 | |
| func (d *dashEscaper) Write(data []byte) (n int, err error) {
 | |
| 	for _, b := range data {
 | |
| 		d.byteBuf[0] = b
 | |
| 
 | |
| 		if d.atBeginningOfLine {
 | |
| 			// The final CRLF isn't included in the hash so we have to wait
 | |
| 			// until this point (the start of the next line) before writing it.
 | |
| 			if !d.isFirstLine {
 | |
| 				d.toHash.Write(crlf)
 | |
| 			}
 | |
| 			d.isFirstLine = false
 | |
| 		}
 | |
| 
 | |
| 		// Any whitespace at the end of the line has to be removed so we
 | |
| 		// buffer it until we find out whether there's more on this line.
 | |
| 		if b == ' ' || b == '\t' || b == '\r' {
 | |
| 			d.whitespace = append(d.whitespace, b)
 | |
| 			d.atBeginningOfLine = false
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if d.atBeginningOfLine {
 | |
| 			// At the beginning of a line, hyphens have to be escaped.
 | |
| 			if b == '-' {
 | |
| 				// The signature isn't calculated over the dash-escaped text so
 | |
| 				// the escape is only written to buffered.
 | |
| 				if _, err = d.buffered.Write(dashEscape); err != nil {
 | |
| 					return
 | |
| 				}
 | |
| 				d.toHash.Write(d.byteBuf)
 | |
| 				d.atBeginningOfLine = false
 | |
| 			} else if b == '\n' {
 | |
| 				// Nothing to do because we delay writing CRLF to the hash.
 | |
| 			} else {
 | |
| 				d.toHash.Write(d.byteBuf)
 | |
| 				d.atBeginningOfLine = false
 | |
| 			}
 | |
| 			if err = d.buffered.WriteByte(b); err != nil {
 | |
| 				return
 | |
| 			}
 | |
| 		} else {
 | |
| 			if b == '\n' {
 | |
| 				// We got a raw \n. Drop any trailing whitespace and write a
 | |
| 				// CRLF.
 | |
| 				d.whitespace = d.whitespace[:0]
 | |
| 				// We delay writing CRLF to the hash until the start of the
 | |
| 				// next line.
 | |
| 				if err = d.buffered.WriteByte(b); err != nil {
 | |
| 					return
 | |
| 				}
 | |
| 				d.atBeginningOfLine = true
 | |
| 			} else {
 | |
| 				// Any buffered whitespace wasn't at the end of the line so
 | |
| 				// we need to write it out.
 | |
| 				if len(d.whitespace) > 0 {
 | |
| 					d.toHash.Write(d.whitespace)
 | |
| 					if _, err = d.buffered.Write(d.whitespace); err != nil {
 | |
| 						return
 | |
| 					}
 | |
| 					d.whitespace = d.whitespace[:0]
 | |
| 				}
 | |
| 				d.toHash.Write(d.byteBuf)
 | |
| 				if err = d.buffered.WriteByte(b); err != nil {
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	n = len(data)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (d *dashEscaper) Close() (err error) {
 | |
| 	if !d.atBeginningOfLine {
 | |
| 		if err = d.buffered.WriteByte(lf); err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	t := d.config.Now()
 | |
| 	for i, k := range d.privateKeys {
 | |
| 		sig := new(packet.Signature)
 | |
| 		sig.SigType = packet.SigTypeText
 | |
| 		sig.PubKeyAlgo = k.PubKeyAlgo
 | |
| 		sig.Hash = d.hashType
 | |
| 		sig.CreationTime = t
 | |
| 		sig.IssuerKeyId = &k.KeyId
 | |
| 
 | |
| 		if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 		if err = sig.Serialize(out); err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err = out.Close(); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if err = d.buffered.Flush(); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // Encode returns a WriteCloser which will clear-sign a message with privateKey
 | |
| // and write it to w. If config is nil, sensible defaults are used.
 | |
| func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
 | |
| 	return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
 | |
| }
 | |
| 
 | |
| // EncodeMulti returns a WriteCloser which will clear-sign a message with all the
 | |
| // private keys indicated and write it to w. If config is nil, sensible defaults
 | |
| // are used.
 | |
| func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
 | |
| 	for _, k := range privateKeys {
 | |
| 		if k.Encrypted {
 | |
| 			return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	hashType := config.Hash()
 | |
| 	name := nameOfHash(hashType)
 | |
| 	if len(name) == 0 {
 | |
| 		return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType)))
 | |
| 	}
 | |
| 
 | |
| 	if !hashType.Available() {
 | |
| 		return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
 | |
| 	}
 | |
| 	var hashers []hash.Hash
 | |
| 	var ws []io.Writer
 | |
| 	for range privateKeys {
 | |
| 		h := hashType.New()
 | |
| 		hashers = append(hashers, h)
 | |
| 		ws = append(ws, h)
 | |
| 	}
 | |
| 	toHash := io.MultiWriter(ws...)
 | |
| 
 | |
| 	buffered := bufio.NewWriter(w)
 | |
| 	// start has a \n at the beginning that we don't want here.
 | |
| 	if _, err = buffered.Write(start[1:]); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if err = buffered.WriteByte(lf); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if _, err = buffered.WriteString("Hash: "); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if _, err = buffered.WriteString(name); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if err = buffered.WriteByte(lf); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if err = buffered.WriteByte(lf); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	plaintext = &dashEscaper{
 | |
| 		buffered: buffered,
 | |
| 		hashers:  hashers,
 | |
| 		hashType: hashType,
 | |
| 		toHash:   toHash,
 | |
| 
 | |
| 		atBeginningOfLine: true,
 | |
| 		isFirstLine:       true,
 | |
| 
 | |
| 		byteBuf: make([]byte, 1),
 | |
| 
 | |
| 		privateKeys: privateKeys,
 | |
| 		config:      config,
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // nameOfHash returns the OpenPGP name for the given hash, or the empty string
 | |
| // if the name isn't known. See RFC 4880, section 9.4.
 | |
| func nameOfHash(h crypto.Hash) string {
 | |
| 	switch h {
 | |
| 	case crypto.MD5:
 | |
| 		return "MD5"
 | |
| 	case crypto.SHA1:
 | |
| 		return "SHA1"
 | |
| 	case crypto.RIPEMD160:
 | |
| 		return "RIPEMD160"
 | |
| 	case crypto.SHA224:
 | |
| 		return "SHA224"
 | |
| 	case crypto.SHA256:
 | |
| 		return "SHA256"
 | |
| 	case crypto.SHA384:
 | |
| 		return "SHA384"
 | |
| 	case crypto.SHA512:
 | |
| 		return "SHA512"
 | |
| 	}
 | |
| 	return ""
 | |
| }
 |