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