| // 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 openpgp |
| |
| import ( |
| "crypto" |
| "hash" |
| "io" |
| "strconv" |
| "time" |
| |
| "golang.org/x/crypto/openpgp/armor" |
| "golang.org/x/crypto/openpgp/errors" |
| "golang.org/x/crypto/openpgp/packet" |
| "golang.org/x/crypto/openpgp/s2k" |
| ) |
| |
| // DetachSign signs message with the private key from signer (which must |
| // already have been decrypted) and writes the signature to w. |
| // If config is nil, sensible defaults will be used. |
| func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { |
| return detachSign(w, signer, message, packet.SigTypeBinary, config) |
| } |
| |
| // ArmoredDetachSign signs message with the private key from signer (which |
| // must already have been decrypted) and writes an armored signature to w. |
| // If config is nil, sensible defaults will be used. |
| func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) { |
| return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config) |
| } |
| |
| // DetachSignText signs message (after canonicalising the line endings) with |
| // the private key from signer (which must already have been decrypted) and |
| // writes the signature to w. |
| // If config is nil, sensible defaults will be used. |
| func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { |
| return detachSign(w, signer, message, packet.SigTypeText, config) |
| } |
| |
| // ArmoredDetachSignText signs message (after canonicalising the line endings) |
| // with the private key from signer (which must already have been decrypted) |
| // and writes an armored signature to w. |
| // If config is nil, sensible defaults will be used. |
| func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { |
| return armoredDetachSign(w, signer, message, packet.SigTypeText, config) |
| } |
| |
| func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { |
| out, err := armor.Encode(w, SignatureType, nil) |
| if err != nil { |
| return |
| } |
| err = detachSign(out, signer, message, sigType, config) |
| if err != nil { |
| return |
| } |
| return out.Close() |
| } |
| |
| func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { |
| if signer.PrivateKey == nil { |
| return errors.InvalidArgumentError("signing key doesn't have a private key") |
| } |
| if signer.PrivateKey.Encrypted { |
| return errors.InvalidArgumentError("signing key is encrypted") |
| } |
| |
| sig := new(packet.Signature) |
| sig.SigType = sigType |
| sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo |
| sig.Hash = config.Hash() |
| sig.CreationTime = config.Now() |
| sig.IssuerKeyId = &signer.PrivateKey.KeyId |
| |
| h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) |
| if err != nil { |
| return |
| } |
| io.Copy(wrappedHash, message) |
| |
| err = sig.Sign(h, signer.PrivateKey, config) |
| if err != nil { |
| return |
| } |
| |
| return sig.Serialize(w) |
| } |
| |
| // FileHints contains metadata about encrypted files. This metadata is, itself, |
| // encrypted. |
| type FileHints struct { |
| // IsBinary can be set to hint that the contents are binary data. |
| IsBinary bool |
| // FileName hints at the name of the file that should be written. It's |
| // truncated to 255 bytes if longer. It may be empty to suggest that the |
| // file should not be written to disk. It may be equal to "_CONSOLE" to |
| // suggest the data should not be written to disk. |
| FileName string |
| // ModTime contains the modification time of the file, or the zero time if not applicable. |
| ModTime time.Time |
| } |
| |
| // SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. |
| // The resulting WriteCloser must be closed after the contents of the file have |
| // been written. |
| // If config is nil, sensible defaults will be used. |
| func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { |
| if hints == nil { |
| hints = &FileHints{} |
| } |
| |
| key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config) |
| if err != nil { |
| return |
| } |
| w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config) |
| if err != nil { |
| return |
| } |
| |
| literaldata := w |
| if algo := config.Compression(); algo != packet.CompressionNone { |
| var compConfig *packet.CompressionConfig |
| if config != nil { |
| compConfig = config.CompressionConfig |
| } |
| literaldata, err = packet.SerializeCompressed(w, algo, compConfig) |
| if err != nil { |
| return |
| } |
| } |
| |
| var epochSeconds uint32 |
| if !hints.ModTime.IsZero() { |
| epochSeconds = uint32(hints.ModTime.Unix()) |
| } |
| return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds) |
| } |
| |
| // intersectPreferences mutates and returns a prefix of a that contains only |
| // the values in the intersection of a and b. The order of a is preserved. |
| func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { |
| var j int |
| for _, v := range a { |
| for _, v2 := range b { |
| if v == v2 { |
| a[j] = v |
| j++ |
| break |
| } |
| } |
| } |
| |
| return a[:j] |
| } |
| |
| func hashToHashId(h crypto.Hash) uint8 { |
| v, ok := s2k.HashToHashId(h) |
| if !ok { |
| panic("tried to convert unknown hash") |
| } |
| return v |
| } |
| |
| // writeAndSign writes the data as a payload package and, optionally, signs |
| // it. hints contains optional information, that is also encrypted, |
| // that aids the recipients in processing the message. The resulting |
| // WriteCloser must be closed after the contents of the file have been |
| // written. If config is nil, sensible defaults will be used. |
| func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { |
| var signer *packet.PrivateKey |
| if signed != nil { |
| signKey, ok := signed.signingKey(config.Now()) |
| if !ok { |
| return nil, errors.InvalidArgumentError("no valid signing keys") |
| } |
| signer = signKey.PrivateKey |
| if signer == nil { |
| return nil, errors.InvalidArgumentError("no private key in signing key") |
| } |
| if signer.Encrypted { |
| return nil, errors.InvalidArgumentError("signing key must be decrypted") |
| } |
| } |
| |
| var hash crypto.Hash |
| for _, hashId := range candidateHashes { |
| if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() { |
| hash = h |
| break |
| } |
| } |
| |
| // If the hash specified by config is a candidate, we'll use that. |
| if configuredHash := config.Hash(); configuredHash.Available() { |
| for _, hashId := range candidateHashes { |
| if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash { |
| hash = h |
| break |
| } |
| } |
| } |
| |
| if hash == 0 { |
| hashId := candidateHashes[0] |
| name, ok := s2k.HashIdToString(hashId) |
| if !ok { |
| name = "#" + strconv.Itoa(int(hashId)) |
| } |
| return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") |
| } |
| |
| if signer != nil { |
| ops := &packet.OnePassSignature{ |
| SigType: packet.SigTypeBinary, |
| Hash: hash, |
| PubKeyAlgo: signer.PubKeyAlgo, |
| KeyId: signer.KeyId, |
| IsLast: true, |
| } |
| if err := ops.Serialize(payload); err != nil { |
| return nil, err |
| } |
| } |
| |
| if hints == nil { |
| hints = &FileHints{} |
| } |
| |
| w := payload |
| if signer != nil { |
| // If we need to write a signature packet after the literal |
| // data then we need to stop literalData from closing |
| // encryptedData. |
| w = noOpCloser{w} |
| |
| } |
| var epochSeconds uint32 |
| if !hints.ModTime.IsZero() { |
| epochSeconds = uint32(hints.ModTime.Unix()) |
| } |
| literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) |
| if err != nil { |
| return nil, err |
| } |
| |
| if signer != nil { |
| return signatureWriter{payload, literalData, hash, hash.New(), signer, config}, nil |
| } |
| return literalData, nil |
| } |
| |
| // Encrypt encrypts a message to a number of recipients and, optionally, signs |
| // it. hints contains optional information, that is also encrypted, that aids |
| // the recipients in processing the message. The resulting WriteCloser must |
| // be closed after the contents of the file have been written. |
| // If config is nil, sensible defaults will be used. |
| func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { |
| if len(to) == 0 { |
| return nil, errors.InvalidArgumentError("no encryption recipient provided") |
| } |
| |
| // These are the possible ciphers that we'll use for the message. |
| candidateCiphers := []uint8{ |
| uint8(packet.CipherAES128), |
| uint8(packet.CipherAES256), |
| uint8(packet.CipherCAST5), |
| } |
| // These are the possible hash functions that we'll use for the signature. |
| candidateHashes := []uint8{ |
| hashToHashId(crypto.SHA256), |
| hashToHashId(crypto.SHA384), |
| hashToHashId(crypto.SHA512), |
| hashToHashId(crypto.SHA1), |
| hashToHashId(crypto.RIPEMD160), |
| } |
| // In the event that a recipient doesn't specify any supported ciphers |
| // or hash functions, these are the ones that we assume that every |
| // implementation supports. |
| defaultCiphers := candidateCiphers[len(candidateCiphers)-1:] |
| defaultHashes := candidateHashes[len(candidateHashes)-1:] |
| |
| encryptKeys := make([]Key, len(to)) |
| for i := range to { |
| var ok bool |
| encryptKeys[i], ok = to[i].encryptionKey(config.Now()) |
| if !ok { |
| return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys") |
| } |
| |
| sig := to[i].primaryIdentity().SelfSignature |
| |
| preferredSymmetric := sig.PreferredSymmetric |
| if len(preferredSymmetric) == 0 { |
| preferredSymmetric = defaultCiphers |
| } |
| preferredHashes := sig.PreferredHash |
| if len(preferredHashes) == 0 { |
| preferredHashes = defaultHashes |
| } |
| candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric) |
| candidateHashes = intersectPreferences(candidateHashes, preferredHashes) |
| } |
| |
| if len(candidateCiphers) == 0 || len(candidateHashes) == 0 { |
| return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms") |
| } |
| |
| cipher := packet.CipherFunction(candidateCiphers[0]) |
| // If the cipher specified by config is a candidate, we'll use that. |
| configuredCipher := config.Cipher() |
| for _, c := range candidateCiphers { |
| cipherFunc := packet.CipherFunction(c) |
| if cipherFunc == configuredCipher { |
| cipher = cipherFunc |
| break |
| } |
| } |
| |
| symKey := make([]byte, cipher.KeySize()) |
| if _, err := io.ReadFull(config.Random(), symKey); err != nil { |
| return nil, err |
| } |
| |
| for _, key := range encryptKeys { |
| if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil { |
| return nil, err |
| } |
| } |
| |
| payload, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config) |
| if err != nil { |
| return |
| } |
| |
| return writeAndSign(payload, candidateHashes, signed, hints, config) |
| } |
| |
| // Sign signs a message. The resulting WriteCloser must be closed after the |
| // contents of the file have been written. hints contains optional information |
| // that aids the recipients in processing the message. |
| // If config is nil, sensible defaults will be used. |
| func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) { |
| if signed == nil { |
| return nil, errors.InvalidArgumentError("no signer provided") |
| } |
| |
| // These are the possible hash functions that we'll use for the signature. |
| candidateHashes := []uint8{ |
| hashToHashId(crypto.SHA256), |
| hashToHashId(crypto.SHA384), |
| hashToHashId(crypto.SHA512), |
| hashToHashId(crypto.SHA1), |
| hashToHashId(crypto.RIPEMD160), |
| } |
| defaultHashes := candidateHashes[len(candidateHashes)-1:] |
| preferredHashes := signed.primaryIdentity().SelfSignature.PreferredHash |
| if len(preferredHashes) == 0 { |
| preferredHashes = defaultHashes |
| } |
| candidateHashes = intersectPreferences(candidateHashes, preferredHashes) |
| return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, config) |
| } |
| |
| // signatureWriter hashes the contents of a message while passing it along to |
| // literalData. When closed, it closes literalData, writes a signature packet |
| // to encryptedData and then also closes encryptedData. |
| type signatureWriter struct { |
| encryptedData io.WriteCloser |
| literalData io.WriteCloser |
| hashType crypto.Hash |
| h hash.Hash |
| signer *packet.PrivateKey |
| config *packet.Config |
| } |
| |
| func (s signatureWriter) Write(data []byte) (int, error) { |
| s.h.Write(data) |
| return s.literalData.Write(data) |
| } |
| |
| func (s signatureWriter) Close() error { |
| sig := &packet.Signature{ |
| SigType: packet.SigTypeBinary, |
| PubKeyAlgo: s.signer.PubKeyAlgo, |
| Hash: s.hashType, |
| CreationTime: s.config.Now(), |
| IssuerKeyId: &s.signer.KeyId, |
| } |
| |
| if err := sig.Sign(s.h, s.signer, s.config); err != nil { |
| return err |
| } |
| if err := s.literalData.Close(); err != nil { |
| return err |
| } |
| if err := sig.Serialize(s.encryptedData); err != nil { |
| return err |
| } |
| return s.encryptedData.Close() |
| } |
| |
| // noOpCloser is like an io.NopCloser, but for an io.Writer. |
| // TODO: we have two of these in OpenPGP packages alone. This probably needs |
| // to be promoted somewhere more common. |
| type noOpCloser struct { |
| w io.Writer |
| } |
| |
| func (c noOpCloser) Write(data []byte) (n int, err error) { |
| return c.w.Write(data) |
| } |
| |
| func (c noOpCloser) Close() error { |
| return nil |
| } |