github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/gpgkeypairmgr_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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_test
    21  
    22  import (
    23  	"bytes"
    24  	"crypto"
    25  	"crypto/rand"
    26  	"crypto/rsa"
    27  	"fmt"
    28  	"os"
    29  	"time"
    30  
    31  	"golang.org/x/crypto/openpgp/packet"
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/asserts"
    35  	"github.com/snapcore/snapd/asserts/assertstest"
    36  	"github.com/snapcore/snapd/osutil"
    37  )
    38  
    39  type gpgKeypairMgrSuite struct {
    40  	homedir    string
    41  	keypairMgr asserts.KeypairManager
    42  }
    43  
    44  var _ = Suite(&gpgKeypairMgrSuite{})
    45  
    46  func (gkms *gpgKeypairMgrSuite) SetUpSuite(c *C) {
    47  	if !osutil.FileExists("/usr/bin/gpg1") && !osutil.FileExists("/usr/bin/gpg") {
    48  		c.Skip("gpg not installed")
    49  	}
    50  }
    51  
    52  func (gkms *gpgKeypairMgrSuite) importKey(key string) {
    53  	assertstest.GPGImportKey(gkms.homedir, key)
    54  }
    55  
    56  func (gkms *gpgKeypairMgrSuite) SetUpTest(c *C) {
    57  	gkms.homedir = c.MkDir()
    58  	os.Setenv("SNAP_GNUPG_HOME", gkms.homedir)
    59  	gkms.keypairMgr = asserts.NewGPGKeypairManager()
    60  	// import test key
    61  	gkms.importKey(assertstest.DevKey)
    62  }
    63  
    64  func (gkms *gpgKeypairMgrSuite) TearDownTest(c *C) {
    65  	os.Unsetenv("SNAP_GNUPG_HOME")
    66  }
    67  
    68  func (gkms *gpgKeypairMgrSuite) TestGetPublicKeyLooksGood(c *C) {
    69  	got, err := gkms.keypairMgr.Get(assertstest.DevKeyID)
    70  	c.Assert(err, IsNil)
    71  	keyID := got.PublicKey().ID()
    72  	c.Check(keyID, Equals, assertstest.DevKeyID)
    73  }
    74  
    75  func (gkms *gpgKeypairMgrSuite) TestGetNotFound(c *C) {
    76  	got, err := gkms.keypairMgr.Get("ffffffffffffffff")
    77  	c.Check(err, ErrorMatches, `cannot find key "ffffffffffffffff" in GPG keyring`)
    78  	c.Check(got, IsNil)
    79  }
    80  
    81  func (gkms *gpgKeypairMgrSuite) TestUseInSigning(c *C) {
    82  	store := assertstest.NewStoreStack("trusted", nil)
    83  
    84  	devKey, err := gkms.keypairMgr.Get(assertstest.DevKeyID)
    85  	c.Assert(err, IsNil)
    86  
    87  	devAcct := assertstest.NewAccount(store, "devel1", map[string]interface{}{
    88  		"account-id": "dev1-id",
    89  	}, "")
    90  	devAccKey := assertstest.NewAccountKey(store, devAcct, nil, devKey.PublicKey(), "")
    91  
    92  	signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    93  		KeypairManager: gkms.keypairMgr,
    94  	})
    95  	c.Assert(err, IsNil)
    96  
    97  	checkDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    98  		Backstore: asserts.NewMemoryBackstore(),
    99  		Trusted:   store.Trusted,
   100  	})
   101  	c.Assert(err, IsNil)
   102  	// add store key
   103  	err = checkDB.Add(store.StoreAccountKey(""))
   104  	c.Assert(err, IsNil)
   105  	// enable devel key
   106  	err = checkDB.Add(devAcct)
   107  	c.Assert(err, IsNil)
   108  	err = checkDB.Add(devAccKey)
   109  	c.Assert(err, IsNil)
   110  
   111  	headers := map[string]interface{}{
   112  		"authority-id":  "dev1-id",
   113  		"snap-sha3-384": blobSHA3_384,
   114  		"snap-id":       "snap-id-1",
   115  		"grade":         "devel",
   116  		"snap-size":     "1025",
   117  		"timestamp":     time.Now().Format(time.RFC3339),
   118  	}
   119  	snapBuild, err := signDB.Sign(asserts.SnapBuildType, headers, nil, assertstest.DevKeyID)
   120  	c.Assert(err, IsNil)
   121  
   122  	err = checkDB.Check(snapBuild)
   123  	c.Check(err, IsNil)
   124  }
   125  
   126  func (gkms *gpgKeypairMgrSuite) TestGetNotUnique(c *C) {
   127  	mockGPG := func(prev asserts.GPGRunner, input []byte, args ...string) ([]byte, error) {
   128  		if args[1] == "--list-secret-keys" {
   129  			return prev(input, args...)
   130  		}
   131  		c.Assert(args[1], Equals, "--export")
   132  
   133  		pk1, err := rsa.GenerateKey(rand.Reader, 512)
   134  		c.Assert(err, IsNil)
   135  		pk2, err := rsa.GenerateKey(rand.Reader, 512)
   136  		c.Assert(err, IsNil)
   137  
   138  		buf := new(bytes.Buffer)
   139  		err = packet.NewRSAPublicKey(time.Now(), &pk1.PublicKey).Serialize(buf)
   140  		c.Assert(err, IsNil)
   141  		err = packet.NewRSAPublicKey(time.Now(), &pk2.PublicKey).Serialize(buf)
   142  		c.Assert(err, IsNil)
   143  
   144  		return buf.Bytes(), nil
   145  	}
   146  	restore := asserts.MockRunGPG(mockGPG)
   147  	defer restore()
   148  
   149  	_, err := gkms.keypairMgr.Get(assertstest.DevKeyID)
   150  	c.Check(err, ErrorMatches, `cannot load GPG public key with fingerprint "[A-F0-9]+": cannot select exported public key, found many`)
   151  }
   152  
   153  func (gkms *gpgKeypairMgrSuite) TestUseInSigningBrokenSignature(c *C) {
   154  	_, rsaPrivKey := assertstest.ReadPrivKey(assertstest.DevKey)
   155  	pgpPrivKey := packet.NewRSAPrivateKey(time.Unix(1, 0), rsaPrivKey)
   156  
   157  	var breakSig func(sig *packet.Signature, cont []byte) []byte
   158  
   159  	mockGPG := func(prev asserts.GPGRunner, input []byte, args ...string) ([]byte, error) {
   160  		if args[1] == "--list-secret-keys" || args[1] == "--export" {
   161  			return prev(input, args...)
   162  		}
   163  		n := len(args)
   164  		c.Assert(args[n-1], Equals, "--detach-sign")
   165  
   166  		sig := new(packet.Signature)
   167  		sig.PubKeyAlgo = packet.PubKeyAlgoRSA
   168  		sig.Hash = crypto.SHA512
   169  		sig.CreationTime = time.Now()
   170  
   171  		// poking to break the signature
   172  		cont := breakSig(sig, input)
   173  
   174  		h := sig.Hash.New()
   175  		h.Write([]byte(cont))
   176  
   177  		err := sig.Sign(h, pgpPrivKey, nil)
   178  		c.Assert(err, IsNil)
   179  
   180  		buf := new(bytes.Buffer)
   181  		sig.Serialize(buf)
   182  		return buf.Bytes(), nil
   183  	}
   184  	restore := asserts.MockRunGPG(mockGPG)
   185  	defer restore()
   186  
   187  	signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
   188  		KeypairManager: gkms.keypairMgr,
   189  	})
   190  	c.Assert(err, IsNil)
   191  
   192  	headers := map[string]interface{}{
   193  		"authority-id":  "dev1-id",
   194  		"snap-sha3-384": blobSHA3_384,
   195  		"snap-id":       "snap-id-1",
   196  		"grade":         "devel",
   197  		"snap-size":     "1025",
   198  		"timestamp":     time.Now().Format(time.RFC3339),
   199  	}
   200  
   201  	tests := []struct {
   202  		breakSig    func(*packet.Signature, []byte) []byte
   203  		expectedErr string
   204  	}{
   205  		{func(sig *packet.Signature, cont []byte) []byte {
   206  			sig.Hash = crypto.SHA1
   207  			return cont
   208  		}, "cannot sign assertion: bad GPG produced signature: expected SHA512 digest"},
   209  		{func(sig *packet.Signature, cont []byte) []byte {
   210  			return cont[:5]
   211  		}, "cannot sign assertion: bad GPG produced signature: it does not verify:.*"},
   212  	}
   213  
   214  	for _, t := range tests {
   215  		breakSig = t.breakSig
   216  
   217  		_, err = signDB.Sign(asserts.SnapBuildType, headers, nil, assertstest.DevKeyID)
   218  		c.Check(err, ErrorMatches, t.expectedErr)
   219  	}
   220  
   221  }
   222  
   223  func (gkms *gpgKeypairMgrSuite) TestUseInSigningFailure(c *C) {
   224  	mockGPG := func(prev asserts.GPGRunner, input []byte, args ...string) ([]byte, error) {
   225  		if args[1] == "--list-secret-keys" || args[1] == "--export" {
   226  			return prev(input, args...)
   227  		}
   228  		n := len(args)
   229  		c.Assert(args[n-1], Equals, "--detach-sign")
   230  		return nil, fmt.Errorf("boom")
   231  	}
   232  	restore := asserts.MockRunGPG(mockGPG)
   233  	defer restore()
   234  
   235  	signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
   236  		KeypairManager: gkms.keypairMgr,
   237  	})
   238  	c.Assert(err, IsNil)
   239  
   240  	headers := map[string]interface{}{
   241  		"authority-id":  "dev1-id",
   242  		"snap-sha3-384": blobSHA3_384,
   243  		"snap-id":       "snap-id-1",
   244  		"grade":         "devel",
   245  		"snap-size":     "1025",
   246  		"timestamp":     time.Now().Format(time.RFC3339),
   247  	}
   248  
   249  	_, err = signDB.Sign(asserts.SnapBuildType, headers, nil, assertstest.DevKeyID)
   250  	c.Check(err, ErrorMatches, "cannot sign assertion: cannot sign using GPG: boom")
   251  }
   252  
   253  const shortPrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
   254  Version: GnuPG v1
   255  
   256  lQOYBFdGO7MBCADltsXglnDQdfBw0yOVpKZdkuvSnJKKn1H72PapgAr7ucLqNBCA
   257  js0kltDTa2LQP4vljiTyoMzOMnex4kXwRPlF+poZIEBHDLT0i/6sJ6mDukss1HBR
   258  GgNpU3y49WTXc8qxFY4clhbuqgQmy6bUmaVoo3Z4z7cqbsCepWfx5y+vJwMYqlo3
   259  Nb4q2+hTKS/o3yLiYB7/hkEhMZrFrOPR5SM7Tz5y7cpF6ObY+JZIp/MK+LsLWLji
   260  fEX/pcOtSjFdQqbcnhJJscXRERlFQDbc+gNmZYZ2RqdH5o46OliHkGhVDVTiW25A
   261  SqhGfnodypbZ9QAPSRvhLrN64AqEsvRb3I13ABEBAAEAB/9cQKg8Nz6sQUkkDm9C
   262  iCK1/qyNYwro9+3VXj9FOCJxEJuqMemUr4TMVnMcDQrchkC5GnpVJGXLw3HVcwFS
   263  amjPhUKAp7aYsg40DcrjuXP27oiFQvWuZGuNT5WNtCNg8WQr9POjIFWqWIYdTHk9
   264  9Ux79vW7s/Oj62GY9OWHPSilxpq1MjDKo9CSMbLeWxW+gbDxaD7cK7H/ONcz8bZ7
   265  pRfEhNIx3mEbWaZpWRrf+dSUx2OJbPGRkeFFMbCNapqftse173BZCwUKsW7RTp2S
   266  w8Vpo2Ky63Jlpz1DpoMDBz2vSH7pzaqAdnziI2r0IKiidajXFfpXJpJ3ICo/QhWj
   267  x1eRBADrI4I99zHeyy+12QMpkDrOu+ahF6/emdsm1FIy88TqeBmLkeXCXKZIpU3c
   268  USnxzm0nPNbOl7Nvf2VdAyeAftyag7t38Cud5MXldv/iY0e6oTKzxgha37yr6oRv
   269  PZ6VGwbkBvWti1HL4yx1QnkHFS6ailR9WiiHr3HaWAklZAsC0QQA+hgOi0V9fMZZ
   270  Y4/iFVRI9k1NK3pl0mP7pVTzbcjVYspLdIPQxPDsHJW0z48g23KOt0vL3yZvxdBx
   271  cfYGqIonAX19aMD5D4bNLx616pZs78DKGlOz6iXDcaib+n/uCNWxd5R/0m/zugrB
   272  qklpyIC/uxx+SmkJqqq378ytfvBMzccD/3Y6m3PM0ZnrIkr4Q7cKi9ao9rvM+J7o
   273  ziMgfnKWedNDxNa4tIVYYGPiXsjxY/ASUyxVjUPbkyCy3ubZrew0zQ9+kQbO/6vB
   274  WAg9ffT9M92QbSDjuxgUiC5GfvlCoDgJtuLRHd0YLDgUCS5nwb+teEsOpiNWEGXc
   275  Tr+5HZO+g6wxT6W0BiAoeHh4KYkBOAQTAQIAIgUCV0Y7swIbLwYLCQgHAwIGFQgC
   276  CQoLBBYCAwECHgECF4AACgkQEYacUJMr9p/i5wf/XbEiAe1+Y/ZNMO8PYnq1Nktk
   277  CbZEfQo+QH/9gJpt4p78YseWeUp14gsULLks3xRojlKNzYkqBpJcP7Ex+hQ3LEp7
   278  9IVbept5md4uuZcU0GFF42WAYXExd2cuxPv3lmWHOPuN63a/xpp0M2vYDfpt63qi
   279  Tly5/P4+NgpD6vAh8zwRHuBV/0mno/QX6cUCLVxq2v1aOqC9zq9B5sdYKQKjsQBP
   280  NOXCt1wPaINkqiW/8w2KhUl6mL6vhO0Onqu/F7M/YNXitv6Z2NFdFUVBh58UZW3C
   281  2jrc8JeRQ4Qlr1oeHh2loYOdZfxFPxRjhsRTnNKY8UHWLfbeI6lMqxR5G3DS+g==
   282  =kQRo
   283  -----END PGP PRIVATE KEY BLOCK-----
   284  `
   285  
   286  func (gkms *gpgKeypairMgrSuite) TestUseInSigningKeyTooShort(c *C) {
   287  	gkms.importKey(shortPrivKey)
   288  	privk, _ := assertstest.ReadPrivKey(shortPrivKey)
   289  
   290  	signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
   291  		KeypairManager: gkms.keypairMgr,
   292  	})
   293  	c.Assert(err, IsNil)
   294  
   295  	headers := map[string]interface{}{
   296  		"authority-id":  "dev1-id",
   297  		"snap-sha3-384": blobSHA3_384,
   298  		"snap-id":       "snap-id-1",
   299  		"grade":         "devel",
   300  		"snap-size":     "1025",
   301  		"timestamp":     time.Now().Format(time.RFC3339),
   302  	}
   303  
   304  	_, err = signDB.Sign(asserts.SnapBuildType, headers, nil, privk.PublicKey().ID())
   305  	c.Check(err, ErrorMatches, `cannot sign assertion: signing needs at least a 4096 bits key, got 2048`)
   306  }
   307  
   308  func (gkms *gpgKeypairMgrSuite) TestParametersForGenerate(c *C) {
   309  	gpgKeypairMgr := gkms.keypairMgr.(*asserts.GPGKeypairManager)
   310  	baseParameters := `
   311  Key-Type: RSA
   312  Key-Length: 4096
   313  Name-Real: test-key
   314  Creation-Date: seconds=1451606400
   315  Preferences: SHA512
   316  `
   317  
   318  	tests := []struct {
   319  		passphrase      string
   320  		extraParameters string
   321  	}{
   322  		{"", ""},
   323  		{"secret", "Passphrase: secret\n"},
   324  	}
   325  
   326  	for _, test := range tests {
   327  		parameters := gpgKeypairMgr.ParametersForGenerate(test.passphrase, "test-key")
   328  		c.Check(parameters, Equals, baseParameters+test.extraParameters)
   329  	}
   330  }
   331  
   332  func (gkms *gpgKeypairMgrSuite) TestList(c *C) {
   333  	gpgKeypairMgr := gkms.keypairMgr.(*asserts.GPGKeypairManager)
   334  
   335  	keys, err := gpgKeypairMgr.List()
   336  	c.Assert(err, IsNil)
   337  	c.Check(keys, HasLen, 1)
   338  	c.Check(keys[0].ID, Equals, assertstest.DevKeyID)
   339  	c.Check(keys[0].Name, Not(Equals), "")
   340  }