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  }