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 "github.com/keybase/go-crypto/openpgp/armor" 15 "github.com/keybase/go-crypto/openpgp/errors" 16 "github.com/keybase/go-crypto/openpgp/packet" 17 "github.com/keybase/go-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 62// SignWithSigner signs the message of type sigType with s and writes the 63// signature to w. 64// If config is nil, sensible defaults will be used. 65func SignWithSigner(s packet.Signer, w io.Writer, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { 66 keyId := s.KeyId() 67 sig := new(packet.Signature) 68 sig.SigType = sigType 69 sig.PubKeyAlgo = s.PublicKeyAlgo() 70 sig.Hash = config.Hash() 71 sig.CreationTime = config.Now() 72 sig.IssuerKeyId = &keyId 73 74 s.Reset() 75 76 wrapped := s.(hash.Hash) 77 78 if sigType == packet.SigTypeText { 79 wrapped = NewCanonicalTextHash(s) 80 } 81 82 io.Copy(wrapped, message) 83 84 err = sig.Sign(s, nil, config) 85 if err != nil { 86 return 87 } 88 89 err = sig.Serialize(w) 90 91 return 92} 93 94func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { 95 signerSubkey, ok := signer.signingKey(config.Now()) 96 if !ok { 97 err = errors.InvalidArgumentError("no valid signing keys") 98 return 99 } 100 if signerSubkey.PrivateKey == nil { 101 return errors.InvalidArgumentError("signing key doesn't have a private key") 102 } 103 if signerSubkey.PrivateKey.Encrypted { 104 return errors.InvalidArgumentError("signing key is encrypted") 105 } 106 107 sig := new(packet.Signature) 108 sig.SigType = sigType 109 sig.PubKeyAlgo = signerSubkey.PrivateKey.PubKeyAlgo 110 sig.Hash = config.Hash() 111 sig.CreationTime = config.Now() 112 sig.IssuerKeyId = &signerSubkey.PrivateKey.KeyId 113 114 h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) 115 if err != nil { 116 return 117 } 118 io.Copy(wrappedHash, message) 119 120 err = sig.Sign(h, signerSubkey.PrivateKey, config) 121 if err != nil { 122 return 123 } 124 125 return sig.Serialize(w) 126} 127 128// FileHints contains metadata about encrypted files. This metadata is, itself, 129// encrypted. 130type FileHints struct { 131 // IsBinary can be set to hint that the contents are binary data. 132 IsBinary bool 133 // FileName hints at the name of the file that should be written. It's 134 // truncated to 255 bytes if longer. It may be empty to suggest that the 135 // file should not be written to disk. It may be equal to "_CONSOLE" to 136 // suggest the data should not be written to disk. 137 FileName string 138 // ModTime contains the modification time of the file, or the zero time if not applicable. 139 ModTime time.Time 140} 141 142// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. 143// The resulting WriteCloser must be closed after the contents of the file have 144// been written. 145// If config is nil, sensible defaults will be used. 146func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { 147 if hints == nil { 148 hints = &FileHints{} 149 } 150 151 key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config) 152 if err != nil { 153 return 154 } 155 w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config) 156 if err != nil { 157 return 158 } 159 160 literaldata := w 161 if algo := config.Compression(); algo != packet.CompressionNone { 162 var compConfig *packet.CompressionConfig 163 if config != nil { 164 compConfig = config.CompressionConfig 165 } 166 literaldata, err = packet.SerializeCompressed(w, algo, compConfig) 167 if err != nil { 168 return 169 } 170 } 171 172 var epochSeconds uint32 173 if !hints.ModTime.IsZero() { 174 epochSeconds = uint32(hints.ModTime.Unix()) 175 } 176 return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds) 177} 178 179// intersectPreferences mutates and returns a prefix of a that contains only 180// the values in the intersection of a and b. The order of a is preserved. 181func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { 182 var j int 183 for _, v := range a { 184 for _, v2 := range b { 185 if v == v2 { 186 a[j] = v 187 j++ 188 break 189 } 190 } 191 } 192 193 return a[:j] 194} 195 196func hashToHashId(h crypto.Hash) uint8 { 197 v, ok := s2k.HashToHashId(h) 198 if !ok { 199 panic("tried to convert unknown hash") 200 } 201 return v 202} 203 204// Encrypt encrypts a message to a number of recipients and, optionally, signs 205// it. hints contains optional information, that is also encrypted, that aids 206// the recipients in processing the message. The resulting WriteCloser must 207// be closed after the contents of the file have been written. 208// If config is nil, sensible defaults will be used. 209func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { 210 var signer *packet.PrivateKey 211 if signed != nil { 212 signKey, ok := signed.signingKey(config.Now()) 213 if !ok { 214 return nil, errors.InvalidArgumentError("no valid signing keys") 215 } 216 signer = signKey.PrivateKey 217 if signer == nil { 218 return nil, errors.InvalidArgumentError("no private key in signing key") 219 } 220 if signer.Encrypted { 221 return nil, errors.InvalidArgumentError("signing key must be decrypted") 222 } 223 } 224 225 // These are the possible ciphers that we'll use for the message. 226 candidateCiphers := []uint8{ 227 uint8(packet.CipherAES128), 228 uint8(packet.CipherAES256), 229 uint8(packet.CipherCAST5), 230 } 231 // These are the possible hash functions that we'll use for the signature. 232 candidateHashes := []uint8{ 233 hashToHashId(crypto.SHA256), 234 hashToHashId(crypto.SHA512), 235 hashToHashId(crypto.SHA1), 236 hashToHashId(crypto.RIPEMD160), 237 } 238 239 // If no preferences were specified, assume something safe and reasonable. 240 defaultCiphers := []uint8{ 241 uint8(packet.CipherAES128), 242 uint8(packet.CipherAES192), 243 uint8(packet.CipherAES256), 244 uint8(packet.CipherCAST5), 245 } 246 247 defaultHashes := []uint8{ 248 hashToHashId(crypto.SHA256), 249 hashToHashId(crypto.SHA512), 250 hashToHashId(crypto.RIPEMD160), 251 } 252 253 encryptKeys := make([]Key, len(to)) 254 for i := range to { 255 var ok bool 256 encryptKeys[i], ok = to[i].encryptionKey(config.Now()) 257 if !ok { 258 return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys") 259 } 260 261 sig := to[i].primaryIdentity().SelfSignature 262 263 preferredSymmetric := sig.PreferredSymmetric 264 if len(preferredSymmetric) == 0 { 265 preferredSymmetric = defaultCiphers 266 } 267 preferredHashes := sig.PreferredHash 268 if len(preferredHashes) == 0 { 269 preferredHashes = defaultHashes 270 } 271 candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric) 272 candidateHashes = intersectPreferences(candidateHashes, preferredHashes) 273 } 274 275 if len(candidateCiphers) == 0 { 276 return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common ciphers") 277 } 278 if len(candidateHashes) == 0 { 279 return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common hashes") 280 } 281 282 cipher := packet.CipherFunction(candidateCiphers[0]) 283 // If the cipher specifed by config is a candidate, we'll use that. 284 configuredCipher := config.Cipher() 285 for _, c := range candidateCiphers { 286 cipherFunc := packet.CipherFunction(c) 287 if cipherFunc == configuredCipher { 288 cipher = cipherFunc 289 break 290 } 291 } 292 293 var hash crypto.Hash 294 for _, hashId := range candidateHashes { 295 if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() { 296 hash = h 297 break 298 } 299 } 300 301 // If the hash specified by config is a candidate, we'll use that. 302 if configuredHash := config.Hash(); configuredHash.Available() { 303 for _, hashId := range candidateHashes { 304 if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash { 305 hash = h 306 break 307 } 308 } 309 } 310 311 if hash == 0 { 312 hashId := candidateHashes[0] 313 name, ok := s2k.HashIdToString(hashId) 314 if !ok { 315 name = "#" + strconv.Itoa(int(hashId)) 316 } 317 return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") 318 } 319 320 symKey := make([]byte, cipher.KeySize()) 321 if _, err := io.ReadFull(config.Random(), symKey); err != nil { 322 return nil, err 323 } 324 325 for _, key := range encryptKeys { 326 if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil { 327 return nil, err 328 } 329 } 330 331 encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config) 332 if err != nil { 333 return 334 } 335 336 if signer != nil { 337 ops := &packet.OnePassSignature{ 338 SigType: packet.SigTypeBinary, 339 Hash: hash, 340 PubKeyAlgo: signer.PubKeyAlgo, 341 KeyId: signer.KeyId, 342 IsLast: true, 343 } 344 if err := ops.Serialize(encryptedData); err != nil { 345 return nil, err 346 } 347 } 348 349 if hints == nil { 350 hints = &FileHints{} 351 } 352 353 w := encryptedData 354 if signer != nil { 355 // If we need to write a signature packet after the literal 356 // data then we need to stop literalData from closing 357 // encryptedData. 358 w = noOpCloser{encryptedData} 359 360 } 361 var epochSeconds uint32 362 if !hints.ModTime.IsZero() { 363 epochSeconds = uint32(hints.ModTime.Unix()) 364 } 365 literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) 366 if err != nil { 367 return nil, err 368 } 369 370 if signer != nil { 371 return signatureWriter{encryptedData, literalData, hash, hash.New(), signer, config}, nil 372 } 373 return literalData, nil 374} 375 376// signatureWriter hashes the contents of a message while passing it along to 377// literalData. When closed, it closes literalData, writes a signature packet 378// to encryptedData and then also closes encryptedData. 379type signatureWriter struct { 380 encryptedData io.WriteCloser 381 literalData io.WriteCloser 382 hashType crypto.Hash 383 h hash.Hash 384 signer *packet.PrivateKey 385 config *packet.Config 386} 387 388func (s signatureWriter) Write(data []byte) (int, error) { 389 s.h.Write(data) 390 return s.literalData.Write(data) 391} 392 393func (s signatureWriter) Close() error { 394 sig := &packet.Signature{ 395 SigType: packet.SigTypeBinary, 396 PubKeyAlgo: s.signer.PubKeyAlgo, 397 Hash: s.hashType, 398 CreationTime: s.config.Now(), 399 IssuerKeyId: &s.signer.KeyId, 400 } 401 402 if err := sig.Sign(s.h, s.signer, s.config); err != nil { 403 return err 404 } 405 if err := s.literalData.Close(); err != nil { 406 return err 407 } 408 if err := sig.Serialize(s.encryptedData); err != nil { 409 return err 410 } 411 return s.encryptedData.Close() 412} 413 414// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. 415// TODO: we have two of these in OpenPGP packages alone. This probably needs 416// to be promoted somewhere more common. 417type noOpCloser struct { 418 w io.Writer 419} 420 421func (c noOpCloser) Write(data []byte) (n int, err error) { 422 return c.w.Write(data) 423} 424 425func (c noOpCloser) Close() error { 426 return nil 427} 428 429// AttachedSign is like openpgp.Encrypt (as in p.crypto/openpgp/write.go), but 430// don't encrypt at all, just sign the literal unencrypted data. 431// Unfortunately we need to duplicate some code here that's already 432// in write.go 433func AttachedSign(out io.WriteCloser, signed Entity, hints *FileHints, 434 config *packet.Config) (in io.WriteCloser, err error) { 435 436 if hints == nil { 437 hints = &FileHints{} 438 } 439 440 if config == nil { 441 config = &packet.Config{} 442 } 443 444 var signer *packet.PrivateKey 445 446 signKey, ok := signed.signingKey(config.Now()) 447 if !ok { 448 err = errors.InvalidArgumentError("no valid signing keys") 449 return 450 } 451 signer = signKey.PrivateKey 452 if signer == nil { 453 err = errors.InvalidArgumentError("no valid signing keys") 454 return 455 } 456 if signer.Encrypted { 457 err = errors.InvalidArgumentError("signing key must be decrypted") 458 return 459 } 460 461 if algo := config.Compression(); algo != packet.CompressionNone { 462 var compConfig *packet.CompressionConfig 463 if config != nil { 464 compConfig = config.CompressionConfig 465 } 466 out, err = packet.SerializeCompressed(out, algo, compConfig) 467 if err != nil { 468 return 469 } 470 } 471 472 hasher := config.Hash() // defaults to SHA-256 473 474 ops := &packet.OnePassSignature{ 475 SigType: packet.SigTypeBinary, 476 Hash: hasher, 477 PubKeyAlgo: signer.PubKeyAlgo, 478 KeyId: signer.KeyId, 479 IsLast: true, 480 } 481 482 if err = ops.Serialize(out); err != nil { 483 return 484 } 485 486 var epochSeconds uint32 487 if !hints.ModTime.IsZero() { 488 epochSeconds = uint32(hints.ModTime.Unix()) 489 } 490 491 // We don't want the literal serializer to closer the output stream 492 // since we're going to need to write to it when we finish up the 493 // signature stuff. 494 in, err = packet.SerializeLiteral(noOpCloser{out}, hints.IsBinary, hints.FileName, epochSeconds) 495 496 if err != nil { 497 return 498 } 499 500 // If we need to write a signature packet after the literal 501 // data then we need to stop literalData from closing 502 // encryptedData. 503 in = signatureWriter{out, in, hasher, hasher.New(), signer, config} 504 505 return 506} 507