2019-10-28 21:10:20 +00:00
// 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"
2021-08-30 15:18:50 +00:00
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/go-crypto/openpgp/s2k"
2019-10-28 21:10:20 +00:00
)
// 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 ) {
2021-08-30 15:18:50 +00:00
signingKey , ok := signer . SigningKeyById ( config . Now ( ) , config . SigningKey ( ) )
if ! ok {
return errors . InvalidArgumentError ( "no valid signing keys" )
}
if signingKey . PrivateKey == nil {
2019-10-28 21:10:20 +00:00
return errors . InvalidArgumentError ( "signing key doesn't have a private key" )
}
2021-08-30 15:18:50 +00:00
if signingKey . PrivateKey . Encrypted {
2019-10-28 21:10:20 +00:00
return errors . InvalidArgumentError ( "signing key is encrypted" )
}
sig := new ( packet . Signature )
sig . SigType = sigType
2021-08-30 15:18:50 +00:00
sig . PubKeyAlgo = signingKey . PrivateKey . PubKeyAlgo
2019-10-28 21:10:20 +00:00
sig . Hash = config . Hash ( )
sig . CreationTime = config . Now ( )
2021-08-30 15:18:50 +00:00
sigLifetimeSecs := config . SigLifetime ( )
sig . SigLifetimeSecs = & sigLifetimeSecs
sig . IssuerKeyId = & signingKey . PrivateKey . KeyId
2019-10-28 21:10:20 +00:00
h , wrappedHash , err := hashForSignature ( sig . Hash , sig . SigType )
if err != nil {
return
}
2021-08-30 15:18:50 +00:00
if _ , err = io . Copy ( wrappedHash , message ) ; err != nil {
return err
}
2019-10-28 21:10:20 +00:00
2021-08-30 15:18:50 +00:00
err = sig . Sign ( h , signingKey . PrivateKey , config )
2019-10-28 21:10:20 +00:00
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
}
2021-08-30 15:18:50 +00:00
var w io . WriteCloser
if config . AEAD ( ) != nil {
w , err = packet . SerializeAEADEncrypted ( ciphertext , key , config . Cipher ( ) , config . AEAD ( ) . Mode ( ) , config )
if err != nil {
return
}
} else {
w , err = packet . SerializeSymmetricallyEncrypted ( ciphertext , config . Cipher ( ) , key , config )
if err != nil {
return
}
2019-10-28 21:10:20 +00:00
}
2021-08-30 15:18:50 +00:00
literalData := w
2019-10-28 21:10:20 +00:00
if algo := config . Compression ( ) ; algo != packet . CompressionNone {
var compConfig * packet . CompressionConfig
if config != nil {
compConfig = config . CompressionConfig
}
2021-08-30 15:18:50 +00:00
literalData , err = packet . SerializeCompressed ( w , algo , compConfig )
2019-10-28 21:10:20 +00:00
if err != nil {
return
}
}
var epochSeconds uint32
if ! hints . ModTime . IsZero ( ) {
epochSeconds = uint32 ( hints . ModTime . Unix ( ) )
}
2021-08-30 15:18:50 +00:00
return packet . SerializeLiteral ( literalData , hints . IsBinary , hints . FileName , epochSeconds )
2019-10-28 21:10:20 +00:00
}
// 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
}
2021-08-30 15:18:50 +00:00
// EncryptText encrypts a message to a number of recipients and, optionally,
// signs it. Optional information is contained in 'hints', 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. The signing is done in text mode.
func EncryptText ( ciphertext io . Writer , to [ ] * Entity , signed * Entity , hints * FileHints , config * packet . Config ) ( plaintext io . WriteCloser , err error ) {
return encrypt ( ciphertext , ciphertext , to , signed , hints , packet . SigTypeText , config )
}
// 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 ) {
return encrypt ( ciphertext , ciphertext , to , signed , hints , packet . SigTypeBinary , config )
}
// EncryptSplit 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 EncryptSplit ( keyWriter io . Writer , dataWriter io . Writer , to [ ] * Entity , signed * Entity , hints * FileHints , config * packet . Config ) ( plaintext io . WriteCloser , err error ) {
return encrypt ( keyWriter , dataWriter , to , signed , hints , packet . SigTypeBinary , config )
}
// EncryptTextSplit 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 EncryptTextSplit ( keyWriter io . Writer , dataWriter io . Writer , to [ ] * Entity , signed * Entity , hints * FileHints , config * packet . Config ) ( plaintext io . WriteCloser , err error ) {
return encrypt ( keyWriter , dataWriter , to , signed , hints , packet . SigTypeText , config )
}
2019-10-28 21:10:20 +00:00
// 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.
2021-08-30 15:18:50 +00:00
func writeAndSign ( payload io . WriteCloser , candidateHashes [ ] uint8 , signed * Entity , hints * FileHints , sigType packet . SignatureType , config * packet . Config ) ( plaintext io . WriteCloser , err error ) {
2019-10-28 21:10:20 +00:00
var signer * packet . PrivateKey
if signed != nil {
2021-08-30 15:18:50 +00:00
signKey , ok := signed . SigningKeyById ( config . Now ( ) , config . SigningKey ( ) )
2019-10-28 21:10:20 +00:00
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 {
2021-08-30 15:18:50 +00:00
SigType : sigType ,
2019-10-28 21:10:20 +00:00
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 {
2021-08-30 15:18:50 +00:00
h , wrappedHash , err := hashForSignature ( hash , sigType )
if err != nil {
return nil , err
}
metadata := & packet . LiteralData {
Format : 't' ,
FileName : hints . FileName ,
Time : epochSeconds ,
}
if hints . IsBinary {
metadata . Format = 'b'
}
return signatureWriter { payload , literalData , hash , wrappedHash , h , signer , sigType , config , metadata } , nil
2019-10-28 21:10:20 +00:00
}
return literalData , nil
}
2021-08-30 15:18:50 +00:00
// encrypt encrypts a message to a number of recipients and, optionally, signs
2019-10-28 21:10:20 +00:00
// 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.
2021-08-30 15:18:50 +00:00
func encrypt ( keyWriter io . Writer , dataWriter io . Writer , to [ ] * Entity , signed * Entity , hints * FileHints , sigType packet . SignatureType , config * packet . Config ) ( plaintext io . WriteCloser , err error ) {
2019-10-28 21:10:20 +00:00
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 ) ,
}
2021-08-30 15:18:50 +00:00
candidateAeadModes := [ ] uint8 {
uint8 ( packet . AEADModeEAX ) ,
uint8 ( packet . AEADModeOCB ) ,
uint8 ( packet . AEADModeExperimentalGCM ) ,
}
candidateCompression := [ ] uint8 {
uint8 ( packet . CompressionNone ) ,
uint8 ( packet . CompressionZIP ) ,
uint8 ( packet . CompressionZLIB ) ,
}
2019-10-28 21:10:20 +00:00
// 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.
2021-08-30 15:18:50 +00:00
defaultCiphers := candidateCiphers [ 0 : 1 ]
defaultHashes := candidateHashes [ 0 : 1 ]
defaultAeadModes := candidateAeadModes [ 0 : 1 ]
defaultCompression := candidateCompression [ 0 : 1 ]
2019-10-28 21:10:20 +00:00
encryptKeys := make ( [ ] Key , len ( to ) )
2021-08-30 15:18:50 +00:00
// AEAD is used only if every key supports it.
aeadSupported := true
2019-10-28 21:10:20 +00:00
for i := range to {
var ok bool
2021-08-30 15:18:50 +00:00
encryptKeys [ i ] , ok = to [ i ] . EncryptionKey ( config . Now ( ) )
2019-10-28 21:10:20 +00:00
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" )
}
2021-08-30 15:18:50 +00:00
sig := to [ i ] . PrimaryIdentity ( ) . SelfSignature
if sig . AEAD == false {
aeadSupported = false
}
2019-10-28 21:10:20 +00:00
preferredSymmetric := sig . PreferredSymmetric
if len ( preferredSymmetric ) == 0 {
preferredSymmetric = defaultCiphers
}
preferredHashes := sig . PreferredHash
if len ( preferredHashes ) == 0 {
preferredHashes = defaultHashes
}
2021-08-30 15:18:50 +00:00
preferredAeadModes := sig . PreferredAEAD
if len ( preferredAeadModes ) == 0 {
preferredAeadModes = defaultAeadModes
}
preferredCompression := sig . PreferredCompression
if len ( preferredCompression ) == 0 {
preferredCompression = defaultCompression
}
2019-10-28 21:10:20 +00:00
candidateCiphers = intersectPreferences ( candidateCiphers , preferredSymmetric )
candidateHashes = intersectPreferences ( candidateHashes , preferredHashes )
2021-08-30 15:18:50 +00:00
candidateAeadModes = intersectPreferences ( candidateAeadModes , preferredAeadModes )
candidateCompression = intersectPreferences ( candidateCompression , preferredCompression )
2019-10-28 21:10:20 +00:00
}
2021-08-30 15:18:50 +00:00
if len ( candidateCiphers ) == 0 || len ( candidateHashes ) == 0 || len ( candidateAeadModes ) == 0 {
2019-10-28 21:10:20 +00:00
return nil , errors . InvalidArgumentError ( "cannot encrypt because recipient set shares no common algorithms" )
}
cipher := packet . CipherFunction ( candidateCiphers [ 0 ] )
2021-08-30 15:18:50 +00:00
mode := packet . AEADMode ( candidateAeadModes [ 0 ] )
2019-10-28 21:10:20 +00:00
// 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 {
2021-08-30 15:18:50 +00:00
if err := packet . SerializeEncryptedKey ( keyWriter , key . PublicKey , cipher , symKey , config ) ; err != nil {
2019-10-28 21:10:20 +00:00
return nil , err
}
}
2021-08-30 15:18:50 +00:00
var payload io . WriteCloser
if aeadSupported {
payload , err = packet . SerializeAEADEncrypted ( dataWriter , symKey , cipher , mode , config )
if err != nil {
return
}
} else {
payload , err = packet . SerializeSymmetricallyEncrypted ( dataWriter , cipher , symKey , config )
if err != nil {
return
}
}
payload , err = handleCompression ( payload , candidateCompression , config )
2019-10-28 21:10:20 +00:00
if err != nil {
2021-08-30 15:18:50 +00:00
return nil , err
2019-10-28 21:10:20 +00:00
}
2021-08-30 15:18:50 +00:00
return writeAndSign ( payload , candidateHashes , signed , hints , sigType , config )
2019-10-28 21:10:20 +00:00
}
// 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 ) ,
}
2021-08-30 15:18:50 +00:00
defaultHashes := candidateHashes [ 0 : 1 ]
preferredHashes := signed . PrimaryIdentity ( ) . SelfSignature . PreferredHash
2019-10-28 21:10:20 +00:00
if len ( preferredHashes ) == 0 {
preferredHashes = defaultHashes
}
candidateHashes = intersectPreferences ( candidateHashes , preferredHashes )
2021-08-30 15:18:50 +00:00
if len ( candidateHashes ) == 0 {
return nil , errors . InvalidArgumentError ( "cannot sign because signing key shares no common algorithms with candidate hashes" )
}
return writeAndSign ( noOpCloser { output } , candidateHashes , signed , hints , packet . SigTypeBinary , config )
2019-10-28 21:10:20 +00:00
}
// 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
2021-08-30 15:18:50 +00:00
wrappedHash hash . Hash
2019-10-28 21:10:20 +00:00
h hash . Hash
signer * packet . PrivateKey
2021-08-30 15:18:50 +00:00
sigType packet . SignatureType
2019-10-28 21:10:20 +00:00
config * packet . Config
2021-08-30 15:18:50 +00:00
metadata * packet . LiteralData // V5 signatures protect document metadata
2019-10-28 21:10:20 +00:00
}
func ( s signatureWriter ) Write ( data [ ] byte ) ( int , error ) {
2021-08-30 15:18:50 +00:00
s . wrappedHash . Write ( data )
switch s . sigType {
case packet . SigTypeBinary :
return s . literalData . Write ( data )
case packet . SigTypeText :
flag := 0
return writeCanonical ( s . literalData , data , & flag )
}
return 0 , errors . UnsupportedError ( "unsupported signature type: " + strconv . Itoa ( int ( s . sigType ) ) )
2019-10-28 21:10:20 +00:00
}
func ( s signatureWriter ) Close ( ) error {
sig := & packet . Signature {
2021-08-30 15:18:50 +00:00
Version : s . signer . Version ,
SigType : s . sigType ,
2019-10-28 21:10:20 +00:00
PubKeyAlgo : s . signer . PubKeyAlgo ,
Hash : s . hashType ,
CreationTime : s . config . Now ( ) ,
IssuerKeyId : & s . signer . KeyId ,
2021-08-30 15:18:50 +00:00
Metadata : s . metadata ,
2019-10-28 21:10:20 +00:00
}
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 ioutil.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
}
2021-08-30 15:18:50 +00:00
func handleCompression ( compressed io . WriteCloser , candidateCompression [ ] uint8 , config * packet . Config ) ( data io . WriteCloser , err error ) {
data = compressed
confAlgo := config . Compression ( )
if confAlgo == packet . CompressionNone {
return
}
finalAlgo := packet . CompressionNone
// if compression specified by config available we will use it
for _ , c := range candidateCompression {
if uint8 ( confAlgo ) == c {
finalAlgo = confAlgo
break
}
}
if finalAlgo != packet . CompressionNone {
var compConfig * packet . CompressionConfig
if config != nil {
compConfig = config . CompressionConfig
}
data , err = packet . SerializeCompressed ( compressed , finalAlgo , compConfig )
if err != nil {
return
}
}
return data , nil
}