github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 {"authority-id: brand-id1\n", "authority-id: random\n", `authority-id and brand-id must match, serial assertions are expected to be signed by the brand: "random" != "brand-id1"`}, 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 tests := []struct { 139 signDB assertstest.SignerDB 140 brandID string 141 authID string 142 keyID string 143 }{ 144 {brandDB, brandDB.AuthorityID, "", brandDB.KeyID}, 145 } 146 147 for _, test := range tests { 148 headers := ex.Headers() 149 headers["brand-id"] = test.brandID 150 if test.authID != "" { 151 headers["authority-id"] = test.authID 152 } else { 153 headers["authority-id"] = test.brandID 154 } 155 headers["timestamp"] = time.Now().Format(time.RFC3339) 156 serial, err := test.signDB.Sign(asserts.SerialType, headers, nil, test.keyID) 157 c.Assert(err, IsNil) 158 159 err = db.Check(serial) 160 c.Check(err, IsNil) 161 } 162 } 163 164 func (ss *serialSuite) TestSerialRequestHappy(c *C) { 165 sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType, 166 map[string]interface{}{ 167 "brand-id": "brand-id1", 168 "model": "baz-3000", 169 "device-key": ss.encodedDevKey, 170 "request-id": "REQID", 171 }, []byte("HW-DETAILS"), ss.deviceKey) 172 c.Assert(err, IsNil) 173 174 // roundtrip 175 a, err := asserts.Decode(asserts.Encode(sreq)) 176 c.Assert(err, IsNil) 177 178 sreq2, ok := a.(*asserts.SerialRequest) 179 c.Assert(ok, Equals, true) 180 181 // standalone signature check 182 err = asserts.SignatureCheck(sreq2, sreq2.DeviceKey()) 183 c.Check(err, IsNil) 184 185 c.Check(sreq2.BrandID(), Equals, "brand-id1") 186 c.Check(sreq2.Model(), Equals, "baz-3000") 187 c.Check(sreq2.RequestID(), Equals, "REQID") 188 189 c.Check(sreq2.Serial(), Equals, "") 190 } 191 192 func (ss *serialSuite) TestSerialRequestHappyOptionalSerial(c *C) { 193 sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType, 194 map[string]interface{}{ 195 "brand-id": "brand-id1", 196 "model": "baz-3000", 197 "serial": "pserial", 198 "device-key": ss.encodedDevKey, 199 "request-id": "REQID", 200 }, []byte("HW-DETAILS"), ss.deviceKey) 201 c.Assert(err, IsNil) 202 203 // roundtrip 204 a, err := asserts.Decode(asserts.Encode(sreq)) 205 c.Assert(err, IsNil) 206 207 sreq2, ok := a.(*asserts.SerialRequest) 208 c.Assert(ok, Equals, true) 209 210 c.Check(sreq2.Model(), Equals, "baz-3000") 211 c.Check(sreq2.Serial(), Equals, "pserial") 212 } 213 214 func (ss *serialSuite) TestSerialRequestDecodeInvalid(c *C) { 215 encoded := "type: serial-request\n" + 216 "brand-id: brand-id1\n" + 217 "model: baz-3000\n" + 218 "device-key:\n DEVICEKEY\n" + 219 "request-id: REQID\n" + 220 "serial: S\n" + 221 "body-length: 2\n" + 222 "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" + 223 "HW" + 224 "\n\n" + 225 "AXNpZw==" 226 227 invalidTests := []struct{ original, invalid, expectedErr string }{ 228 {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`}, 229 {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, 230 {"model: baz-3000\n", "", `"model" header is mandatory`}, 231 {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, 232 {"request-id: REQID\n", "", `"request-id" header is mandatory`}, 233 {"request-id: REQID\n", "request-id: \n", `"request-id" header should not be empty`}, 234 {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`}, 235 {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`}, 236 {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`}, 237 {"serial: S\n", "serial:\n - xyz\n", `"serial" header must be a string`}, 238 } 239 240 for _, test := range invalidTests { 241 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 242 invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) 243 244 _, err := asserts.Decode([]byte(invalid)) 245 c.Check(err, ErrorMatches, serialReqErrPrefix+test.expectedErr) 246 } 247 } 248 249 func (ss *serialSuite) TestSerialRequestDecodeKeyIDMismatch(c *C) { 250 invalid := "type: serial-request\n" + 251 "brand-id: brand-id1\n" + 252 "model: baz-3000\n" + 253 "device-key:\n " + strings.Replace(ss.encodedDevKey, "\n", "\n ", -1) + "\n" + 254 "request-id: REQID\n" + 255 "body-length: 2\n" + 256 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" + 257 "HW" + 258 "\n\n" + 259 "AXNpZw==" 260 261 _, err := asserts.Decode([]byte(invalid)) 262 c.Check(err, ErrorMatches, "assertion serial-request: device key does not match included signing key id") 263 } 264 265 func (ss *serialSuite) TestDeviceSessionRequest(c *C) { 266 ts := time.Now().UTC().Round(time.Second) 267 sessReq, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, 268 map[string]interface{}{ 269 "brand-id": "brand-id1", 270 "model": "baz-3000", 271 "serial": "99990", 272 "nonce": "NONCE", 273 "timestamp": ts.Format(time.RFC3339), 274 }, nil, ss.deviceKey) 275 c.Assert(err, IsNil) 276 277 // roundtrip 278 a, err := asserts.Decode(asserts.Encode(sessReq)) 279 c.Assert(err, IsNil) 280 281 sessReq2, ok := a.(*asserts.DeviceSessionRequest) 282 c.Assert(ok, Equals, true) 283 284 // standalone signature check 285 err = asserts.SignatureCheck(sessReq2, ss.deviceKey.PublicKey()) 286 c.Check(err, IsNil) 287 288 c.Check(sessReq2.BrandID(), Equals, "brand-id1") 289 c.Check(sessReq2.Model(), Equals, "baz-3000") 290 c.Check(sessReq2.Serial(), Equals, "99990") 291 c.Check(sessReq2.Nonce(), Equals, "NONCE") 292 c.Check(sessReq2.Timestamp().Equal(ts), Equals, true) 293 } 294 295 func (ss *serialSuite) TestDeviceSessionRequestDecodeInvalid(c *C) { 296 tsLine := "timestamp: " + time.Now().Format(time.RFC3339) + "\n" 297 encoded := "type: device-session-request\n" + 298 "brand-id: brand-id1\n" + 299 "model: baz-3000\n" + 300 "serial: 99990\n" + 301 "nonce: NONCE\n" + 302 tsLine + 303 "body-length: 0\n" + 304 "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" + 305 "AXNpZw==" 306 307 invalidTests := []struct{ original, invalid, expectedErr string }{ 308 {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, 309 {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, 310 {"serial: 99990\n", "", `"serial" header is mandatory`}, 311 {"nonce: NONCE\n", "nonce: \n", `"nonce" header should not be empty`}, 312 {tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, 313 } 314 315 for _, test := range invalidTests { 316 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 317 _, err := asserts.Decode([]byte(invalid)) 318 c.Check(err, ErrorMatches, deviceSessReqErrPrefix+test.expectedErr) 319 } 320 }