summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
blob: 5ae61568ef3ebe9caae69eb3a533dcf77fbfda83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"use strict";

const { ProductAddonChecker } = ChromeUtils.importESModule(
  "resource://gre/modules/addons/ProductAddonChecker.sys.mjs"
);

Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true);

// Setup a test server for content signature tests.
const signedTestServer = new HttpServer();
const testDataDir = "data/productaddons/";

// Start the server so we can grab the identity. We need to know this so the
// server can reference itself in the handlers that will be set up.
signedTestServer.start();
const signedBaseUri =
  signedTestServer.identity.primaryScheme +
  "://" +
  signedTestServer.identity.primaryHost +
  ":" +
  signedTestServer.identity.primaryPort;

// Setup endpoint to handle x5u lookups correctly.
const validX5uPath = "/valid_x5u";
// These certificates are generated using ./mach generate-test-certs <path_to_certspec>
const validCertChain = loadCertChain(testDataDir + "content_signing", [
  "aus_ee",
  "int",
]);
signedTestServer.registerPathHandler(validX5uPath, (req, res) => {
  res.write(validCertChain.join("\n"));
});
const validX5uUrl = signedBaseUri + validX5uPath;

// Setup endpoint to handle x5u lookups incorrectly.
const invalidX5uPath = "/invalid_x5u";
const invalidCertChain = loadCertChain(testDataDir + "content_signing", [
  "aus_ee",
  // This cert chain is missing the intermediate cert!
]);
signedTestServer.registerPathHandler(invalidX5uPath, (req, res) => {
  res.write(invalidCertChain.join("\n"));
});
const invalidX5uUrl = signedBaseUri + invalidX5uPath;

// Will hold the XML data from good.xml.
let goodXml;
// This sig is generated using the following command at mozilla-central root
// `cat toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml | ./mach python security/manager/ssl/tests/unit/test_content_signing/pysign.py`
// If test certificates are regenerated, this signature must also be.
const goodXmlContentSignature =
  "7QYnPqFoOlS02BpDdIRIljzmPr6BFwPs1z1y8KJUBlnU7EVG6FbnXmVVt5Op9wDzgvhXX7th8qFJvpPOZs_B_tHRDNJ8SK0HN95BAN15z3ZW2r95SSHmU-fP2JgoNOR3";

const goodXmlPath = "/good.xml";
// Requests use query strings to test different signature states.
const validSignatureQuery = "validSignature";
const invalidSignatureQuery = "invalidSignature";
const missingSignatureQuery = "missingSignature";
const incompleteSignatureQuery = "incompleteSignature";
const badX5uSignatureQuery = "badX5uSignature";
signedTestServer.registerPathHandler(goodXmlPath, (req, res) => {
  if (req.queryString == validSignatureQuery) {
    res.setHeader(
      "content-signature",
      `x5u=${validX5uUrl}; p384ecdsa=${goodXmlContentSignature}`
    );
  } else if (req.queryString == invalidSignatureQuery) {
    res.setHeader("content-signature", `x5u=${validX5uUrl}; p384ecdsa=garbage`);
  } else if (req.queryString == missingSignatureQuery) {
    // Intentionally don't set the header.
  } else if (req.queryString == incompleteSignatureQuery) {
    res.setHeader(
      "content-signature",
      `x5u=${validX5uUrl}` // There's no p384ecdsa part!
    );
  } else if (req.queryString == badX5uSignatureQuery) {
    res.setHeader(
      "content-signature",
      `x5u=${invalidX5uUrl}; p384ecdsa=${goodXmlContentSignature}`
    );
  } else {
    Assert.ok(
      false,
      "Invalid queryString passed to server! Tests shouldn't do that!"
    );
  }
  res.write(goodXml);
});

// Handle aysnc load of good.xml.
add_task(async function load_good_xml() {
  goodXml = await IOUtils.readUTF8(do_get_file(testDataDir + "good.xml").path);
});

add_task(async function test_valid_content_signature() {
  try {
    const res = await ProductAddonChecker.getProductAddonList(
      signedBaseUri + goodXmlPath + "?" + validSignatureQuery,
      /*allowNonBuiltIn*/ false,
      /*allowedCerts*/ false,
      /*verifyContentSignature*/ true
    );
    Assert.ok(true, "Should successfully get addon list");

    // Smoke test the results are as expected.
    Assert.equal(res.addons[0].id, "test1");
    Assert.equal(res.addons[1].id, "test2");
    Assert.equal(res.addons[2].id, "test3");
    Assert.equal(res.addons[3].id, "test4");
    Assert.equal(res.addons[4].id, undefined);
  } catch (e) {
    Assert.ok(
      false,
      `Should successfully get addon list, instead failed with ${e}`
    );
  }
});

add_task(async function test_invalid_content_signature() {
  try {
    await ProductAddonChecker.getProductAddonList(
      signedBaseUri + goodXmlPath + "?" + invalidSignatureQuery,
      /*allowNonBuiltIn*/ false,
      /*allowedCerts*/ false,
      /*verifyContentSignature*/ true
    );
    Assert.ok(false, "Should fail to get addon list");
  } catch (e) {
    Assert.ok(true, "Should fail to get addon list");
    // The nsIContentSignatureVerifier will throw an error on this path,
    // check that we've caught and re-thrown, but don't check the full error
    // message as it's messy and subject to change.
    Assert.ok(
      e.message.startsWith("Content signature validation failed:"),
      "Should get signature failure message"
    );
  }
});

add_task(async function test_missing_content_signature_header() {
  try {
    await ProductAddonChecker.getProductAddonList(
      signedBaseUri + goodXmlPath + "?" + missingSignatureQuery,
      /*allowNonBuiltIn*/ false,
      /*allowedCerts*/ false,
      /*verifyContentSignature*/ true
    );
    Assert.ok(false, "Should fail to get addon list");
  } catch (e) {
    Assert.ok(true, "Should fail to get addon list");
    Assert.equal(
      e.addonCheckerErr,
      ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR
    );
    Assert.equal(
      e.message,
      "Content signature validation failed: missing content signature header"
    );
  }
});

add_task(async function test_incomplete_content_signature_header() {
  try {
    await ProductAddonChecker.getProductAddonList(
      signedBaseUri + goodXmlPath + "?" + incompleteSignatureQuery,
      /*allowNonBuiltIn*/ false,
      /*allowedCerts*/ false,
      /*verifyContentSignature*/ true
    );
    Assert.ok(false, "Should fail to get addon list");
  } catch (e) {
    Assert.ok(true, "Should fail to get addon list");
    Assert.equal(
      e.addonCheckerErr,
      ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR
    );
    Assert.equal(
      e.message,
      "Content signature validation failed: missing signature"
    );
  }
});

add_task(async function test_bad_x5u_content_signature_header() {
  try {
    await ProductAddonChecker.getProductAddonList(
      signedBaseUri + goodXmlPath + "?" + badX5uSignatureQuery,
      /*allowNonBuiltIn*/ false,
      /*allowedCerts*/ false,
      /*verifyContentSignature*/ true
    );
    Assert.ok(false, "Should fail to get addon list");
  } catch (e) {
    Assert.ok(true, "Should fail to get addon list");
    Assert.equal(
      e.addonCheckerErr,
      ProductAddonChecker.VERIFICATION_INVALID_ERR
    );
    Assert.equal(e.message, "Content signature is not valid");
  }
});