github.com/goreleaser/nfpm/v2@v2.44.0/internal/sign/pgp.go (about)

     1  package sign
     2  
     3  import (
     4  	"bytes"
     5  	"crypto"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strconv"
    11  	"unicode"
    12  
    13  	"github.com/ProtonMail/go-crypto/openpgp"
    14  	"github.com/ProtonMail/go-crypto/openpgp/clearsign"
    15  	"github.com/ProtonMail/go-crypto/openpgp/packet"
    16  	"github.com/goreleaser/nfpm/v2"
    17  )
    18  
    19  // PGPSignerWithKeyID returns a PGP signer that creates a detached non-ASCII-armored
    20  // signature and is compatible with rpmpack's signature API.
    21  func PGPSignerWithKeyID(keyFile, passphrase string, hexKeyID *string) func([]byte) ([]byte, error) {
    22  	return func(data []byte) ([]byte, error) {
    23  		keyID, err := parseKeyID(hexKeyID)
    24  		if err != nil {
    25  			return nil, fmt.Errorf("%v is not a valid key id: %w", hexKeyID, err)
    26  		}
    27  
    28  		key, err := readSigningKey(keyFile, passphrase)
    29  		if err != nil {
    30  			return nil, &nfpm.ErrSigningFailure{Err: err}
    31  		}
    32  
    33  		var signature bytes.Buffer
    34  
    35  		if err := openpgp.DetachSign(
    36  			&signature,
    37  			key,
    38  			bytes.NewReader(data),
    39  			&packet.Config{
    40  				SigningKeyId: keyID,
    41  				DefaultHash:  crypto.SHA256,
    42  			},
    43  		); err != nil {
    44  			return nil, &nfpm.ErrSigningFailure{Err: err}
    45  		}
    46  
    47  		return signature.Bytes(), nil
    48  	}
    49  }
    50  
    51  // PGPArmoredDetachSign creates an ASCII-armored detached signature.
    52  func PGPArmoredDetachSign(message io.Reader, keyFile, passphrase string) ([]byte, error) {
    53  	return PGPArmoredDetachSignWithKeyID(message, keyFile, passphrase, nil)
    54  }
    55  
    56  // PGPArmoredDetachSignWithKeyID creates an ASCII-armored detached signature.
    57  func PGPArmoredDetachSignWithKeyID(message io.Reader, keyFile, passphrase string, hexKeyID *string) ([]byte, error) {
    58  	keyID, err := parseKeyID(hexKeyID)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("%v is not a valid key id: %w", hexKeyID, err)
    61  	}
    62  
    63  	key, err := readSigningKey(keyFile, passphrase)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("armored detach sign: %w", err)
    66  	}
    67  
    68  	var signature bytes.Buffer
    69  
    70  	err = openpgp.ArmoredDetachSign(&signature, key, message, &packet.Config{
    71  		SigningKeyId: keyID,
    72  		DefaultHash:  crypto.SHA256,
    73  	})
    74  	if err != nil {
    75  		return nil, fmt.Errorf("armored detach sign: %w", err)
    76  	}
    77  
    78  	return signature.Bytes(), nil
    79  }
    80  
    81  func PGPClearSignWithKeyID(message io.Reader, keyFile, passphrase string, hexKeyID *string) ([]byte, error) {
    82  	keyID, err := parseKeyID(hexKeyID)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("%v is not a valid key id: %w", hexKeyID, err)
    85  	}
    86  
    87  	key, err := readSigningKey(keyFile, passphrase)
    88  	if err != nil {
    89  		return nil, fmt.Errorf("clear sign: %w", err)
    90  	}
    91  
    92  	var signature bytes.Buffer
    93  
    94  	writeCloser, err := clearsign.Encode(
    95  		&signature,
    96  		key.PrivateKey,
    97  		&packet.Config{
    98  			SigningKeyId: keyID,
    99  			DefaultHash:  crypto.SHA256,
   100  		},
   101  	)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("clear sign: %w", err)
   104  	}
   105  
   106  	if _, err := io.Copy(writeCloser, message); err != nil {
   107  		return nil, fmt.Errorf("clear sign: %w", err)
   108  	}
   109  
   110  	if err := writeCloser.Close(); err != nil {
   111  		return nil, fmt.Errorf("clear sign: %w", err)
   112  	}
   113  
   114  	return signature.Bytes(), nil
   115  }
   116  
   117  // PGPVerify is exported for use in tests and verifies an ASCII-armored or non-ASCII-armored
   118  // signature using an ASCII-armored or non-ASCII-armored public key file. The signer
   119  // identity is not explicitly checked, other that the obvious fact that the signer's key must
   120  // be in the armoredPubKeyFile.
   121  func PGPVerify(message io.Reader, signature []byte, armoredPubKeyFile string) error {
   122  	keyFileContent, err := os.ReadFile(armoredPubKeyFile)
   123  	if err != nil {
   124  		return fmt.Errorf("reading armored public key file: %w", err)
   125  	}
   126  
   127  	var keyring openpgp.EntityList
   128  
   129  	if isASCII(keyFileContent) {
   130  		keyring, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(keyFileContent))
   131  		if err != nil {
   132  			return fmt.Errorf("decoding armored public key file: %w", err)
   133  		}
   134  	} else {
   135  		keyring, err = openpgp.ReadKeyRing(bytes.NewReader(keyFileContent))
   136  		if err != nil {
   137  			return fmt.Errorf("decoding public key file: %w", err)
   138  		}
   139  	}
   140  
   141  	if isASCII(signature) {
   142  		_, err = openpgp.CheckArmoredDetachedSignature(keyring, message, bytes.NewReader(signature), nil)
   143  		return err
   144  	}
   145  
   146  	_, err = openpgp.CheckDetachedSignature(keyring, message, bytes.NewReader(signature), nil)
   147  	return err
   148  }
   149  
   150  func PGPReadMessage(message []byte, armoredPubKeyFile string) (plaintext []byte, err error) {
   151  	keyFileContent, err := os.ReadFile(armoredPubKeyFile)
   152  	if err != nil {
   153  		return nil, fmt.Errorf("reading armored public key file: %w", err)
   154  	}
   155  
   156  	var keyring openpgp.EntityList
   157  
   158  	if isASCII(keyFileContent) {
   159  		keyring, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(keyFileContent))
   160  		if err != nil {
   161  			return nil, fmt.Errorf("decoding armored public key file: %w", err)
   162  		}
   163  	} else {
   164  		keyring, err = openpgp.ReadKeyRing(bytes.NewReader(keyFileContent))
   165  		if err != nil {
   166  			return nil, fmt.Errorf("decoding public key file: %w", err)
   167  		}
   168  	}
   169  
   170  	block, _ := clearsign.Decode(message)
   171  	_, err = block.VerifySignature(keyring, nil)
   172  
   173  	return block.Plaintext, err
   174  }
   175  
   176  func parseKeyID(hexKeyID *string) (uint64, error) {
   177  	if hexKeyID == nil || *hexKeyID == "" {
   178  		return 0, nil
   179  	}
   180  
   181  	result, err := strconv.ParseUint(*hexKeyID, 16, 64)
   182  	if err != nil {
   183  		return 0, err
   184  	}
   185  	return result, nil
   186  }
   187  
   188  var (
   189  	errMoreThanOneKey = errors.New("more than one signing key in keyring")
   190  	errNoKeys         = errors.New("no signing key in keyring")
   191  	errNoPassword     = errors.New("key is encrypted but no passphrase was provided")
   192  )
   193  
   194  func readSigningKey(keyFile, passphrase string) (*openpgp.Entity, error) {
   195  	fileContent, err := os.ReadFile(keyFile)
   196  	if err != nil {
   197  		return nil, fmt.Errorf("reading PGP key file: %w", err)
   198  	}
   199  
   200  	var entityList openpgp.EntityList
   201  
   202  	if isASCII(fileContent) {
   203  		entityList, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(fileContent))
   204  		if err != nil {
   205  			return nil, fmt.Errorf("decoding armored PGP keyring: %w", err)
   206  		}
   207  	} else {
   208  		entityList, err = openpgp.ReadKeyRing(bytes.NewReader(fileContent))
   209  		if err != nil {
   210  			return nil, fmt.Errorf("decoding PGP keyring: %w", err)
   211  		}
   212  	}
   213  	var key *openpgp.Entity
   214  
   215  	for _, candidate := range entityList {
   216  		if candidate.PrivateKey == nil {
   217  			continue
   218  		}
   219  
   220  		if !candidate.PrivateKey.CanSign() {
   221  			continue
   222  		}
   223  
   224  		if key != nil {
   225  			return nil, errMoreThanOneKey
   226  		}
   227  
   228  		key = candidate
   229  	}
   230  
   231  	if key == nil {
   232  		return nil, errNoKeys
   233  	}
   234  
   235  	if key.PrivateKey.Encrypted {
   236  		if passphrase == "" {
   237  			return nil, errNoPassword
   238  		}
   239  		pw := []byte(passphrase)
   240  		err = key.PrivateKey.Decrypt(pw)
   241  		if err != nil {
   242  			return nil, fmt.Errorf("decrypt secret signing key: %w", err)
   243  		}
   244  		for _, sub := range key.Subkeys {
   245  			if sub.PrivateKey != nil {
   246  				if err := sub.PrivateKey.Decrypt(pw); err != nil {
   247  					return nil, fmt.Errorf("gopenpgp: error in unlocking sub key: %w", err)
   248  				}
   249  			}
   250  		}
   251  	}
   252  
   253  	return key, nil
   254  }
   255  
   256  func isASCII(s []byte) bool {
   257  	for i := range s {
   258  		if s[i] > unicode.MaxASCII {
   259  			return false
   260  		}
   261  	}
   262  	return true
   263  }