mirror of
https://github.com/sparkle-project/Sparkle.git
synced 2025-11-01 15:34:38 +00:00
248 lines
12 KiB
Swift
248 lines
12 KiB
Swift
//
|
|
// SUUpdateValidatorTest.swift
|
|
// Sparkle
|
|
//
|
|
// Created by Jordan Rose on 2020-06-13.
|
|
// Copyright © 2020 Sparkle Project. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import XCTest
|
|
|
|
class SUUpdateValidatorTest: XCTestCase {
|
|
enum BundleConfig: String, CaseIterable, Equatable {
|
|
case none = "None"
|
|
case dsaOnly = "DSAOnly"
|
|
case edOnly = "EDOnly"
|
|
case both = "Both"
|
|
case codeSignedOnly = "CodeSignedOnly"
|
|
case codeSignedBoth = "CodeSignedBoth"
|
|
case codeSignedOnlyNew = "CodeSignedOnlyNew"
|
|
case codeSignedBothNew = "CodeSignedBothNew"
|
|
case codeSignedOldED = "CodeSignedOldED"
|
|
case codeSignedInvalidOnly = "CodeSignedInvalidOnly"
|
|
case codeSignedInvalid = "CodeSignedInvalid"
|
|
|
|
var hasAnyKeys: Bool {
|
|
switch self {
|
|
case .none, .codeSignedOnly, .codeSignedOnlyNew, .codeSignedInvalidOnly:
|
|
return false
|
|
case .edOnly, .both, .codeSignedBoth, .codeSignedBothNew, .codeSignedOldED, .codeSignedInvalid:
|
|
return true
|
|
case .dsaOnly:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SignatureConfig: CaseIterable, Equatable, CustomDebugStringConvertible {
|
|
enum State: CaseIterable, Equatable {
|
|
case none, invalid, invalidFormat, valid
|
|
}
|
|
|
|
var ed: State
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
var dsa: State
|
|
#endif
|
|
|
|
static let allCases: [SignatureConfig] = State.allCases.flatMap { dsaState in
|
|
State.allCases.map { edState in
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
SignatureConfig(ed: edState, dsa: dsaState)
|
|
#else
|
|
SignatureConfig(ed: edState)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
var debugDescription: String {
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
return "(ed: \(self.ed), dsa: \(self.dsa))"
|
|
#else
|
|
return "(ed: \(self.ed))"
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func bundle(_ config: BundleConfig) -> Bundle {
|
|
let testBundle = Bundle(for: SUUpdateValidatorTest.self)
|
|
let configBundleURL = testBundle.url(forResource: config.rawValue, withExtension: "bundle", subdirectory: "SUUpdateValidatorTest")!
|
|
return Bundle(url: configBundleURL)!
|
|
}
|
|
|
|
func signatures(_ config: SignatureConfig) -> SUSignatures {
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
let dsaSig: String?
|
|
switch config.dsa {
|
|
case .none: dsaSig = nil
|
|
case .invalid: dsaSig = "ABwCFCIHCIYYkfZavNzTitTW5tlRp/k5AhQ40poFytqcVhIYdCxQznaXeJPJDQ=="
|
|
// Use some invalid base64 strings
|
|
case .invalidFormat: dsaSig = "%%wCFCIHCIYYkfZavNzTitTW5tlRp/k5AhQ40poFytqcVhIYdCxQznaXeJPJDQ=="
|
|
case .valid: dsaSig = "MCwCFCIHCIYYkfZavNzTitTW5tlRp/k5AhQ40poFytqcVhIYdCxQznaXeJPJDQ=="
|
|
}
|
|
#endif
|
|
|
|
let edSig: String?
|
|
switch config.ed {
|
|
case .none: edSig = nil
|
|
case .invalid: edSig = "wTcpXCgWoa4NrJpsfzS61FXJIbv963//12U2ef9xstzVOLPHYK2N4/ojgpDV5N1/NGG1uWMBgK+kEWp0Z5zMDQ=="
|
|
// Use some invalid base64 strings
|
|
case .invalidFormat: edSig = "%%cpXCgWoa4NrJpsfzS61FXJIbv963//12U2ef9xstzVOLPHYK2N4/ojgpDV5N1/NGG1uWMBgK+kEWp0Z5zMDQ=="
|
|
case .valid: edSig = "EIawm2YkDZ2gBfkEMF2+1VuuTeXnCGZOdnMdVgPPvDZioq7bvDayXqKkIIzSjKMmeFdcFJOHdnba5ZV60+gPBw=="
|
|
}
|
|
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
return SUSignatures(ed: edSig, dsa: dsaSig)
|
|
#else
|
|
return SUSignatures(ed: edSig)
|
|
#endif
|
|
}
|
|
|
|
var signedTestFilePath: String {
|
|
let testBundle = Bundle(for: SUUpdateValidatorTest.self)
|
|
return testBundle.path(forResource: "signed-test-file", ofType: "txt")!
|
|
}
|
|
|
|
func testPrevalidation(bundle bundleConfig: BundleConfig, signatures signatureConfig: SignatureConfig, expectedResult: Bool, line: UInt = #line) {
|
|
let host = SUHost(bundle: self.bundle(bundleConfig))
|
|
let signatures = self.signatures(signatureConfig)
|
|
let validator = SUUpdateValidator(downloadPath: self.signedTestFilePath, signatures: signatures, host: host)
|
|
|
|
let result = (try? validator.validateDownloadPath()) != nil
|
|
XCTAssertEqual(result, expectedResult, "bundle: \(bundleConfig), signatures: \(signatureConfig)", line: line)
|
|
}
|
|
|
|
func testPrevalidation() {
|
|
for signatureConfig in SignatureConfig.allCases {
|
|
testPrevalidation(bundle: .none, signatures: signatureConfig, expectedResult: false)
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPrevalidation(bundle: .dsaOnly, signatures: signatureConfig, expectedResult: signatureConfig.dsa == .valid)
|
|
#endif
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPrevalidation(bundle: .edOnly, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid && signatureConfig.dsa != .invalidFormat)
|
|
#else
|
|
testPrevalidation(bundle: .edOnly, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid)
|
|
#endif
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPrevalidation(bundle: .both, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid && signatureConfig.dsa != .invalidFormat)
|
|
#else
|
|
testPrevalidation(bundle: .both, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func testPostValidation(oldBundle oldBundleConfig: BundleConfig, newBundle newBundleConfig: BundleConfig, signatures signatureConfig: SignatureConfig, expectedResult: Bool, line: UInt = #line) {
|
|
let oldBundle = self.bundle(oldBundleConfig)
|
|
let host = SUHost(bundle: oldBundle)
|
|
let signatures = self.signatures(signatureConfig)
|
|
|
|
let validator = SUUpdateValidator(downloadPath: self.signedTestFilePath, signatures: signatures, host: host)
|
|
|
|
let updateDirectory = temporaryDirectory("SUUpdateValidatorTest")!
|
|
defer { try! FileManager.default.removeItem(atPath: updateDirectory) }
|
|
let newBundle = self.bundle(newBundleConfig)
|
|
try! FileManager.default.copyItem(at: newBundle.bundleURL, to: URL(fileURLWithPath: updateDirectory).appendingPathComponent(oldBundle.bundleURL.lastPathComponent))
|
|
|
|
let result = (try? validator.validate(withUpdateDirectory: updateDirectory)) != nil
|
|
XCTAssertEqual(result, expectedResult, "oldBundle: \(oldBundleConfig), newBundle: \(newBundleConfig), signatures: \(signatureConfig)", line: line)
|
|
}
|
|
|
|
func testPostValidation(bundle bundleConfig: BundleConfig, signatures signatureConfig: SignatureConfig, expectedResult: Bool, line: UInt = #line) {
|
|
testPostValidation(oldBundle: bundleConfig, newBundle: bundleConfig, signatures: signatureConfig, expectedResult: expectedResult, line: line)
|
|
}
|
|
|
|
func testPostValidationWithoutCodeSigning() {
|
|
for signatureConfig in SignatureConfig.allCases {
|
|
testPostValidation(bundle: .none, signatures: signatureConfig, expectedResult: false)
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPostValidation(bundle: .dsaOnly, signatures: signatureConfig, expectedResult: signatureConfig.dsa == .valid)
|
|
#endif
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPostValidation(bundle: .edOnly, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid && signatureConfig.dsa != .invalidFormat)
|
|
#else
|
|
testPostValidation(bundle: .edOnly, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid)
|
|
#endif
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPostValidation(bundle: .both, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid && signatureConfig.dsa != .invalidFormat)
|
|
#else
|
|
testPostValidation(bundle: .both, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func testPostValidationWithCodeSigning() {
|
|
for signatureConfig in SignatureConfig.allCases {
|
|
testPostValidation(bundle: .codeSignedOnly, signatures: signatureConfig, expectedResult: true)
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPostValidation(bundle: .codeSignedBoth, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid && signatureConfig.dsa != .invalidFormat)
|
|
#else
|
|
testPostValidation(bundle: .codeSignedBoth, signatures: signatureConfig, expectedResult: signatureConfig.ed == .valid)
|
|
#endif
|
|
|
|
testPostValidation(bundle: .codeSignedInvalidOnly, signatures: signatureConfig, expectedResult: false)
|
|
testPostValidation(bundle: .codeSignedInvalid, signatures: signatureConfig, expectedResult: false)
|
|
}
|
|
}
|
|
|
|
func testPostValidationWithKeyRemoval() {
|
|
for bundleConfig in BundleConfig.allCases {
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPostValidation(oldBundle: .dsaOnly, newBundle: bundleConfig, signatures: SignatureConfig(ed: .valid, dsa: .valid), expectedResult: bundleConfig.hasAnyKeys && bundleConfig != .codeSignedInvalid)
|
|
#else
|
|
testPostValidation(oldBundle: .dsaOnly, newBundle: bundleConfig, signatures: SignatureConfig(ed: .valid), expectedResult: false)
|
|
#endif
|
|
do {
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
let signatureConfig = SignatureConfig(ed: .valid, dsa: .valid)
|
|
#else
|
|
let signatureConfig = SignatureConfig(ed: .valid)
|
|
#endif
|
|
testPostValidation(oldBundle: .edOnly, newBundle: bundleConfig, signatures: signatureConfig, expectedResult: bundleConfig.hasAnyKeys && bundleConfig != .codeSignedInvalid)
|
|
}
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
testPostValidation(oldBundle: .both, newBundle: bundleConfig, signatures: SignatureConfig(ed: .valid, dsa: .valid), expectedResult: bundleConfig.hasAnyKeys && bundleConfig != .codeSignedInvalid)
|
|
#else
|
|
testPostValidation(oldBundle: .both, newBundle: bundleConfig, signatures: SignatureConfig(ed: .valid), expectedResult: bundleConfig.hasAnyKeys && bundleConfig != .codeSignedInvalid)
|
|
#endif
|
|
do {
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
let signatureConfig = SignatureConfig(ed: .valid, dsa: .valid)
|
|
#else
|
|
let signatureConfig = SignatureConfig(ed: .valid)
|
|
#endif
|
|
testPostValidation(oldBundle: .codeSignedBoth, newBundle: bundleConfig, signatures: signatureConfig, expectedResult: bundleConfig.hasAnyKeys && bundleConfig != .codeSignedInvalid)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testPostValidationWithKeyRotation() {
|
|
for signatureConfig in SignatureConfig.allCases {
|
|
#if SPARKLE_BUILD_LEGACY_DSA_SUPPORT
|
|
let signatureIsValid = (signatureConfig.ed == .valid && signatureConfig.dsa != .invalidFormat)
|
|
#else
|
|
let signatureIsValid = (signatureConfig.ed == .valid)
|
|
#endif
|
|
|
|
// It's okay to add DSA keys or add code signing.
|
|
testPostValidation(oldBundle: .codeSignedOnly, newBundle: .codeSignedBoth, signatures: signatureConfig, expectedResult: signatureIsValid)
|
|
testPostValidation(oldBundle: .both, newBundle: .codeSignedBoth, signatures: signatureConfig, expectedResult: signatureIsValid)
|
|
|
|
// If you want to change your code signing, you have to be using both forms of auth.
|
|
testPostValidation(oldBundle: .codeSignedOnly, newBundle: .codeSignedOnlyNew, signatures: signatureConfig, expectedResult: false)
|
|
testPostValidation(oldBundle: .codeSignedBoth, newBundle: .codeSignedOnlyNew, signatures: signatureConfig, expectedResult: false)
|
|
testPostValidation(oldBundle: .codeSignedOnly, newBundle: .codeSignedBothNew, signatures: signatureConfig, expectedResult: false)
|
|
testPostValidation(oldBundle: .codeSignedBoth, newBundle: .codeSignedBothNew, signatures: signatureConfig, expectedResult: signatureIsValid)
|
|
|
|
// If you want to change your keys, you have to be using both forms of auth.
|
|
testPostValidation(oldBundle: .codeSignedOldED, newBundle: .codeSignedOnly, signatures: signatureConfig, expectedResult: false)
|
|
testPostValidation(oldBundle: .codeSignedOldED, newBundle: .codeSignedBoth, signatures: signatureConfig, expectedResult: signatureIsValid)
|
|
|
|
// You can't change two things at once.
|
|
testPostValidation(oldBundle: .codeSignedOldED, newBundle: .codeSignedBothNew, signatures: signatureConfig, expectedResult: false)
|
|
|
|
// It's permitted to remove code signing too.
|
|
testPostValidation(oldBundle: .codeSignedBoth, newBundle: .both, signatures: signatureConfig, expectedResult: signatureIsValid)
|
|
}
|
|
}
|
|
}
|