Skip to content

[Collections] Signing (part 5): validate signature #3252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ string(COMPARE EQUAL ${CMAKE_SYSTEM_NAME} Windows CMAKE_INSTALL_DEFAULT)
option(USE_CMAKE_INSTALL
"Install build products using cmake's install() instead of the bootstrap script's install()"
${CMAKE_INSTALL_DEFAULT})

if(BUILD_SHARED_LIBS)
set(CMAKE_POSITION_INDEPENDENT_CODE YES)
endif()

if(FIND_PM_DEPS)
find_package(TSC CONFIG REQUIRED)
Expand Down
Binary file not shown.
Binary file not shown.
Binary file added Fixtures/Collections/Signing/TestRootCA_rsa.cer
Binary file not shown.
Binary file added Fixtures/Collections/Signing/Test_ec.cer
Binary file not shown.
5 changes: 5 additions & 0 deletions Fixtures/Collections/Signing/Test_ec_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKRNt0dFe1qbIFqyWbpU3dvrdzRqZ18BrQBhIoSzm8K2oAoGCCqGSM49
AwEHoUQDQgAE7TEGQSoJ6YWtocE3GTe/GEXgLayMdIGDe1OL66KLECP1CKm0BsJy
Cz5Ae+Rox51jc8zTUcniBXZRNhoP6+6AhQ==
-----END EC PRIVATE KEY-----
Binary file added Fixtures/Collections/Signing/Test_rsa.cer
Binary file not shown.
27 changes: 27 additions & 0 deletions Fixtures/Collections/Signing/Test_rsa_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtFFRh3JRc3PEWmjJ6iygXdSyIeIrNYgVYQPU9t5QwrFQvWSE
IvLaxmKWY9cHOacYJaOgfK2LKuod72K0xFOKX00Ww9Pt6mileP9SKpCkwcT8WN4Z
BLH1dhUqWt32ZZnay0TDs9XT8JQM8vwZrQ2+TlOvVTJdmU8GQzeponj4iNRvoc6Q
g3CvBtADhJHfKW+TGFfDQf284NQEs/95DRual90yeu/E46uY18PGU2jlhJdyugAy
+dK+bBTvZrg0AGylGGZJEQeqPzmWkwMuRsyRTop606yd53op6MhJCEOrlE0aWvUs
Spd/OLPuh5+AhS0RLCkQ8wz/bYZbR66Z4pABewIDAQABAoIBAQCwJuzFrAkkB0kf
pVTzfqsfXwSyEzdw8UMpZkvq613sBLrCemqXlbXhrjgKyuqVCMaPJp1Gj2bwAoxB
6qR7Ur1PwohlwCihIZ/dZ1fGm01Iun5m9nlsW8lWlPCumj32HWpfvwqMKW0Fjixk
R6FxrIZoEFqtmSlU9p1AlyURwqnRSEsAHGeeIj4owD69p5fegjwOjVsJJdvnrU1Y
6iRH3ywlsasv8vonwWiqo2rY3z9SXXb4Omni6U39sKQfDH002wBtZNL9rt/Yx8CD
ua2iikH1BXOWKHl0Wu/swfkPqscX0nYPucMkcUCwZ+xAxZ8DIc1Y/yzgtNaiYEox
GsIMbzMpAoGBANmq1kMzf594jNmb8a23mB50viZYjgLQ8esBZVNHERRK8gAl4feH
uoNvkBdhmT3BtaQCl7RFP315I1LUGctjaWbs12xc2L+5t7kWVfAyHFo5n6eiXoQC
zIretNBzmILp1IJ76atKyhWuH0YWh+UWL6S8rr9K0m6ZWUqSOrvTioPPAoGBANQS
omXglhKBtZGRXiaZpZt2Qz/nPNY95NLEK3yN6lwvI192KEjEkulqJyFELmFoeK4P
uAq5yuXp6qB7BlqxZYGj6/qnsTomeJZb0dwimISHXM46WoSq5sJ0srn8ln/N30PK
8NysaCLaIz13Jonll5D2kCvvZ4Ia8WJq+LkaapaVAoGAZC/K4TGR+3/MLNknW1MW
9GW9o/68lrU/tHB3B+a9CL8aNlE5eeqCQb8W7nwgwZkolu4Oj44UFBeu15ACs2f1
esdmvFzb8xtzYgDS23TlMe41+z20DUUQipbJWOzr9M3V351TR2FsNKBpiqQSNrKI
iWXDdQ7mXru8qqM134AV0GcCgYAKLiLRlShfFw7qP/ovDC0g+1pbFPScrDfxzizw
O7fGWRTvnjJs29LZlZjvReCcGHHCmUqSaTzOMJ5subsiW2WuBXpse+RMEFC1lw7J
7Hc51W2lELQLrlCJgSSbPP7Uf8N586IAVd5h3erXJoMZF4ZhFRTypvlnC3gO62ep
KxV2yQKBgF5nM2fs31xPsHN3Q/iGiQQQG//PIUttCl47XcUnxoabgebIq+lp5UmQ
ArYcBO4+cBZSbNjmUdUOSyM8fAUrWmy4QyvZXNy15V7W6qVzxfa0hT8T6tFUVmKG
qseI/cG+CoygHw9OqBcffl1d8LVAHmF8mkfzJ2CnQs9CLFxS1+f9
-----END RSA PRIVATE KEY-----
25 changes: 20 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,28 @@ let package = Package(
dependencies: ["SwiftToolsSupport-auto", "Basics", "PackageLoading", "PackageModel", "SourceControl"]),

