1 //  Copyright (c) 2020 650 Industries, Inc. All rights reserved.
2 
3 import ExpoModulesTestCore
4 
5 @testable import EXUpdates
6 
7 class CodeSigningConfigurationSpec : ExpoSpec {
specnull8   override func spec() {
9     it("works with spearate certificate chain") {
10       let leafCert = getTestCertificate(TestCertificate.chainLeaf)
11       let intermediateCert = getTestCertificate(TestCertificate.chainIntermediate)
12       let rootCert = getTestCertificate(TestCertificate.chainRoot)
13 
14       let testCert = getTestCertificate(TestCertificate.test)
15 
16       let chain1 = CodeSigningConfiguration.separateCertificateChain(certificateChainInManifestResponse: leafCert)
17       expect(chain1.count) == 1
18 
19       let chain2 = CodeSigningConfiguration.separateCertificateChain(certificateChainInManifestResponse: leafCert + intermediateCert)
20       expect(chain2.count) == 2
21 
22       let chain3 = CodeSigningConfiguration.separateCertificateChain(certificateChainInManifestResponse: leafCert + intermediateCert + rootCert)
23       expect(chain3.count) == 3
24 
25       let chainWithABunchOfNewlinesAndStuff  = CodeSigningConfiguration.separateCertificateChain(
26         certificateChainInManifestResponse: testCert + "\n\n\n\n" + testCert
27       )
28       expect(chainWithABunchOfNewlinesAndStuff.count) == 2
29     }
30 
31     describe("createAcceptSignatureHeader") {
32       it("creates signature header default values") {
33         let cert = getTestCertificate(TestCertificate.test)
34         let configuration = try CodeSigningConfiguration(
35           embeddedCertificateString: cert,
36           metadata: [:],
37           includeManifestResponseCertificateChain: false,
38           allowUnsignedManifests: false
39         )
40         let signatureHeader = configuration.createAcceptSignatureHeader()
41         expect(signatureHeader) == "sig, keyid=\"root\", alg=\"rsa-v1_5-sha256\""
42       }
43 
44       it("creates signature header values from config") {
45         let cert = getTestCertificate(TestCertificate.test)
46         let configuration = try CodeSigningConfiguration(
47           embeddedCertificateString: cert,
48           metadata: [
49             CodeSigningMetadataFields.AlgorithmFieldKey: CodeSigningAlgorithm.RSA_SHA256.rawValue,
50             CodeSigningMetadataFields.KeyIdFieldKey: "test"
51           ],
52           includeManifestResponseCertificateChain: false,
53           allowUnsignedManifests: false
54         )
55         let signatureHeader = configuration.createAcceptSignatureHeader()
56         expect(signatureHeader) == "sig, keyid=\"test\", alg=\"rsa-v1_5-sha256\""
57       }
58 
59       it("creates signature header escaped values") {
60         let cert = getTestCertificate(TestCertificate.test)
61         let configuration = try CodeSigningConfiguration(
62           embeddedCertificateString: cert,
63           metadata: [
64             CodeSigningMetadataFields.AlgorithmFieldKey: CodeSigningAlgorithm.RSA_SHA256.rawValue,
65             CodeSigningMetadataFields.KeyIdFieldKey: #"test"hello\"#
66           ],
67           includeManifestResponseCertificateChain: false,
68           allowUnsignedManifests: false
69         )
70         let signatureHeader = configuration.createAcceptSignatureHeader()
71         expect(signatureHeader) == #"sig, keyid="test\"hello\\", alg="rsa-v1_5-sha256""#
72       }
73 
74       it("creates signature header throws invalid arg") {
75         let cert = getTestCertificate(TestCertificate.test)
76         expect {
77           try CodeSigningConfiguration(
78             embeddedCertificateString: cert,
79             metadata: [
80               CodeSigningMetadataFields.AlgorithmFieldKey: "fake",
81               CodeSigningMetadataFields.KeyIdFieldKey: "test"
82             ],
83             includeManifestResponseCertificateChain: false,
84             allowUnsignedManifests: false)
85         }.to(throwError(CodeSigningError.AlgorithmParseError))
86       }
87     }
88 
89     describe("validateSignature") {
90       it("works for valid case") {
91         let cert = getTestCertificate(TestCertificate.test)
92         let configuration = try CodeSigningConfiguration(
93           embeddedCertificateString: cert,
94           metadata: [:],
95           includeManifestResponseCertificateChain: false,
96           allowUnsignedManifests: false
97         )
98         let signatureValidationResult = try configuration.validateSignature(
99           signature: CertificateFixtures.testNewManifestBodySignature,
100           signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
101           manifestResponseCertificateChain: nil
102         )
103         expect(signatureValidationResult.validationResult) == ValidationResult.valid
104         expect(signatureValidationResult.expoProjectInformation).to(beNil())
105       }
106 
107       it("returns false when signature is invalid") {
108         let cert = getTestCertificate(TestCertificate.test)
109         let configuration = try CodeSigningConfiguration(
110           embeddedCertificateString: cert,
111           metadata: [:],
112           includeManifestResponseCertificateChain: false,
113           allowUnsignedManifests: false
114         )
115         let signatureValidationResult = try configuration.validateSignature(
116           signature: "sig=\"aGVsbG8=\"",
117           signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
118           manifestResponseCertificateChain: nil
119         )
120         expect(signatureValidationResult.validationResult) == ValidationResult.invalid
121         expect(signatureValidationResult.expoProjectInformation).to(beNil())
122       }
123 
124       it("throws when key does not match") {
125         let cert = getTestCertificate(TestCertificate.test)
126         let configuration = try CodeSigningConfiguration(
127           embeddedCertificateString: cert,
128           metadata: [CodeSigningMetadataFields.KeyIdFieldKey: "test"],
129           includeManifestResponseCertificateChain: false,
130           allowUnsignedManifests: false
131         )
132         expect {
133           try configuration.validateSignature(
134             signature: "sig=\"aGVsbG8=\", keyid=\"other\"",
135             signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
136             manifestResponseCertificateChain: nil
137           )
138         }.to(throwError(CodeSigningError.KeyIdMismatchError))
139       }
140 
141       it("does not use chain in manifest response if flag is false") {
142         let testCert = getTestCertificate(TestCertificate.test)
143         let leafCert = getTestCertificate(TestCertificate.chainLeaf)
144         let intermediateCert = getTestCertificate(TestCertificate.chainIntermediate)
145         let configuration = try CodeSigningConfiguration(
146           embeddedCertificateString: testCert,
147           metadata: [:],
148           includeManifestResponseCertificateChain: false,
149           allowUnsignedManifests: false
150         )
151         let signatureValidationResult = try configuration.validateSignature(
152           signature: CertificateFixtures.testNewManifestBodySignature,
153           signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
154           manifestResponseCertificateChain: leafCert + intermediateCert
155         )
156         expect(signatureValidationResult.validationResult) == ValidationResult.valid
157         expect(signatureValidationResult.expoProjectInformation).to(beNil())
158       }
159 
160       it("does use chain in manifest response if flag is true") {
161         let leafCert = getTestCertificate(TestCertificate.chainLeaf)
162         let intermediateCert = getTestCertificate(TestCertificate.chainIntermediate)
163         let rootCert = getTestCertificate(TestCertificate.chainRoot)
164         let configuration = try CodeSigningConfiguration(
165           embeddedCertificateString: rootCert,
166           metadata: [CodeSigningMetadataFields.KeyIdFieldKey: "ca-root"],
167           includeManifestResponseCertificateChain: true,
168           allowUnsignedManifests: false
169         )
170         let signatureValidationResult = try configuration.validateSignature(
171           signature: CertificateFixtures.testNewManifestBodyValidChainLeafSignature,
172           signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
173           manifestResponseCertificateChain: leafCert + intermediateCert
174         )
175         expect(signatureValidationResult.validationResult) == ValidationResult.valid
176 
177         let expoProjectInformation = signatureValidationResult.expoProjectInformation
178         expect(expoProjectInformation?.scopeKey) == "@test/app"
179         expect(expoProjectInformation?.projectId) == "285dc9ca-a25d-4f60-93be-36dc312266d7"
180       }
181 
182       it("AllowsUnsignedManifestIfAllowUnsignedFlagIsTrue") {
183         let testCert = getTestCertificate(TestCertificate.test)
184         let configuration = try CodeSigningConfiguration(
185           embeddedCertificateString: testCert,
186           metadata: [CodeSigningMetadataFields.KeyIdFieldKey: "test"],
187           includeManifestResponseCertificateChain: true,
188           allowUnsignedManifests: true
189         )
190         let signatureValidationResult = try configuration.validateSignature(
191           signature: nil,
192           signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
193           manifestResponseCertificateChain: nil
194         )
195         expect(signatureValidationResult.validationResult) == ValidationResult.skipped
196         expect(signatureValidationResult.expoProjectInformation).to(beNil())
197       }
198 
199       it("ChecksSignedManifestIfAllowUnsignedFlagIsTrueButSignatureIsProvided") {
200         let testCert = getTestCertificate(TestCertificate.test)
201         let configuration = try CodeSigningConfiguration(
202           embeddedCertificateString: testCert,
203           metadata: [CodeSigningMetadataFields.KeyIdFieldKey: "test"],
204           includeManifestResponseCertificateChain: true,
205           allowUnsignedManifests: true
206         )
207         let signatureValidationResult = try configuration.validateSignature(
208           signature: "sig=\"aGVsbG8=\"",
209           signedData: CertificateFixtures.testNewManifestBody.data(using: .utf8)!,
210           manifestResponseCertificateChain: nil
211         )
212         expect(signatureValidationResult.validationResult) == ValidationResult.invalid
213         expect(signatureValidationResult.expoProjectInformation).to(beNil())
214       }
215     }
216   }
217 }
218