github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/pgp_dec.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  
    11  	"time"
    12  
    13  	"github.com/keybase/go-crypto/openpgp"
    14  	"github.com/keybase/go-crypto/openpgp/armor"
    15  	"github.com/keybase/go-crypto/openpgp/clearsign"
    16  	"github.com/keybase/go-crypto/openpgp/errors"
    17  )
    18  
    19  type SignatureStatus struct {
    20  	IsSigned        bool
    21  	Verified        bool
    22  	SignatureError  error
    23  	KeyID           uint64
    24  	Entity          *openpgp.Entity
    25  	SignatureTime   time.Time
    26  	RecipientKeyIDs []uint64
    27  	Warnings        HashSecurityWarnings
    28  }
    29  
    30  func PGPDecryptWithBundles(g *GlobalContext, source io.Reader, sink io.Writer, keys []*PGPKeyBundle) (*SignatureStatus, error) {
    31  	opkr := make(openpgp.EntityList, len(keys))
    32  	for i, k := range keys {
    33  		opkr[i] = k.Entity
    34  	}
    35  	return PGPDecrypt(g, source, sink, opkr)
    36  }
    37  
    38  // PGPDecrypt only generates warnings about insecure _message_ signatures, not
    39  // _key_ signatures - that is handled by engine.PGPDecrypt.
    40  func PGPDecrypt(g *GlobalContext, source io.Reader, sink io.Writer, kr openpgp.KeyRing) (*SignatureStatus, error) {
    41  
    42  	var sc StreamClassification
    43  	var err error
    44  
    45  	sc, source, err = ClassifyStream(source)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if sc.Format != CryptoMessageFormatPGP {
    51  		return nil, WrongCryptoFormatError{
    52  			Wanted:    CryptoMessageFormatPGP,
    53  			Received:  sc.Format,
    54  			Operation: "decrypt",
    55  		}
    56  	}
    57  
    58  	if sc.Type == CryptoMessageTypeClearSignature {
    59  		return pgpDecryptClearsign(g, source, sink, kr)
    60  	}
    61  
    62  	if sc.Armored {
    63  		b, err := armor.Decode(source)
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		source = b.Body
    68  	}
    69  
    70  	g.Log.Debug("Calling into openpgp ReadMessage for decryption")
    71  	md, err := openpgp.ReadMessage(source, kr, nil, nil)
    72  	if err != nil {
    73  		if err == errors.ErrKeyIncorrect {
    74  			return nil, NoDecryptionKeyError{Msg: "unable to find a PGP decryption key for this message"}
    75  		}
    76  		return nil, err
    77  	}
    78  
    79  	if md.IsSigned {
    80  		g.Log.Debug("message is signed (SignedByKeyId: %+v) (have key? %v)", md.SignedByKeyId, md.SignedBy != nil)
    81  	}
    82  
    83  	n, err := io.Copy(sink, md.UnverifiedBody)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	g.Log.Debug("PGPDecrypt: copied %d bytes to writer", n)
    88  
    89  	var status SignatureStatus
    90  	if md.IsSigned {
    91  		status.IsSigned = true
    92  		status.KeyID = md.SignedByKeyId
    93  		if md.Signature != nil {
    94  			status.SignatureTime = md.Signature.CreationTime
    95  
    96  			if !IsHashSecure(md.Signature.Hash) {
    97  				status.Warnings = append(
    98  					status.Warnings,
    99  					NewHashSecurityWarning(
   100  						HashSecurityWarningSignatureHash,
   101  						md.Signature.Hash,
   102  						nil,
   103  					),
   104  				)
   105  			}
   106  		}
   107  		if md.SignedBy != nil {
   108  			status.Entity = md.SignedBy.Entity
   109  		}
   110  		if md.SignatureError != nil {
   111  			status.SignatureError = md.SignatureError
   112  		} else {
   113  			status.Verified = true
   114  		}
   115  	}
   116  
   117  	status.RecipientKeyIDs = md.EncryptedToKeyIds
   118  
   119  	return &status, nil
   120  }
   121  
   122  func pgpDecryptClearsign(g *GlobalContext, source io.Reader, sink io.Writer, kr openpgp.KeyRing) (*SignatureStatus, error) {
   123  	// clearsign decode only works with the whole data slice, not a reader
   124  	// so have to read it all here:
   125  	msg, err := io.ReadAll(source)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	b, _ := clearsign.Decode(msg)
   130  	if b == nil {
   131  		return nil, fmt.Errorf("Unable to decode clearsigned message")
   132  	}
   133  
   134  	sigBytes, err := io.ReadAll(b.ArmoredSignature.Body)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	signer, err := openpgp.CheckDetachedSignature(kr, bytes.NewReader(b.Bytes), bytes.NewReader(sigBytes))
   140  	if err != nil {
   141  		return nil, fmt.Errorf("Check sig error: %s", err)
   142  	}
   143  
   144  	n, err := io.Copy(sink, bytes.NewReader(b.Plaintext))
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	g.Log.Debug("PGPDecrypt: copied %d bytes to writer", n)
   149  
   150  	var status SignatureStatus
   151  	if signer == nil {
   152  		return &status, nil
   153  	}
   154  
   155  	// Reexamine the signature to figure out its hash
   156  	digestHash, signerKeyID, err := ExtractPGPSignatureHashMethod(kr, sigBytes)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	if !IsHashSecure(digestHash) {
   161  		status.Warnings = append(
   162  			status.Warnings,
   163  			NewHashSecurityWarning(
   164  				HashSecurityWarningSignatureHash,
   165  				digestHash,
   166  				nil,
   167  			),
   168  		)
   169  	}
   170  
   171  	status.IsSigned = true
   172  	status.Verified = true
   173  	status.Entity = signer
   174  	status.KeyID = signerKeyID
   175  
   176  	return &status, nil
   177  }