// MARK: Package Collections

.target(
/** Data structures and support for package collections */
name: "PackageCollections",
dependencies: ["SwiftToolsSupport-auto", "Basics", "PackageModel", "SourceControl", "PackageCollectionsModel", "PackageCollectionsSigning"]),

.target(
/** Package collections models */
name: "PackageCollectionsModel",
dependencies: []),

.target(
/** Data structures and support for package collections */
name: "PackageCollections",
dependencies: ["SwiftToolsSupport-auto", "Basics", "PackageModel", "SourceControl", "PackageCollectionsModel", "Crypto"]),
/** Package collections signing C lib */
name: "PackageCollectionsSigningLibc",
dependencies: ["Crypto"],
cSettings: [
.define("WIN32_LEAN_AND_MEAN"),
]),
.target(
/** Package collections signing */
name: "PackageCollectionsSigning",
dependencies: ["PackageCollectionsModel", "PackageCollectionsSigningLibc", "Crypto", "Basics"]),

// MARK: Package Manager Functionality

Expand Down Expand Up @@ -262,12 +274,15 @@ let package = Package(
.testTarget(
name: "PackageGraphPerformanceTests",
dependencies: ["PackageGraph", "SPMTestSupport"]),
.testTarget(
name: "PackageCollectionsTests",
dependencies: ["PackageCollections", "SPMTestSupport"]),
.testTarget(
name: "PackageCollectionsModelTests",
dependencies: ["PackageCollectionsModel"]),
.testTarget(
name: "PackageCollectionsTests",
dependencies: ["SPMTestSupport", "PackageCollections"]),
name: "PackageCollectionsSigningTests",
dependencies: ["PackageCollectionsSigning", "SPMTestSupport"]),
.testTarget(
name: "SourceControlTests",
dependencies: ["SourceControl", "SPMTestSupport"]),
Expand Down
4 changes: 3 additions & 1 deletion Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
# Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
Expand All @@ -12,6 +12,8 @@ add_subdirectory(Commands)
add_subdirectory(LLBuildManifest)
add_subdirectory(PackageCollections)
add_subdirectory(PackageCollectionsModel)
add_subdirectory(PackageCollectionsSigning)
add_subdirectory(PackageCollectionsSigningLibc)
add_subdirectory(PackageDescription)
add_subdirectory(PackageGraph)
add_subdirectory(PackageLoading)
Expand Down
5 changes: 2 additions & 3 deletions Sources/PackageCollections/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@ target_link_libraries(PackageCollections PUBLIC
TSCUtility
Basics
Crypto
CCryptoBoringSSL
PackageCollectionsModel
PackageCollectionsSigning
PackageModel
SourceControl)
# NOTE(compnerd) workaround for CMake not setting up include flags yet
set_target_properties(PackageCollections PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
target_link_options(PackageCollections PRIVATE
"$<$<PLATFORM_ID:Darwin>:SHELL:-Xlinker -framework -Xlinker Security>")

if(USE_CMAKE_INSTALL)
install(TARGETS PackageCollections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import class Foundation.JSONDecoder
import struct Foundation.URL

import PackageCollectionsModel
import PackageCollectionsSigning
import PackageModel
import SourceControl
import TSCBasic
Expand All @@ -26,12 +27,20 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
private let diagnosticsEngine: DiagnosticsEngine?
private let httpClient: HTTPClient
private let decoder: JSONDecoder
private let signatureValidator: PackageCollectionSignatureValidator

init(configuration: Configuration = .init(), httpClient: HTTPClient? = nil, diagnosticsEngine: DiagnosticsEngine? = nil) {
init(configuration: Configuration = .init(),
httpClient: HTTPClient? = nil,
signatureValidator: PackageCollectionSignatureValidator? = nil,
diagnosticsEngine: DiagnosticsEngine? = nil) {
self.configuration = configuration
self.diagnosticsEngine = diagnosticsEngine
self.httpClient = httpClient ?? Self.makeDefaultHTTPClient(diagnosticsEngine: diagnosticsEngine)
self.decoder = JSONDecoder.makeWithDefaults()
self.signatureValidator = signatureValidator ?? PackageCollectionSigning(
trustedRootCertsDir: configuration.trustedRootCertsDir,
diagnosticsEngine: diagnosticsEngine
)
}

func get(_ source: Model.CollectionSource, callback: @escaping (Result<Model.Collection, Error>) -> Void) {
Expand Down Expand Up @@ -97,14 +106,21 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
do {
// parse json and construct result
do {
// This fails if "signature" is missing
let signature = try JSONModel.SignedCollection.signature(from: body, using: self.decoder)
// TODO: Check collection's signature
// If signature is
// a. valid: process the collection; set isSigned=true
// b. invalid: includes expired cert, untrusted cert, signature-payload mismatch => return error
let collection = try JSONModel.SignedCollection.collection(from: body, using: self.decoder)
callback(self.makeCollection(from: collection, source: source, signature: Model.SignatureData(from: signature)))
// This fails if collection is not signed (i.e., no "signature")
let signedCollection = try self.decoder.decode(JSONModel.SignedCollection.self, from: body)
let certPolicyKey = self.configuration.certificatePolicyKey(for: source) ?? .default
// Check the signature
self.signatureValidator.validate(signedCollection: signedCollection, certPolicyKey: certPolicyKey, jsonDecoder: self.decoder) { result in
switch result {
case .failure(let error):
return callback(.failure(error))
case .success(let isValid):
guard isValid else {
return callback(.failure(Errors.invalidSignature))
}
return callback(self.makeCollection(from: signedCollection.collection, source: source, signature: Model.SignatureData(from: signedCollection.signature)))
}
}
} catch {
// Collection is not signed
guard let collection = try response.decodeBody(JSONModel.Collection.self, using: self.decoder) else {
Expand Down Expand Up @@ -213,17 +229,30 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {

public struct Configuration {
public var maximumSizeInBytes: Int
public var trustedRootCertsDir: URL?
public var sourceCertPolicies: [String: CertificatePolicyKey]

public init(maximumSizeInBytes: Int? = nil) {
public init(maximumSizeInBytes: Int? = nil,
trustedRootCertsDir: URL? = nil,
sourceCertPolicies: [String: CertificatePolicyKey]? = nil) {
// TODO: where should we read defaults from?
self.maximumSizeInBytes = maximumSizeInBytes ?? 5_000_000 // 5MB
self.trustedRootCertsDir = trustedRootCertsDir
self.sourceCertPolicies = sourceCertPolicies ?? [:]
}

func certificatePolicyKey(for source: Model.CollectionSource) -> CertificatePolicyKey? {
// Certificate policy is associated to a collection host
guard let host = source.url.host else { return nil }
return self.sourceCertPolicies[host]
}
}

public enum Errors: Error {
case invalidJSON(Error)
case invalidResponse(String)
case responseTooLarge(Int)
case invalidSignature
}
}

Expand Down
48 changes: 0 additions & 48 deletions Sources/PackageCollectionsModel/PackageCollectionModel+v1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -431,52 +431,4 @@ extension PackageCollectionModel.V1.SignedCollection: Codable {
self.collection = try PackageCollectionModel.V1.Collection(from: decoder)
self.signature = try container.decode(PackageCollectionModel.V1.Signature.self, forKey: .signature)
}

// MARK: - Extract value for single key path

static let keyPathKey = "key_path"

public static func collection(from data: Data, using decoder: JSONDecoder) throws -> PackageCollectionModel.V1.Collection {
guard let keyPathUserInfoKey = CodingUserInfoKey(rawValue: Self.keyPathKey) else {
throw KeyPathValueError.invalidUserInfo
}
decoder.userInfo[keyPathUserInfoKey] = \PackageCollectionModel.V1.SignedCollection.collection
return try decoder.decode(KeyPathValue<PackageCollectionModel.V1.Collection>.self, from: data).value
}

public static func signature(from data: Data, using decoder: JSONDecoder) throws -> PackageCollectionModel.V1.Signature {
guard let keyPathUserInfoKey = CodingUserInfoKey(rawValue: Self.keyPathKey) else {
throw KeyPathValueError.invalidUserInfo
}
decoder.userInfo[keyPathUserInfoKey] = \PackageCollectionModel.V1.SignedCollection.signature
return try decoder.decode(KeyPathValue<PackageCollectionModel.V1.Signature>.self, from: data).value
}

private struct KeyPathValue<T: Decodable>: Decodable {
let value: T

init(from decoder: Decoder) throws {
guard let keyPathUserInfoKey = CodingUserInfoKey(rawValue: PackageCollectionModel.V1.SignedCollection.keyPathKey) else {
throw KeyPathValueError.invalidUserInfo
}
guard let keyPath = decoder.userInfo[keyPathUserInfoKey] as? KeyPath<PackageCollectionModel.V1.SignedCollection, T> else {
throw KeyPathValueError.missingUserInfo
}
switch keyPath {
case \PackageCollectionModel.V1.SignedCollection.collection:
self.value = try T(from: decoder)
case \PackageCollectionModel.V1.SignedCollection.signature:
let container = try decoder.container(keyedBy: PackageCollectionModel.V1.SignedCollection.CodingKeys.self)
self.value = try container.decode(T.self, forKey: .signature) as T
default:
throw KeyPathValueError.unknownKeyPath
}
}
}

public enum KeyPathValueError: Error {
case invalidUserInfo
case missingUserInfo
case unknownKeyPath
}
}
59 changes: 59 additions & 0 deletions Sources/PackageCollectionsSigning/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2021 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_library(PackageCollectionsSigning
Certificate/Certificate.swift
Certificate/CertificatePolicy.swift
Key/ASN1/ASN1.swift
Key/ASN1/ASN1Error.swift
Key/ASN1/PEMDocument.swift
Key/ASN1/SEC1PrivateKey.swift
Key/ASN1/SubjectPublicKeyInfo.swift
Key/ASN1/Types/ASN1BitString.swift
Key/ASN1/Types/ASN1Identifier.swift
Key/ASN1/Types/ASN1Integer.swift
Key/ASN1/Types/ASN1ObjectIdentifier.swift
Key/ASN1/Types/ASN1OctetString.swift
Key/BoringSSLKey.swift
Key/Key.swift
Key/Key+EC.swift
Key/Key+RSA.swift
PackageCollectionSigning.swift
Signing/BoringSSLSigning.swift
Signing/Signature.swift
Signing/Signing.swift
Signing/Signing+ECKey.swift
Signing/Signing+RSAKey.swift
Utilities/Base64URL.swift
Utilities/Utilities.swift)
target_link_libraries(PackageCollectionsSigning PUBLIC
Basics
PackageCollectionsModel
TSCBasic
$<$<NOT:$<PLATFORM_ID:Darwin>>:dispatch>
$<$<NOT:$<PLATFORM_ID:Darwin>>:Foundation>)
target_link_libraries(PackageCollectionsSigning PRIVATE
Crypto
CCryptoBoringSSL
PackageCollectionsSigningLibc)
target_include_directories(PackageCollectionsSigning PRIVATE
$<TARGET_PROPERTY:PackageCollectionsSigningLibc,INCLUDE_DIRECTORIES>)

# NOTE(compnerd) workaround for CMake not setting up include flags yet
set_target_properties(PackageCollectionsSigning PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
target_link_options(PackageCollectionsSigning PRIVATE
"$<$<PLATFORM_ID:Darwin>:SHELL:-Xlinker -framework -Xlinker Security>")

if(USE_CMAKE_INSTALL)
install(TARGETS PackageCollectionsSigning
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
endif()
set_property(GLOBAL APPEND PROPERTY SwiftPM_EXPORTS PackageCollectionsSigning)
Loading