...

Source file src/github.com/ProtonMail/go-crypto/openpgp/clearsign/clearsign.go

Documentation: github.com/ProtonMail/go-crypto/openpgp/clearsign

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package clearsign generates and processes OpenPGP, clear-signed data. See
     6  // RFC 4880, section 7.
     7  //
     8  // Clearsigned messages are cryptographically signed, but the contents of the
     9  // message are kept in plaintext so that it can be read without special tools.
    10  package clearsign // import "github.com/ProtonMail/go-crypto/openpgp/clearsign"
    11  
    12  import (
    13  	"bufio"
    14  	"bytes"
    15  	"crypto"
    16  	"fmt"
    17  	"hash"
    18  	"io"
    19  	"net/textproto"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/ProtonMail/go-crypto/openpgp"
    24  	"github.com/ProtonMail/go-crypto/openpgp/armor"
    25  	"github.com/ProtonMail/go-crypto/openpgp/errors"
    26  	"github.com/ProtonMail/go-crypto/openpgp/packet"
    27  )
    28  
    29  // A Block represents a clearsigned message. A signature on a Block can
    30  // be checked by calling Block.VerifySignature.
    31  type Block struct {
    32  	Headers          textproto.MIMEHeader // Optional unverified Hash headers
    33  	Plaintext        []byte               // The original message text
    34  	Bytes            []byte               // The signed message
    35  	ArmoredSignature *armor.Block         // The signature block
    36  }
    37  
    38  // start is the marker which denotes the beginning of a clearsigned message.
    39  var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----")
    40  
    41  // dashEscape is prefixed to any lines that begin with a hyphen so that they
    42  // can't be confused with endText.
    43  var dashEscape = []byte("- ")
    44  
    45  // endText is a marker which denotes the end of the message and the start of
    46  // an armored signature.
    47  var endText = []byte("-----BEGIN PGP SIGNATURE-----")
    48  
    49  // end is a marker which denotes the end of the armored signature.
    50  var end = []byte("\n-----END PGP SIGNATURE-----")
    51  
    52  var crlf = []byte("\r\n")
    53  var lf = byte('\n')
    54  
    55  // getLine returns the first \r\n or \n delineated line from the given byte
    56  // array. The line does not include the \r\n or \n. The remainder of the byte
    57  // array (also not including the new line bytes) is also returned and this will
    58  // always be smaller than the original argument.
    59  func getLine(data []byte) (line, rest []byte) {
    60  	i := bytes.Index(data, []byte{'\n'})
    61  	var j int
    62  	if i < 0 {
    63  		i = len(data)
    64  		j = i
    65  	} else {
    66  		j = i + 1
    67  		if i > 0 && data[i-1] == '\r' {
    68  			i--
    69  		}
    70  	}
    71  	return data[0:i], data[j:]
    72  }
    73  
    74  // Decode finds the first clearsigned message in data and returns it, as well as
    75  // the suffix of data which remains after the message. Any prefix data is
    76  // discarded.
    77  //
    78  // If no message is found, or if the message is invalid, Decode returns nil and
    79  // the whole data slice. The only allowed header type is Hash, and it is not
    80  // verified against the signature hash.
    81  func Decode(data []byte) (b *Block, rest []byte) {
    82  	// start begins with a newline. However, at the very beginning of
    83  	// the byte array, we'll accept the start string without it.
    84  	rest = data
    85  	if bytes.HasPrefix(data, start[1:]) {
    86  		rest = rest[len(start)-1:]
    87  	} else if i := bytes.Index(data, start); i >= 0 {
    88  		rest = rest[i+len(start):]
    89  	} else {
    90  		return nil, data
    91  	}
    92  
    93  	// Consume the start line and check it does not have a suffix.
    94  	suffix, rest := getLine(rest)
    95  	if len(suffix) != 0 {
    96  		return nil, data
    97  	}
    98  
    99  	var line []byte
   100  	b = &Block{
   101  		Headers: make(textproto.MIMEHeader),
   102  	}
   103  
   104  	// Next come a series of header lines.
   105  	for {
   106  		// This loop terminates because getLine's second result is
   107  		// always smaller than its argument.
   108  		if len(rest) == 0 {
   109  			return nil, data
   110  		}
   111  		// An empty line marks the end of the headers.
   112  		if line, rest = getLine(rest); len(line) == 0 {
   113  			break
   114  		}
   115  
   116  		// Reject headers with control or Unicode characters.
   117  		if i := bytes.IndexFunc(line, func(r rune) bool {
   118  			return r < 0x20 || r > 0x7e
   119  		}); i != -1 {
   120  			return nil, data
   121  		}
   122  
   123  		i := bytes.Index(line, []byte{':'})
   124  		if i == -1 {
   125  			return nil, data
   126  		}
   127  
   128  		key, val := string(line[0:i]), string(line[i+1:])
   129  		key = strings.TrimSpace(key)
   130  		if key != "Hash" {
   131  			return nil, data
   132  		}
   133  		for _, val := range strings.Split(val, ",") {
   134  			val = strings.TrimSpace(val)
   135  			b.Headers.Add(key, val)
   136  		}
   137  	}
   138  
   139  	firstLine := true
   140  	for {
   141  		start := rest
   142  
   143  		line, rest = getLine(rest)
   144  		if len(line) == 0 && len(rest) == 0 {
   145  			// No armored data was found, so this isn't a complete message.
   146  			return nil, data
   147  		}
   148  		if bytes.Equal(line, endText) {
   149  			// Back up to the start of the line because armor expects to see the
   150  			// header line.
   151  			rest = start
   152  			break
   153  		}
   154  
   155  		// The final CRLF isn't included in the hash so we don't write it until
   156  		// we've seen the next line.
   157  		if firstLine {
   158  			firstLine = false
   159  		} else {
   160  			b.Bytes = append(b.Bytes, crlf...)
   161  		}
   162  
   163  		if bytes.HasPrefix(line, dashEscape) {
   164  			line = line[2:]
   165  		}
   166  		line = bytes.TrimRight(line, " \t")
   167  		b.Bytes = append(b.Bytes, line...)
   168  
   169  		b.Plaintext = append(b.Plaintext, line...)
   170  		b.Plaintext = append(b.Plaintext, lf)
   171  	}
   172  
   173  	// We want to find the extent of the armored data (including any newlines at
   174  	// the end).
   175  	i := bytes.Index(rest, end)
   176  	if i == -1 {
   177  		return nil, data
   178  	}
   179  	i += len(end)
   180  	for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') {
   181  		i++
   182  	}
   183  	armored := rest[:i]
   184  	rest = rest[i:]
   185  
   186  	var err error
   187  	b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored))
   188  	if err != nil {
   189  		return nil, data
   190  	}
   191  
   192  	return b, rest
   193  }
   194  
   195  // A dashEscaper is an io.WriteCloser which processes the body of a clear-signed
   196  // message. The clear-signed message is written to buffered and a hash, suitable
   197  // for signing, is maintained in h.
   198  //
   199  // When closed, an armored signature is created and written to complete the
   200  // message.
   201  type dashEscaper struct {
   202  	buffered *bufio.Writer
   203  	hashers  []hash.Hash // one per key in privateKeys
   204  	hashType crypto.Hash
   205  	toHash   io.Writer // writes to all the hashes in hashers
   206  
   207  	atBeginningOfLine bool
   208  	isFirstLine       bool
   209  
   210  	whitespace []byte
   211  	byteBuf    []byte // a one byte buffer to save allocations
   212  
   213  	privateKeys []*packet.PrivateKey
   214  	config      *packet.Config
   215  }
   216  
   217  func (d *dashEscaper) Write(data []byte) (n int, err error) {
   218  	for _, b := range data {
   219  		d.byteBuf[0] = b
   220  
   221  		if d.atBeginningOfLine {
   222  			// The final CRLF isn't included in the hash so we have to wait
   223  			// until this point (the start of the next line) before writing it.
   224  			if !d.isFirstLine {
   225  				d.toHash.Write(crlf)
   226  			}
   227  			d.isFirstLine = false
   228  		}
   229  
   230  		// Any whitespace at the end of the line has to be removed so we
   231  		// buffer it until we find out whether there's more on this line.
   232  		if b == ' ' || b == '\t' || b == '\r' {
   233  			d.whitespace = append(d.whitespace, b)
   234  			d.atBeginningOfLine = false
   235  			continue
   236  		}
   237  
   238  		if d.atBeginningOfLine {
   239  			// At the beginning of a line, hyphens have to be escaped.
   240  			if b == '-' {
   241  				// The signature isn't calculated over the dash-escaped text so
   242  				// the escape is only written to buffered.
   243  				if _, err = d.buffered.Write(dashEscape); err != nil {
   244  					return
   245  				}
   246  				d.toHash.Write(d.byteBuf)
   247  				d.atBeginningOfLine = false
   248  			} else if b == '\n' {
   249  				// Nothing to do because we delay writing CRLF to the hash.
   250  			} else {
   251  				d.toHash.Write(d.byteBuf)
   252  				d.atBeginningOfLine = false
   253  			}
   254  			if err = d.buffered.WriteByte(b); err != nil {
   255  				return
   256  			}
   257  		} else {
   258  			if b == '\n' {
   259  				// We got a raw \n. Drop any trailing whitespace and write a
   260  				// CRLF.
   261  				d.whitespace = d.whitespace[:0]
   262  				// We delay writing CRLF to the hash until the start of the
   263  				// next line.
   264  				if err = d.buffered.WriteByte(b); err != nil {
   265  					return
   266  				}
   267  				d.atBeginningOfLine = true
   268  			} else {
   269  				// Any buffered whitespace wasn't at the end of the line so
   270  				// we need to write it out.
   271  				if len(d.whitespace) > 0 {
   272  					d.toHash.Write(d.whitespace)
   273  					if _, err = d.buffered.Write(d.whitespace); err != nil {
   274  						return
   275  					}
   276  					d.whitespace = d.whitespace[:0]
   277  				}
   278  				d.toHash.Write(d.byteBuf)
   279  				if err = d.buffered.WriteByte(b); err != nil {
   280  					return
   281  				}
   282  			}
   283  		}
   284  	}
   285  
   286  	n = len(data)
   287  	return
   288  }
   289  
   290  func (d *dashEscaper) Close() (err error) {
   291  	if !d.atBeginningOfLine {
   292  		if err = d.buffered.WriteByte(lf); err != nil {
   293  			return
   294  		}
   295  	}
   296  
   297  	out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
   298  	if err != nil {
   299  		return
   300  	}
   301  
   302  	t := d.config.Now()
   303  	for i, k := range d.privateKeys {
   304  		sig := new(packet.Signature)
   305  		sig.SigType = packet.SigTypeText
   306  		sig.PubKeyAlgo = k.PubKeyAlgo
   307  		sig.Hash = d.hashType
   308  		sig.CreationTime = t
   309  		sig.IssuerKeyId = &k.KeyId
   310  		sig.IssuerFingerprint = k.Fingerprint
   311  		sig.Notations = d.config.Notations()
   312  		sigLifetimeSecs := d.config.SigLifetime()
   313  		sig.SigLifetimeSecs = &sigLifetimeSecs
   314  
   315  		if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
   316  			return
   317  		}
   318  		if err = sig.Serialize(out); err != nil {
   319  			return
   320  		}
   321  	}
   322  
   323  	if err = out.Close(); err != nil {
   324  		return
   325  	}
   326  	if err = d.buffered.Flush(); err != nil {
   327  		return
   328  	}
   329  	return
   330  }
   331  
   332  // Encode returns a WriteCloser which will clear-sign a message with privateKey
   333  // and write it to w. If config is nil, sensible defaults are used.
   334  func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
   335  	return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
   336  }
   337  
   338  // EncodeMulti returns a WriteCloser which will clear-sign a message with all the
   339  // private keys indicated and write it to w. If config is nil, sensible defaults
   340  // are used.
   341  func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
   342  	for _, k := range privateKeys {
   343  		if k.Encrypted {
   344  			return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
   345  		}
   346  	}
   347  
   348  	hashType := config.Hash()
   349  	name := nameOfHash(hashType)
   350  	if len(name) == 0 {
   351  		return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType)))
   352  	}
   353  
   354  	if !hashType.Available() {
   355  		return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
   356  	}
   357  	var hashers []hash.Hash
   358  	var ws []io.Writer
   359  	for range privateKeys {
   360  		h := hashType.New()
   361  		hashers = append(hashers, h)
   362  		ws = append(ws, h)
   363  	}
   364  	toHash := io.MultiWriter(ws...)
   365  
   366  	buffered := bufio.NewWriter(w)
   367  	// start has a \n at the beginning that we don't want here.
   368  	if _, err = buffered.Write(start[1:]); err != nil {
   369  		return
   370  	}
   371  	if err = buffered.WriteByte(lf); err != nil {
   372  		return
   373  	}
   374  	if _, err = buffered.WriteString("Hash: "); err != nil {
   375  		return
   376  	}
   377  	if _, err = buffered.WriteString(name); err != nil {
   378  		return
   379  	}
   380  	if err = buffered.WriteByte(lf); err != nil {
   381  		return
   382  	}
   383  	if err = buffered.WriteByte(lf); err != nil {
   384  		return
   385  	}
   386  
   387  	plaintext = &dashEscaper{
   388  		buffered: buffered,
   389  		hashers:  hashers,
   390  		hashType: hashType,
   391  		toHash:   toHash,
   392  
   393  		atBeginningOfLine: true,
   394  		isFirstLine:       true,
   395  
   396  		byteBuf: make([]byte, 1),
   397  
   398  		privateKeys: privateKeys,
   399  		config:      config,
   400  	}
   401  
   402  	return
   403  }
   404  
   405  // VerifySignature checks a clearsigned message signature, and checks that the
   406  // hash algorithm in the header matches the hash algorithm in the signature.
   407  func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) {
   408  	var expectedHashes []crypto.Hash
   409  	for _, v := range b.Headers {
   410  		for _, name := range v {
   411  			expectedHash := nameToHash(name)
   412  			if uint8(expectedHash) == 0 {
   413  				return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers")
   414  			}
   415  			expectedHashes = append(expectedHashes, expectedHash)
   416  		}
   417  	}
   418  	if len(expectedHashes) == 0 {
   419  		expectedHashes = append(expectedHashes, crypto.MD5)
   420  	}
   421  	return openpgp.CheckDetachedSignatureAndHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, config)
   422  }
   423  
   424  // nameOfHash returns the OpenPGP name for the given hash, or the empty string
   425  // if the name isn't known. See RFC 4880, section 9.4.
   426  func nameOfHash(h crypto.Hash) string {
   427  	switch h {
   428  	case crypto.SHA224:
   429  		return "SHA224"
   430  	case crypto.SHA256:
   431  		return "SHA256"
   432  	case crypto.SHA384:
   433  		return "SHA384"
   434  	case crypto.SHA512:
   435  		return "SHA512"
   436  	case crypto.SHA3_256:
   437  		return "SHA3-256"
   438  	case crypto.SHA3_512:
   439  		return "SHA3-512"
   440  	}
   441  	return ""
   442  }
   443  
   444  // nameToHash returns a hash for a given OpenPGP name, or 0
   445  // if the name isn't known. See RFC 4880, section 9.4.
   446  func nameToHash(h string) crypto.Hash {
   447  	switch h {
   448  	case "SHA1":
   449  		return crypto.SHA1
   450  	case "SHA224":
   451  		return crypto.SHA224
   452  	case "SHA256":
   453  		return crypto.SHA256
   454  	case "SHA384":
   455  		return crypto.SHA384
   456  	case "SHA512":
   457  		return crypto.SHA512
   458  	case "SHA3-256":
   459  		return crypto.SHA3_256
   460  	case "SHA3-512":
   461  		return crypto.SHA3_512
   462  	}
   463  	return crypto.Hash(0)
   464  }
   465  

View as plain text