...

Source file src/golang.org/x/crypto/openpgp/write.go

Documentation: golang.org/x/crypto/openpgp

     1  // Copyright 2011 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 openpgp
     6  
     7  import (
     8  	"crypto"
     9  	"hash"
    10  	"io"
    11  	"strconv"
    12  	"time"
    13  
    14  	"golang.org/x/crypto/openpgp/armor"
    15  	"golang.org/x/crypto/openpgp/errors"
    16  	"golang.org/x/crypto/openpgp/packet"
    17  	"golang.org/x/crypto/openpgp/s2k"
    18  )
    19  
    20  // DetachSign signs message with the private key from signer (which must
    21  // already have been decrypted) and writes the signature to w.
    22  // If config is nil, sensible defaults will be used.
    23  func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
    24  	return detachSign(w, signer, message, packet.SigTypeBinary, config)
    25  }
    26  
    27  // ArmoredDetachSign signs message with the private key from signer (which
    28  // must already have been decrypted) and writes an armored signature to w.
    29  // If config is nil, sensible defaults will be used.
    30  func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) {
    31  	return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config)
    32  }
    33  
    34  // DetachSignText signs message (after canonicalising the line endings) with
    35  // the private key from signer (which must already have been decrypted) and
    36  // writes the signature to w.
    37  // If config is nil, sensible defaults will be used.
    38  func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
    39  	return detachSign(w, signer, message, packet.SigTypeText, config)
    40  }
    41  
    42  // ArmoredDetachSignText signs message (after canonicalising the line endings)
    43  // with the private key from signer (which must already have been decrypted)
    44  // and writes an armored signature to w.
    45  // If config is nil, sensible defaults will be used.
    46  func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
    47  	return armoredDetachSign(w, signer, message, packet.SigTypeText, config)
    48  }
    49  
    50  func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
    51  	out, err := armor.Encode(w, SignatureType, nil)
    52  	if err != nil {
    53  		return
    54  	}
    55  	err = detachSign(out, signer, message, sigType, config)
    56  	if err != nil {
    57  		return
    58  	}
    59  	return out.Close()
    60  }
    61  
    62  func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
    63  	if signer.PrivateKey == nil {
    64  		return errors.InvalidArgumentError("signing key doesn't have a private key")
    65  	}
    66  	if signer.PrivateKey.Encrypted {
    67  		return errors.InvalidArgumentError("signing key is encrypted")
    68  	}
    69  
    70  	sig := new(packet.Signature)
    71  	sig.SigType = sigType
    72  	sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo
    73  	sig.Hash = config.Hash()
    74  	sig.CreationTime = config.Now()
    75  	sig.IssuerKeyId = &signer.PrivateKey.KeyId
    76  
    77  	h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
    78  	if err != nil {
    79  		return
    80  	}
    81  	io.Copy(wrappedHash, message)
    82  
    83  	err = sig.Sign(h, signer.PrivateKey, config)
    84  	if err != nil {
    85  		return
    86  	}
    87  
    88  	return sig.Serialize(w)
    89  }
    90  
    91  // FileHints contains metadata about encrypted files. This metadata is, itself,
    92  // encrypted.
    93  type FileHints struct {
    94  	// IsBinary can be set to hint that the contents are binary data.
    95  	IsBinary bool
    96  	// FileName hints at the name of the file that should be written. It's
    97  	// truncated to 255 bytes if longer. It may be empty to suggest that the
    98  	// file should not be written to disk. It may be equal to "_CONSOLE" to
    99  	// suggest the data should not be written to disk.
   100  	FileName string
   101  	// ModTime contains the modification time of the file, or the zero time if not applicable.
   102  	ModTime time.Time
   103  }
   104  
   105  // SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
   106  // The resulting WriteCloser must be closed after the contents of the file have
   107  // been written.
   108  // If config is nil, sensible defaults will be used.
   109  func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
   110  	if hints == nil {
   111  		hints = &FileHints{}
   112  	}
   113  
   114  	key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
   115  	if err != nil {
   116  		return
   117  	}
   118  	w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config)
   119  	if err != nil {
   120  		return
   121  	}
   122  
   123  	literaldata := w
   124  	if algo := config.Compression(); algo != packet.CompressionNone {
   125  		var compConfig *packet.CompressionConfig
   126  		if config != nil {
   127  			compConfig = config.CompressionConfig
   128  		}
   129  		literaldata, err = packet.SerializeCompressed(w, algo, compConfig)
   130  		if err != nil {
   131  			return
   132  		}
   133  	}
   134  
   135  	var epochSeconds uint32
   136  	if !hints.ModTime.IsZero() {
   137  		epochSeconds = uint32(hints.ModTime.Unix())
   138  	}
   139  	return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds)
   140  }
   141  
   142  // intersectPreferences mutates and returns a prefix of a that contains only
   143  // the values in the intersection of a and b. The order of a is preserved.
   144  func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
   145  	var j int
   146  	for _, v := range a {
   147  		for _, v2 := range b {
   148  			if v == v2 {
   149  				a[j] = v
   150  				j++
   151  				break
   152  			}
   153  		}
   154  	}
   155  
   156  	return a[:j]
   157  }
   158  
   159  func hashToHashId(h crypto.Hash) uint8 {
   160  	v, ok := s2k.HashToHashId(h)
   161  	if !ok {
   162  		panic("tried to convert unknown hash")
   163  	}
   164  	return v
   165  }
   166  
   167  // writeAndSign writes the data as a payload package and, optionally, signs
   168  // it. hints contains optional information, that is also encrypted,
   169  // that aids the recipients in processing the message. The resulting
   170  // WriteCloser must be closed after the contents of the file have been
   171  // written. If config is nil, sensible defaults will be used.
   172  func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
   173  	var signer *packet.PrivateKey
   174  	if signed != nil {
   175  		signKey, ok := signed.signingKey(config.Now())
   176  		if !ok {
   177  			return nil, errors.InvalidArgumentError("no valid signing keys")
   178  		}
   179  		signer = signKey.PrivateKey
   180  		if signer == nil {
   181  			return nil, errors.InvalidArgumentError("no private key in signing key")
   182  		}
   183  		if signer.Encrypted {
   184  			return nil, errors.InvalidArgumentError("signing key must be decrypted")
   185  		}
   186  	}
   187  
   188  	var hash crypto.Hash
   189  	for _, hashId := range candidateHashes {
   190  		if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() {
   191  			hash = h
   192  			break
   193  		}
   194  	}
   195  
   196  	// If the hash specified by config is a candidate, we'll use that.
   197  	if configuredHash := config.Hash(); configuredHash.Available() {
   198  		for _, hashId := range candidateHashes {
   199  			if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash {
   200  				hash = h
   201  				break
   202  			}
   203  		}
   204  	}
   205  
   206  	if hash == 0 {
   207  		hashId := candidateHashes[0]
   208  		name, ok := s2k.HashIdToString(hashId)
   209  		if !ok {
   210  			name = "#" + strconv.Itoa(int(hashId))
   211  		}
   212  		return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
   213  	}
   214  
   215  	if signer != nil {
   216  		ops := &packet.OnePassSignature{
   217  			SigType:    packet.SigTypeBinary,
   218  			Hash:       hash,
   219  			PubKeyAlgo: signer.PubKeyAlgo,
   220  			KeyId:      signer.KeyId,
   221  			IsLast:     true,
   222  		}
   223  		if err := ops.Serialize(payload); err != nil {
   224  			return nil, err
   225  		}
   226  	}
   227  
   228  	if hints == nil {
   229  		hints = &FileHints{}
   230  	}
   231  
   232  	w := payload
   233  	if signer != nil {
   234  		// If we need to write a signature packet after the literal
   235  		// data then we need to stop literalData from closing
   236  		// encryptedData.
   237  		w = noOpCloser{w}
   238  
   239  	}
   240  	var epochSeconds uint32
   241  	if !hints.ModTime.IsZero() {
   242  		epochSeconds = uint32(hints.ModTime.Unix())
   243  	}
   244  	literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	if signer != nil {
   250  		return signatureWriter{payload, literalData, hash, hash.New(), signer, config}, nil
   251  	}
   252  	return literalData, nil
   253  }
   254  
   255  // Encrypt encrypts a message to a number of recipients and, optionally, signs
   256  // it. hints contains optional information, that is also encrypted, that aids
   257  // the recipients in processing the message. The resulting WriteCloser must
   258  // be closed after the contents of the file have been written.
   259  // If config is nil, sensible defaults will be used.
   260  func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
   261  	if len(to) == 0 {
   262  		return nil, errors.InvalidArgumentError("no encryption recipient provided")
   263  	}
   264  
   265  	// These are the possible ciphers that we'll use for the message.
   266  	candidateCiphers := []uint8{
   267  		uint8(packet.CipherAES128),
   268  		uint8(packet.CipherAES256),
   269  		uint8(packet.CipherCAST5),
   270  	}
   271  	// These are the possible hash functions that we'll use for the signature.
   272  	candidateHashes := []uint8{
   273  		hashToHashId(crypto.SHA256),
   274  		hashToHashId(crypto.SHA384),
   275  		hashToHashId(crypto.SHA512),
   276  		hashToHashId(crypto.SHA1),
   277  		hashToHashId(crypto.RIPEMD160),
   278  	}
   279  	// In the event that a recipient doesn't specify any supported ciphers
   280  	// or hash functions, these are the ones that we assume that every
   281  	// implementation supports.
   282  	defaultCiphers := candidateCiphers[len(candidateCiphers)-1:]
   283  	defaultHashes := candidateHashes[len(candidateHashes)-1:]
   284  
   285  	encryptKeys := make([]Key, len(to))
   286  	for i := range to {
   287  		var ok bool
   288  		encryptKeys[i], ok = to[i].encryptionKey(config.Now())
   289  		if !ok {
   290  			return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
   291  		}
   292  
   293  		sig := to[i].primaryIdentity().SelfSignature
   294  
   295  		preferredSymmetric := sig.PreferredSymmetric
   296  		if len(preferredSymmetric) == 0 {
   297  			preferredSymmetric = defaultCiphers
   298  		}
   299  		preferredHashes := sig.PreferredHash
   300  		if len(preferredHashes) == 0 {
   301  			preferredHashes = defaultHashes
   302  		}
   303  		candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric)
   304  		candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
   305  	}
   306  
   307  	if len(candidateCiphers) == 0 || len(candidateHashes) == 0 {
   308  		return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
   309  	}
   310  
   311  	cipher := packet.CipherFunction(candidateCiphers[0])
   312  	// If the cipher specified by config is a candidate, we'll use that.
   313  	configuredCipher := config.Cipher()
   314  	for _, c := range candidateCiphers {
   315  		cipherFunc := packet.CipherFunction(c)
   316  		if cipherFunc == configuredCipher {
   317  			cipher = cipherFunc
   318  			break
   319  		}
   320  	}
   321  
   322  	symKey := make([]byte, cipher.KeySize())
   323  	if _, err := io.ReadFull(config.Random(), symKey); err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	for _, key := range encryptKeys {
   328  		if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil {
   329  			return nil, err
   330  		}
   331  	}
   332  
   333  	payload, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config)
   334  	if err != nil {
   335  		return
   336  	}
   337  
   338  	return writeAndSign(payload, candidateHashes, signed, hints, config)
   339  }
   340  
   341  // Sign signs a message. The resulting WriteCloser must be closed after the
   342  // contents of the file have been written.  hints contains optional information
   343  // that aids the recipients in processing the message.
   344  // If config is nil, sensible defaults will be used.
   345  func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
   346  	if signed == nil {
   347  		return nil, errors.InvalidArgumentError("no signer provided")
   348  	}
   349  
   350  	// These are the possible hash functions that we'll use for the signature.
   351  	candidateHashes := []uint8{
   352  		hashToHashId(crypto.SHA256),
   353  		hashToHashId(crypto.SHA384),
   354  		hashToHashId(crypto.SHA512),
   355  		hashToHashId(crypto.SHA1),
   356  		hashToHashId(crypto.RIPEMD160),
   357  	}
   358  	defaultHashes := candidateHashes[len(candidateHashes)-1:]
   359  	preferredHashes := signed.primaryIdentity().SelfSignature.PreferredHash
   360  	if len(preferredHashes) == 0 {
   361  		preferredHashes = defaultHashes
   362  	}
   363  	candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
   364  	return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, config)
   365  }
   366  
   367  // signatureWriter hashes the contents of a message while passing it along to
   368  // literalData. When closed, it closes literalData, writes a signature packet
   369  // to encryptedData and then also closes encryptedData.
   370  type signatureWriter struct {
   371  	encryptedData io.WriteCloser
   372  	literalData   io.WriteCloser
   373  	hashType      crypto.Hash
   374  	h             hash.Hash
   375  	signer        *packet.PrivateKey
   376  	config        *packet.Config
   377  }
   378  
   379  func (s signatureWriter) Write(data []byte) (int, error) {
   380  	s.h.Write(data)
   381  	return s.literalData.Write(data)
   382  }
   383  
   384  func (s signatureWriter) Close() error {
   385  	sig := &packet.Signature{
   386  		SigType:      packet.SigTypeBinary,
   387  		PubKeyAlgo:   s.signer.PubKeyAlgo,
   388  		Hash:         s.hashType,
   389  		CreationTime: s.config.Now(),
   390  		IssuerKeyId:  &s.signer.KeyId,
   391  	}
   392  
   393  	if err := sig.Sign(s.h, s.signer, s.config); err != nil {
   394  		return err
   395  	}
   396  	if err := s.literalData.Close(); err != nil {
   397  		return err
   398  	}
   399  	if err := sig.Serialize(s.encryptedData); err != nil {
   400  		return err
   401  	}
   402  	return s.encryptedData.Close()
   403  }
   404  
   405  // noOpCloser is like an io.NopCloser, but for an io.Writer.
   406  // TODO: we have two of these in OpenPGP packages alone. This probably needs
   407  // to be promoted somewhere more common.
   408  type noOpCloser struct {
   409  	w io.Writer
   410  }
   411  
   412  func (c noOpCloser) Write(data []byte) (n int, err error) {
   413  	return c.w.Write(data)
   414  }
   415  
   416  func (c noOpCloser) Close() error {
   417  	return nil
   418  }
   419  

View as plain text