Skip to content

Commit 6d34a73

Browse files
committed
Add signing tool
This is **experimental**!!! Depends on swiftlang/swift-package-manager#3272
1 parent b213535 commit 6d34a73

File tree

18 files changed

+329
-36
lines changed

18 files changed

+329
-36
lines changed

Package.swift

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ let package = Package(
99
products: [
1010
.library(name: "PackageCollectionGenerator", targets: ["PackageCollectionGenerator"]),
1111
.executable(name: "package-collection-generate", targets: ["PackageCollectionGeneratorExecutable"]),
12+
.library(name: "PackageCollectionSigner", targets: ["PackageCollectionSigner"]),
13+
.executable(name: "package-collection-sign", targets: ["PackageCollectionSignerExecutable"]),
1214
.library(name: "PackageCollectionValidator", targets: ["PackageCollectionValidator"]),
1315
.executable(name: "package-collection-validate", targets: ["PackageCollectionValidatorExecutable"]),
1416
.library(name: "PackageCollectionDiff", targets: ["PackageCollectionDiff"]),
@@ -17,37 +19,44 @@ let package = Package(
1719
dependencies: [
1820
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "0.3.0")),
1921
// FIXME: need semver
20-
.package(name: "SwiftPM", url: "https://github.com/apple/swift-package-manager.git", .branch("main")),
22+
.package(name: "SwiftPM", url: "https://github.com/yim-lee/swift-package-manager.git", .branch("wire-signing-all")),
2123
],
2224
targets: [
2325
.target(name: "Utilities", dependencies: [
24-
.product(name: "SwiftPMDataModel", package: "SwiftPM"),
26+
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
2527
]),
2628

2729
.target(name: "PackageCollectionGenerator", dependencies: [
2830
"Utilities",
29-
.product(name: "SwiftPMDataModel", package: "SwiftPM"),
31+
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
3032
.product(name: "ArgumentParser", package: "swift-argument-parser"),
3133
]),
3234
.target(name: "PackageCollectionGeneratorExecutable", dependencies: ["PackageCollectionGenerator"]),
3335

36+
.target(name: "PackageCollectionSigner", dependencies: [
37+
"Utilities",
38+
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
39+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
40+
]),
41+
.target(name: "PackageCollectionSignerExecutable", dependencies: ["PackageCollectionSigner"]),
42+
3443
.target(name: "PackageCollectionValidator", dependencies: [
3544
"Utilities",
36-
.product(name: "SwiftPMDataModel", package: "SwiftPM"),
45+
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
3746
.product(name: "ArgumentParser", package: "swift-argument-parser"),
3847
]),
3948
.target(name: "PackageCollectionValidatorExecutable", dependencies: ["PackageCollectionValidator"]),
4049

4150
.target(name: "PackageCollectionDiff", dependencies: [
4251
"Utilities",
43-
.product(name: "SwiftPMDataModel", package: "SwiftPM"),
52+
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
4453
.product(name: "ArgumentParser", package: "swift-argument-parser"),
4554
]),
4655
.target(name: "PackageCollectionDiffExecutable", dependencies: ["PackageCollectionDiff"]),
4756

4857
.target(name: "TestUtilities", dependencies: [
4958
.product(name: "ArgumentParser", package: "swift-argument-parser"),
50-
.product(name: "SwiftPMDataModel", package: "SwiftPM"),
59+
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
5160
]),
5261

5362
.testTarget(name: "PackageCollectionGeneratorTests", dependencies: ["PackageCollectionGenerator"]),
@@ -56,6 +65,11 @@ let package = Package(
5665
"TestUtilities",
5766
]),
5867

68+
.testTarget(name: "PackageCollectionSignerExecutableTests", dependencies: [
69+
"PackageCollectionSignerExecutable",
70+
"TestUtilities",
71+
]),
72+
5973
.testTarget(name: "PackageCollectionValidatorExecutableTests", dependencies: [
6074
"PackageCollectionValidatorExecutable",
6175
"TestUtilities",

Sources/PackageCollectionDiff/PackageCollectionDiff.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import ArgumentParser
1616
import Foundation
17+
18+
import Basics
1719
import PackageCollectionsModel
1820
import TSCBasic
1921
import Utilities
@@ -41,8 +43,7 @@ public struct PackageCollectionDiff: ParsableCommand {
4143

4244
print("Comparing collections located at \(self.collectionOnePath) and \(self.collectionTwoPath)", inColor: .cyan, verbose: self.verbose)
4345

44-
let jsonDecoder = JSONDecoder()
45-
jsonDecoder.dateDecodingStrategy = .iso8601
46+
let jsonDecoder = JSONDecoder.makeWithDefaults()
4647

4748
let collectionOne = try self.parsePackageCollection(at: self.collectionOnePath, using: jsonDecoder)
4849
let collectionTwo = try self.parsePackageCollection(at: self.collectionTwoPath, using: jsonDecoder)

Sources/PackageCollectionGenerator/Models/PackageCollectionGeneratorInput.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Foundation
16+
1617
import PackageCollectionsModel
1718

1819
/// Input for the `package-collection-generate` command

Sources/PackageCollectionGenerator/Models/PackageManifest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Foundation
16+
1617
import PackageModel
1718

1819
// Note: Not using SwiftPM's `PackageModel.Manifest` to avoid issues with

Sources/PackageCollectionGenerator/PackageCollectionGenerate.swift

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import ArgumentParser
1616
import Foundation
17+
18+
import Basics
1719
import PackageCollectionsModel
1820
import PackageModel
1921
import TSCBasic
@@ -58,7 +60,7 @@ public struct PackageCollectionGenerate: ParsableCommand {
5860
print("Using input file located at \(self.inputPath)", inColor: .cyan, verbose: self.verbose)
5961

6062
// Get the list of packages to process
61-
let jsonDecoder = JSONDecoder()
63+
let jsonDecoder = JSONDecoder.makeWithDefaults()
6264
let input = try jsonDecoder.decode(PackageCollectionGeneratorInput.self, from: Data(contentsOf: URL(fileURLWithPath: self.inputPath)))
6365
print("\(input)", verbose: self.verbose)
6466

@@ -85,16 +87,6 @@ public struct PackageCollectionGenerate: ParsableCommand {
8587
generatedBy: input.author
8688
)
8789

88-
let jsonEncoder = JSONEncoder()
89-
jsonEncoder.dateEncodingStrategy = .iso8601
90-
if #available(macOS 10.15, *) {
91-
#if os(macOS)
92-
jsonEncoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
93-
#else
94-
jsonEncoder.outputFormatting = [.sortedKeys]
95-
#endif
96-
}
97-
9890
// Make sure the output directory exists
9991
let outputAbsolutePath: AbsolutePath
10092
do {
@@ -106,6 +98,7 @@ public struct PackageCollectionGenerate: ParsableCommand {
10698
try localFileSystem.createDirectory(outputDirectory, recursive: true)
10799

108100
// Write the package collection
101+
let jsonEncoder = JSONEncoder.makeWithDefaults(prettified: false)
109102
let jsonData = try jsonEncoder.encode(packageCollection)
110103
try jsonData.write(to: URL(fileURLWithPath: outputAbsolutePath.pathString))
111104
print("Package collection saved to \(outputAbsolutePath)", inColor: .cyan, verbose: self.verbose)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Package Collection Generator open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift Package Collection Generator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Package Collection Generator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import PackageCollectionsSigning
16+
17+
import ArgumentParser
18+
import Dispatch
19+
import Foundation
20+
21+
import Basics
22+
import PackageCollectionsModel
23+
import PackageCollectionsSigning
24+
25+
import TSCBasic
26+
import Utilities
27+
28+
public struct PackageCollectionSign: ParsableCommand {
29+
public static let configuration = CommandConfiguration(
30+
abstract: "Sign a package collection."
31+
)
32+
33+
@Argument(help: "The path to the package collection file to be signed")
34+
var inputPath: String
35+
36+
@Argument(help: "The path to write the signed package collection to")
37+
var outputPath: String
38+
39+
@Argument(help: "The path to certificate's private key (PEM encoded)")
40+
var privateKeyPath: String
41+
42+
@Argument(help: "Paths to all certificates (DER encoded) in the chain. The certificate used for signing must be first and the root certificate last.")
43+
var certChainPaths: [String]
44+
45+
@Flag(name: .long, help: "Show extra logging for debugging purposes")
46+
var verbose: Bool = false
47+
48+
typealias Model = PackageCollectionModel.V1
49+
50+
public init() {}
51+
52+
public func run() throws {
53+
try self._run(signer: nil)
54+
}
55+
56+
internal func _run(signer: PackageCollectionSigner?) throws {
57+
guard !self.certChainPaths.isEmpty else {
58+
printError("Certificate chain cannot be empty")
59+
throw PackageCollectionSigningError.emptyCertChain
60+
}
61+
62+
Process.verbose = self.verbose
63+
64+
print("Signing package collection located at \(self.inputPath)", inColor: .cyan, verbose: self.verbose)
65+
66+
let jsonDecoder = JSONDecoder.makeWithDefaults()
67+
let collection = try jsonDecoder.decode(Model.Collection.self, from: Data(contentsOf: URL(fileURLWithPath: self.inputPath)))
68+
69+
let privateKeyURL = URL(fileURLWithPath: self.privateKeyPath)
70+
let certChainURLs = self.certChainPaths.map(URL.init(fileURLWithPath:))
71+
72+
try withTemporaryDirectory(removeTreeOnDeinit: true) { tmpDir in
73+
// The last item in the array is the root certificate and we want to trust it, so here we
74+
// create a temp directory, copy the root certificate to it, and make it the trustedRootCertsDir.
75+
let rootCertPath = AbsolutePath(certChainPaths.last!) // !-safe since certChain cannot be empty at this point
76+
let rootCertFilename = rootCertPath.components.last!
77+
try localFileSystem.copy(from: rootCertPath, to: tmpDir.appending(component: rootCertFilename))
78+
79+
// Sign the collection
80+
let signer = signer ?? PackageCollectionSigning(trustedRootCertsDir: tmpDir.asURL, callbackQueue: DispatchQueue.global(), diagnosticsEngine: DiagnosticsEngine())
81+
let signedCollection = try tsc_await { callback in
82+
signer.sign(collection: collection, certChainPaths: certChainURLs, certPrivateKeyPath: privateKeyURL, certPolicyKey: .default, callback: callback)
83+
}
84+
85+
// Make sure the output directory exists
86+
let outputAbsolutePath: AbsolutePath
87+
do {
88+
outputAbsolutePath = try AbsolutePath(validating: self.outputPath)
89+
} catch {
90+
outputAbsolutePath = AbsolutePath(self.outputPath, relativeTo: AbsolutePath(FileManager.default.currentDirectoryPath))
91+
}
92+
let outputDirectory = outputAbsolutePath.parentDirectory
93+
try localFileSystem.createDirectory(outputDirectory, recursive: true)
94+
95+
// Write the signed collection
96+
let jsonEncoder = JSONEncoder.makeWithDefaults(prettified: false)
97+
let jsonData = try jsonEncoder.encode(signedCollection)
98+
try jsonData.write(to: URL(fileURLWithPath: outputAbsolutePath.pathString))
99+
print("Signed package collection saved to \(outputAbsolutePath)", inColor: .cyan, verbose: self.verbose)
100+
}
101+
}
102+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Package Collection Signer
2+
3+
This tool is used for signing a package collection.
4+
5+
```
6+
> swift run package-collection-sign --help
7+
OVERVIEW: Sign a package collection.
8+
9+
USAGE: package-collection-sign <input-path> <output-path> <private-key-path> [<cert-chain-paths> ...] [--verbose]
10+
11+
ARGUMENTS:
12+
<input-path> The path to the package collection file to be signed
13+
<output-path> The path to write the signed package collection to
14+
<private-key-path> The path to certificate's private key (PEM encoded)
15+
<cert-chain-paths> Paths to all certificates (DER encoded) in the chain. The certificate used for signing must be first and the root
16+
certificate last.
17+
18+
OPTIONS:
19+
--verbose Show extra logging for debugging purposes
20+
-h, --help Show help information.
21+
```
22+
23+
### Sample Usage
24+
25+
```
26+
> swift run package-collection-sign \
27+
my-collection.json \
28+
my-signed-collection.json \
29+
priivate-key.pem \
30+
certificate.cer intermediate_ca.cer root_ca.cer
31+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Package Collection Generator open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift Package Collection Generator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Package Collection Generator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import PackageCollectionSigner
16+
17+
PackageCollectionSign.main()

Sources/PackageCollectionValidator/PackageCollectionValidate.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import ArgumentParser
1616
import Foundation
17+
18+
import Basics
1719
import enum PackageCollections.ValidationError
1820
import struct PackageCollections.ValidationMessage
1921
import enum PackageCollectionsModel.PackageCollectionModel
@@ -45,8 +47,7 @@ public struct PackageCollectionValidate: ParsableCommand {
4547

4648
let validator = Model.Validator()
4749

48-
let jsonDecoder = JSONDecoder()
49-
jsonDecoder.dateDecodingStrategy = .iso8601
50+
let jsonDecoder = JSONDecoder.makeWithDefaults()
5051

5152
let collection: Model.Collection
5253
do {

Tests/PackageCollectionDiffExecutableTests/PackageCollectionDiffTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import XCTest
16+
1517
@testable import PackageCollectionDiff
1618
@testable import TestUtilities
1719
import TSCBasic
18-
import XCTest
1920

2021
final class PackageCollectionDiffTests: XCTestCase {
2122
func test_help() throws {

0 commit comments

Comments
 (0)