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 5package openpgp 6 7import ( 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. 23func 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. 30func 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. 38func 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. 46func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { 47 return armoredDetachSign(w, signer, message, packet.SigTypeText, config) 48} 49 50func 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 62func 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. 93type 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. 109func 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. 144func 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 159func 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. 172func 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. 260func 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. 345func 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. 370type 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 379func (s signatureWriter) Write(data []byte) (int, error) { 380 s.h.Write(data) 381 return s.literalData.Write(data) 382} 383 384func (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 ioutil.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. 408type noOpCloser struct { 409 w io.Writer 410} 411 412func (c noOpCloser) Write(data []byte) (n int, err error) { 413 return c.w.Write(data) 414} 415 416func (c noOpCloser) Close() error { 417 return nil 418} 419