github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/asserts/extkeypairmgr.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package asserts
    21  
    22  import (
    23  	"bytes"
    24  	"crypto"
    25  	"crypto/rsa"
    26  	"crypto/x509"
    27  	"encoding/json"
    28  	"fmt"
    29  	"io"
    30  	"os/exec"
    31  
    32  	"golang.org/x/crypto/openpgp/packet"
    33  
    34  	"github.com/snapcore/snapd/strutil"
    35  )
    36  
    37  type ExternalKeyInfo struct {
    38  	Name string
    39  	ID   string
    40  }
    41  
    42  // ExternalKeypairManager is key pair manager implemented via an external program interface.
    43  // TODO: points to interface docs
    44  type ExternalKeypairManager struct {
    45  	keyMgrPath string
    46  	nameToID   map[string]string
    47  	cache      map[string]*cachedExtKey
    48  }
    49  
    50  // NewExternalKeypairManager creates a new ExternalKeypairManager using the program at keyMgrPath.
    51  func NewExternalKeypairManager(keyMgrPath string) (*ExternalKeypairManager, error) {
    52  	em := &ExternalKeypairManager{
    53  		keyMgrPath: keyMgrPath,
    54  		nameToID:   make(map[string]string),
    55  		cache:      make(map[string]*cachedExtKey),
    56  	}
    57  	if err := em.checkFeatures(); err != nil {
    58  		return nil, err
    59  	}
    60  	return em, nil
    61  }
    62  
    63  func (em *ExternalKeypairManager) keyMgr(op string, args []string, in []byte, out interface{}) error {
    64  	args = append([]string{op}, args...)
    65  	cmd := exec.Command(em.keyMgrPath, args...)
    66  	var outBuf bytes.Buffer
    67  	var errBuf bytes.Buffer
    68  
    69  	if len(in) != 0 {
    70  		cmd.Stdin = bytes.NewBuffer(in)
    71  	}
    72  	cmd.Stdout = &outBuf
    73  	cmd.Stderr = &errBuf
    74  
    75  	if err := cmd.Run(); err != nil {
    76  		return fmt.Errorf("external keypair manager %q %v failed: %v (%q)", em.keyMgrPath, args, err, errBuf.Bytes())
    77  
    78  	}
    79  	switch o := out.(type) {
    80  	case *[]byte:
    81  		*o = outBuf.Bytes()
    82  	default:
    83  		if err := json.Unmarshal(outBuf.Bytes(), out); err != nil {
    84  			return fmt.Errorf("cannot decode external keypair manager %q %v output: %v", em.keyMgrPath, args, err)
    85  		}
    86  	}
    87  	return nil
    88  }
    89  
    90  func (em *ExternalKeypairManager) checkFeatures() error {
    91  	var feats struct {
    92  		Signing    []string `json:"signing"`
    93  		PublicKeys []string `json:"public-keys"`
    94  	}
    95  	if err := em.keyMgr("features", nil, nil, &feats); err != nil {
    96  		return err
    97  	}
    98  	if !strutil.ListContains(feats.Signing, "RSA-PKCS") {
    99  		return fmt.Errorf("external keypair manager %q missing support for RSA-PKCS signing", em.keyMgrPath)
   100  	}
   101  	if !strutil.ListContains(feats.PublicKeys, "DER") {
   102  		return fmt.Errorf("external keypair manager %q missing support for public key DER output format", em.keyMgrPath)
   103  	}
   104  	return nil
   105  }
   106  
   107  func (em *ExternalKeypairManager) keyNames() ([]string, error) {
   108  	var knames struct {
   109  		Names []string `json:"key-names"`
   110  	}
   111  	if err := em.keyMgr("key-names", nil, nil, &knames); err != nil {
   112  		return nil, fmt.Errorf("cannot get all external keypair manager key names: %v", err)
   113  	}
   114  	return knames.Names, nil
   115  }
   116  
   117  func (em *ExternalKeypairManager) findByName(name string) (PublicKey, *rsa.PublicKey, error) {
   118  	var k []byte
   119  	err := em.keyMgr("get-public-key", []string{"-f", "DER", "-k", name}, nil, &k)
   120  	if err != nil {
   121  		return nil, nil, fmt.Errorf("cannot find external key: %v", err)
   122  	}
   123  	pubk, err := x509.ParsePKIXPublicKey(k)
   124  	if err != nil {
   125  		return nil, nil, fmt.Errorf("cannot decode external key %q: %v", name, err)
   126  	}
   127  	rsaPub, ok := pubk.(*rsa.PublicKey)
   128  	if !ok {
   129  		return nil, nil, fmt.Errorf("expected RSA public key, got instead: %T", pubk)
   130  	}
   131  	pubKey := RSAPublicKey(rsaPub)
   132  	return pubKey, rsaPub, nil
   133  }
   134  
   135  func (em *ExternalKeypairManager) Export(keyName string) ([]byte, error) {
   136  	pubKey, _, err := em.findByName(keyName)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	return EncodePublicKey(pubKey)
   141  }
   142  
   143  func (em *ExternalKeypairManager) loadKey(name string) (*cachedExtKey, error) {
   144  	id, ok := em.nameToID[name]
   145  	if ok {
   146  		return em.cache[id], nil
   147  	}
   148  	pubKey, rsaPub, err := em.findByName(name)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	id = pubKey.ID()
   153  	em.nameToID[name] = id
   154  	cachedKey := &cachedExtKey{
   155  		pubKey: pubKey,
   156  		signer: &extSigner{
   157  			keyName: name,
   158  			rsaPub:  rsaPub,
   159  			// signWith is filled later
   160  		},
   161  	}
   162  	em.cache[id] = cachedKey
   163  	return cachedKey, nil
   164  }
   165  
   166  func (em *ExternalKeypairManager) privateKey(cachedKey *cachedExtKey) PrivateKey {
   167  	if cachedKey.privKey == nil {
   168  		extSigner := cachedKey.signer
   169  		// fill signWith
   170  		extSigner.signWith = em.signWith
   171  		signer := packet.NewSignerPrivateKey(v1FixedTimestamp, extSigner)
   172  		signk := openpgpPrivateKey{privk: signer}
   173  		extKey := &extPGPPrivateKey{
   174  			pubKey:     cachedKey.pubKey,
   175  			from:       fmt.Sprintf("external keypair manager %q", em.keyMgrPath),
   176  			externalID: extSigner.keyName,
   177  			bitLen:     extSigner.rsaPub.N.BitLen(),
   178  			doSign:     signk.sign,
   179  		}
   180  		cachedKey.privKey = extKey
   181  	}
   182  	return cachedKey.privKey
   183  }
   184  
   185  func (em *ExternalKeypairManager) GetByName(keyName string) (PrivateKey, error) {
   186  	cachedKey, err := em.loadKey(keyName)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	return em.privateKey(cachedKey), nil
   191  }
   192  
   193  // ExternalUnsupportedOpError represents the error situation of operations
   194  // that are not supported/mediated via ExternalKeypairManager.
   195  type ExternalUnsupportedOpError struct {
   196  	msg string
   197  }
   198  
   199  func (euoe *ExternalUnsupportedOpError) Error() string {
   200  	return euoe.msg
   201  }
   202  
   203  func (em *ExternalKeypairManager) Put(privKey PrivateKey) error {
   204  	return &ExternalUnsupportedOpError{"cannot import private key into external keypair manager"}
   205  }
   206  
   207  func (em *ExternalKeypairManager) Delete(keyName string) error {
   208  	return &ExternalUnsupportedOpError{"no support to delete external keypair manager keys"}
   209  }
   210  
   211  func (em *ExternalKeypairManager) Generate(keyName string) error {
   212  	return &ExternalUnsupportedOpError{"no support to mediate generating an external keypair manager key"}
   213  }
   214  
   215  func (em *ExternalKeypairManager) loadAllKeys() ([]string, error) {
   216  	names, err := em.keyNames()
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	for _, name := range names {
   221  		if _, err := em.loadKey(name); err != nil {
   222  			return nil, err
   223  		}
   224  	}
   225  	return names, nil
   226  }
   227  
   228  func (em *ExternalKeypairManager) Get(keyID string) (PrivateKey, error) {
   229  	cachedKey, ok := em.cache[keyID]
   230  	if !ok {
   231  		// try to load all keys
   232  		if _, err := em.loadAllKeys(); err != nil {
   233  			return nil, err
   234  		}
   235  		cachedKey, ok = em.cache[keyID]
   236  		if !ok {
   237  			return nil, fmt.Errorf("cannot find external key with id %q", keyID)
   238  		}
   239  	}
   240  	return em.privateKey(cachedKey), nil
   241  }
   242  
   243  func (em *ExternalKeypairManager) List() ([]ExternalKeyInfo, error) {
   244  	names, err := em.loadAllKeys()
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	res := make([]ExternalKeyInfo, len(names))
   249  	for i, name := range names {
   250  		res[i].Name = name
   251  		res[i].ID = em.cache[em.nameToID[name]].pubKey.ID()
   252  	}
   253  	return res, nil
   254  }
   255  
   256  // see https://datatracker.ietf.org/doc/html/rfc2313 and more recently
   257  // and more precisely about SHA-512:
   258  // https://datatracker.ietf.org/doc/html/rfc3447#section-9.2 Notes 1.
   259  var digestInfoSHA512Prefix = []byte{0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}
   260  
   261  func (em *ExternalKeypairManager) signWith(keyName string, digest []byte) (signature []byte, err error) {
   262  	// wrap the digest into the needed DigestInfo, the RSA-PKCS
   263  	// mechanism or equivalent is expected not to do this on its
   264  	// own
   265  	toSign := &bytes.Buffer{}
   266  	toSign.Write(digestInfoSHA512Prefix)
   267  	toSign.Write(digest)
   268  
   269  	err = em.keyMgr("sign", []string{"-m", "RSA-PKCS", "-k", keyName}, toSign.Bytes(), &signature)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	return signature, nil
   274  }
   275  
   276  type cachedExtKey struct {
   277  	pubKey  PublicKey
   278  	signer  *extSigner
   279  	privKey PrivateKey
   280  }
   281  
   282  type extSigner struct {
   283  	keyName  string
   284  	rsaPub   *rsa.PublicKey
   285  	signWith func(keyName string, digest []byte) (signature []byte, err error)
   286  }
   287  
   288  func (es *extSigner) Public() crypto.PublicKey {
   289  	return es.rsaPub
   290  }
   291  
   292  func (es *extSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
   293  	if opts.HashFunc() != crypto.SHA512 {
   294  		return nil, fmt.Errorf("unexpected pgp signature digest")
   295  	}
   296  
   297  	return es.signWith(es.keyName, digest)
   298  }