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 }