github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/asserts/serial_asserts_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 "strings" 24 "time" 25 26 . "gopkg.in/check.v1" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/asserts/assertstest" 30 ) 31 32 var ( 33 _ = Suite(&serialSuite{}) 34 ) 35 36 type serialSuite struct { 37 ts time.Time 38 tsLine string 39 deviceKey asserts.PrivateKey 40 encodedDevKey string 41 } 42 43 func (ss *serialSuite) SetUpSuite(c *C) { 44 ss.ts = time.Now().Truncate(time.Second).UTC() 45 ss.tsLine = "timestamp: " + ss.ts.Format(time.RFC3339) + "\n" 46 47 ss.deviceKey = testPrivKey2 48 encodedPubKey, err := asserts.EncodePublicKey(ss.deviceKey.PublicKey()) 49 c.Assert(err, IsNil) 50 ss.encodedDevKey = string(encodedPubKey) 51 } 52 53 const serialExample = "type: serial\n" + 54 "authority-id: brand-id1\n" + 55 "brand-id: brand-id1\n" + 56 "model: baz-3000\n" + 57 "serial: 2700\n" + 58 "device-key:\n DEVICEKEY\n" + 59 "device-key-sha3-384: KEYID\n" + 60 "TSLINE" + 61 "body-length: 2\n" + 62 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" + 63 "HW" + 64 "\n\n" + 65 "AXNpZw==" 66 67 func (ss *serialSuite) TestDecodeOK(c *C) { 68 encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) 69 encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) 70 encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1) 71 a, err := asserts.Decode([]byte(encoded)) 72 c.Assert(err, IsNil) 73 c.Check(a.Type(), Equals, asserts.SerialType) 74 serial := a.(*asserts.Serial) 75 c.Check(serial.AuthorityID(), Equals, "brand-id1") 76 c.Check(serial.Timestamp(), Equals, ss.ts) 77 c.Check(serial.BrandID(), Equals, "brand-id1") 78 c.Check(serial.Model(), Equals, "baz-3000") 79 c.Check(serial.Serial(), Equals, "2700") 80 c.Check(serial.DeviceKey().ID(), Equals, ss.deviceKey.PublicKey().ID()) 81 } 82 83 const ( 84 deviceSessReqErrPrefix = "assertion device-session-request: " 85 serialErrPrefix = "assertion serial: " 86 serialReqErrPrefix = "assertion serial-request: " 87 ) 88 89 func (ss *serialSuite) TestDecodeInvalid(c *C) { 90 encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) 91 92 invalidTests := []struct{ original, invalid, expectedErr string }{ 93 {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`}, 94 {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, 95 {"brand-id: brand-id1\n", "brand-id: ,1\n", `"brand-id" header contains invalid characters: ",1\"`}, 96 {"model: baz-3000\n", "", `"model" header is mandatory`}, 97 {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, 98 {"model: baz-3000\n", "model: _what\n", `"model" header contains invalid characters: "_what"`}, 99 {"serial: 2700\n", "", `"serial" header is mandatory`}, 100 {"serial: 2700\n", "serial: \n", `"serial" header should not be empty`}, 101 {ss.tsLine, "", `"timestamp" header is mandatory`}, 102 {ss.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, 103 {ss.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, 104 {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`}, 105 {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`}, 106 {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`}, 107 {"device-key-sha3-384: KEYID\n", "", `"device-key-sha3-384" header is mandatory`}, 108 } 109 110 for _, test := range invalidTests { 111 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 112 invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) 113 invalid = strings.Replace(invalid, "KEYID", ss.deviceKey.PublicKey().ID(), 1) 114 _, err := asserts.Decode([]byte(invalid)) 115 c.Check(err, ErrorMatches, serialErrPrefix+test.expectedErr) 116 } 117 } 118 119 func (ss *serialSuite) TestDecodeKeyIDMismatch(c *C) { 120 invalid := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) 121 invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) 122 invalid = strings.Replace(invalid, "KEYID", "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1) 123 124 _, err := asserts.Decode([]byte(invalid)) 125 c.Check(err, ErrorMatches, serialErrPrefix+"device key does not match provided key id") 126 } 127 128 func (ss *serialSuite) TestSerialCheck(c *C) { 129 encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) 130 encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) 131 encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1) 132 ex, err := asserts.Decode([]byte(encoded)) 133 c.Assert(err, IsNil) 134 135 storeDB, db := makeStoreAndCheckDB(c) 136 brandDB := setup3rdPartySigning(c, "brand1", storeDB, db) 137 138 const serialMismatchErr = `serial with authority "generic" different from brand "brand1" without model assertion with serial-authority set to to allow for them` 139 brandID := brandDB.AuthorityID 140 brandKeyID := brandDB.KeyID 141 genericKeyID := storeDB.GenericKey.PublicKeyID() 142 modelNA := []interface{}(nil) 143 brandOnly := []interface{}{} 144 tests := []struct { 145 // serial-authority setting in model 146 // nil == model not available at check (modelNA) 147 // empty == just brand (brandOnly) 148 serialAuth []interface{} 149 signDB assertstest.SignerDB 150 authID string 151 keyID string 152 expectedErr string 153 }{ 154 {modelNA, brandDB, "", brandKeyID, ""}, 155 {brandOnly, brandDB, "", brandKeyID, ""}, 156 {[]interface{}{"generic"}, brandDB, "", brandKeyID, ""}, 157 {[]interface{}{"generic", brandID}, brandDB, "", brandKeyID, ""}, 158 {[]interface{}{"generic"}, storeDB, "generic", genericKeyID, ""}, 159 {brandOnly, storeDB, "generic", genericKeyID, serialMismatchErr}, 160 {modelNA, storeDB, "generic", genericKeyID, serialMismatchErr}, 161 {[]interface{}{"other"}, storeDB, "generic", genericKeyID, serialMismatchErr}, 162 } 163 164 for _, test := range tests { 165 checkDB := db.WithStackedBackstore(asserts.NewMemoryBackstore()) 166 167 if test.serialAuth != nil { 168 modHeaders := map[string]interface{}{ 169 "series": "16", 170 "brand-id": brandID, 171 "architecture": "amd64", 172 "model": "baz-3000", 173 "gadget": "gadget", 174 "kernel": "kernel", 175 "timestamp": time.Now().Format(time.RFC3339), 176 } 177 if len(test.serialAuth) != 0 { 178 modHeaders["serial-authority"] = test.serialAuth 179 } 180 model, err := brandDB.Sign(asserts.ModelType, modHeaders, nil, "") 181 c.Assert(err, IsNil) 182 err = checkDB.Add(model) 183 c.Assert(err, IsNil) 184 } 185 186 headers := ex.Headers() 187 headers["brand-id"] = brandID 188 if test.authID != "" { 189 headers["authority-id"] = test.authID 190 } else { 191 headers["authority-id"] = brandID 192 } 193 headers["timestamp"] = time.Now().Format(time.RFC3339) 194 serial, err := test.signDB.Sign(asserts.SerialType, headers, nil, test.keyID) 195 c.Check(err, IsNil) 196 if err != nil { 197 continue 198 } 199 200 err = checkDB.Check(serial) 201 if test.expectedErr == "" { 202 c.Check(err, IsNil) 203 } else { 204 c.Check(err, ErrorMatches, test.expectedErr) 205 } 206 } 207 } 208 209 func (ss *serialSuite) TestSerialRequestHappy(c *C) { 210 sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType, 211 map[string]interface{}{ 212 "brand-id": "brand-id1", 213 "model": "baz-3000", 214 "device-key": ss.encodedDevKey, 215 "request-id": "REQID", 216 }, []byte("HW-DETAILS"), ss.deviceKey) 217 c.Assert(err, IsNil) 218 219 // roundtrip 220 a, err := asserts.Decode(asserts.Encode(sreq)) 221 c.Assert(err, IsNil) 222 223 sreq2, ok := a.(*asserts.SerialRequest) 224 c.Assert(ok, Equals, true) 225 226 // standalone signature check 227 err = asserts.SignatureCheck(sreq2, sreq2.DeviceKey()) 228 c.Check(err, IsNil) 229 230 c.Check(sreq2.BrandID(), Equals, "brand-id1") 231 c.Check(sreq2.Model(), Equals, "baz-3000") 232 c.Check(sreq2.RequestID(), Equals, "REQID") 233 234 c.Check(sreq2.Serial(), Equals, "") 235 } 236 237 func (ss *serialSuite) TestSerialRequestHappyOptionalSerial(c *C) { 238 sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType, 239 map[string]interface{}{ 240 "brand-id": "brand-id1", 241 "model": "baz-3000", 242 "serial": "pserial", 243 "device-key": ss.encodedDevKey, 244 "request-id": "REQID", 245 }, []byte("HW-DETAILS"), ss.deviceKey) 246 c.Assert(err, IsNil) 247 248 // roundtrip 249 a, err := asserts.Decode(asserts.Encode(sreq)) 250 c.Assert(err, IsNil) 251 252 sreq2, ok := a.(*asserts.SerialRequest) 253 c.Assert(ok, Equals, true) 254 255 c.Check(sreq2.Model(), Equals, "baz-3000") 256 c.Check(sreq2.Serial(), Equals, "pserial") 257 } 258 259 func (ss *serialSuite) TestSerialRequestDecodeInvalid(c *C) { 260 encoded := "type: serial-request\n" + 261 "brand-id: brand-id1\n" + 262 "model: baz-3000\n" + 263 "device-key:\n DEVICEKEY\n" + 264 "request-id: REQID\n" + 265 "serial: S\n" + 266 "body-length: 2\n" + 267 "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" + 268 "HW" + 269 "\n\n" + 270 "AXNpZw==" 271 272 invalidTests := []struct{ original, invalid, expectedErr string }{ 273 {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`}, 274 {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, 275 {"model: baz-3000\n", "", `"model" header is mandatory`}, 276 {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, 277 {"request-id: REQID\n", "", `"request-id" header is mandatory`}, 278 {"request-id: REQID\n", "request-id: \n", `"request-id" header should not be empty`}, 279 {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`}, 280 {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`}, 281 {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`}, 282 {"serial: S\n", "serial:\n - xyz\n", `"serial" header must be a string`}, 283 } 284 285 for _, test := range invalidTests { 286 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 287 invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) 288 289 _, err := asserts.Decode([]byte(invalid)) 290 c.Check(err, ErrorMatches, serialReqErrPrefix+test.expectedErr) 291 } 292 } 293 294 func (ss *serialSuite) TestSerialRequestDecodeKeyIDMismatch(c *C) { 295 invalid := "type: serial-request\n" + 296 "brand-id: brand-id1\n" + 297 "model: baz-3000\n" + 298 "device-key:\n " + strings.Replace(ss.encodedDevKey, "\n", "\n ", -1) + "\n" + 299 "request-id: REQID\n" + 300 "body-length: 2\n" + 301 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" + 302 "HW" + 303 "\n\n" + 304 "AXNpZw==" 305 306 _, err := asserts.Decode([]byte(invalid)) 307 c.Check(err, ErrorMatches, "assertion serial-request: device key does not match included signing key id") 308 } 309 310 func (ss *serialSuite) TestDeviceSessionRequest(c *C) { 311 ts := time.Now().UTC().Round(time.Second) 312 sessReq, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, 313 map[string]interface{}{ 314 "brand-id": "brand-id1", 315 "model": "baz-3000", 316 "serial": "99990", 317 "nonce": "NONCE", 318 "timestamp": ts.Format(time.RFC3339), 319 }, nil, ss.deviceKey) 320 c.Assert(err, IsNil) 321 322 // roundtrip 323 a, err := asserts.Decode(asserts.Encode(sessReq)) 324 c.Assert(err, IsNil) 325 326 sessReq2, ok := a.(*asserts.DeviceSessionRequest) 327 c.Assert(ok, Equals, true) 328 329 // standalone signature check 330 err = asserts.SignatureCheck(sessReq2, ss.deviceKey.PublicKey()) 331 c.Check(err, IsNil) 332 333 c.Check(sessReq2.BrandID(), Equals, "brand-id1") 334 c.Check(sessReq2.Model(), Equals, "baz-3000") 335 c.Check(sessReq2.Serial(), Equals, "99990") 336 c.Check(sessReq2.Nonce(), Equals, "NONCE") 337 c.Check(sessReq2.Timestamp().Equal(ts), Equals, true) 338 } 339 340 func (ss *serialSuite) TestDeviceSessionRequestDecodeInvalid(c *C) { 341 tsLine := "timestamp: " + time.Now().Format(time.RFC3339) + "\n" 342 encoded := "type: device-session-request\n" + 343 "brand-id: brand-id1\n" + 344 "model: baz-3000\n" + 345 "serial: 99990\n" + 346 "nonce: NONCE\n" + 347 tsLine + 348 "body-length: 0\n" + 349 "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" + 350 "AXNpZw==" 351 352 invalidTests := []struct{ original, invalid, expectedErr string }{ 353 {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, 354 {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, 355 {"serial: 99990\n", "", `"serial" header is mandatory`}, 356 {"nonce: NONCE\n", "nonce: \n", `"nonce" header should not be empty`}, 357 {tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, 358 } 359 360 for _, test := range invalidTests { 361 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 362 _, err := asserts.Decode([]byte(invalid)) 363 c.Check(err, ErrorMatches, deviceSessReqErrPrefix+test.expectedErr) 364 } 365 }