Package collections, introduced by SE-0291, are curated lists of packages and associated metadata that make discovery of existing packages easier. They are authored as static JSON documents and can be published to the web or distributed to local file systems.
- Using package collections with the
package-collection
CLI - Package collection configuration
- Publishing package collections
With the swift package-collection
command-line interface, SwiftPM users can subscribe to package collections. Contents of imported package
collections are accessible to any clients of libSwiftPM.
swift package-collection
has the following subcommands:
add
: Add a new collectiondescribe
: Get metadata for a collection or a package included in an imported collectionlist
: List configured collectionsrefresh
: Refresh configured collectionsremove
: Remove a configured collectionsearch
: Search for packages by keywords or module names within imported collections
This subcommand adds a package collection hosted on the web (HTTPS required):
$ swift package-collection add https://www.example.com/packages.json
Added "Sample Package Collection" to your package collections.
Or found in the local file system:
$ swift package-collection add file:///absolute/path/to/packages.json
Added "Sample Package Collection" to your package collections.
The optional order
hint can be used to order collections and may potentially influence ranking in search results:
$ swift package-collection add https://www.example.com/packages.json [--order N]
Added "Sample Package Collection" to your package collections.
Package collection publishers may sign a collection to protect its contents from being tampered with. If a collection is signed, SwiftPM will check that the signature is valid before importing it and return an error if any of these fails:
- The file's contents, signature excluded, must match what was used to generate the signature. In other words, this checks to see if the collection has been altered since it was signed.
- The signing certificate must meet all the requirements.
$ swift package-collection add https://www.example.com/bad-packages.json
The collection's signature is invalid. If you would like to continue please rerun command with '--skip-signature-check'.
Users may continue adding the collection despite the error or preemptively skip the signature check on a package collection by passing the --skip-signature-check
flag:
$ swift package-collection add https://www.example.com/packages.json --skip-signature-check
For package collections hosted on the web, publishers may ask SwiftPM to enforce the signature requirement. If a package collection is expected to be signed but it isn't, user will see the following error message:
$ swift package-collection add https://www.example.com/bad-packages.json
The collection is missing required signature, which means it might have been compromised.
Users should NOT add the package collection in this case.
Since generating a collection signature requires a certificate, part of the signature check involves validating the certificate and its chain and making sure that the root certificate is trusted.
On Apple platforms, all root certificates that come preinstalled with the OS are automatically trusted. Users may include additional certificates to trust by placing
them in the ~/.swiftpm/config/trust-root-certs
directory.
On non-Apple platforms, there are no trusted root certificates by default other than those shipped with the certificate-pinning configuration. Only those
found in ~/.swiftpm/config/trust-root-certs
are trusted. This means that the signature check will always fail unless the trust-root-certs
directory is set up:
$ swift package-collection add https://www.example.com/packages.json
The collection's signature cannot be verified due to missing configuration.
Users can explicitly specify they trust a publisher and any collections they publish, by obtaining that publisher's root certificate and saving it to ~/.swiftpm/config/trust-root-certs
. The
root certificates must be DER-encoded. Since SwiftPM trusts all certificate chains under a root, depending on what roots are installed, some publishers may already be trusted implicitly and
users don't need to explicitly specify each one.
Users will get an error when trying to add an unsigned package collection:
$ swift package-collection add https://www.example.com/packages.json
The collection is not signed. If you would still like to add it please rerun 'add' with '--trust-unsigned'.
To continue user must confirm their trust by passing the --trust-unsigned
flag:
$ swift package-collection add https://www.example.com/packages.json --trust-unsigned
The --skip-signature-check
flag has no effects on unsigned collections.
This subcommand shows metadata for a collection or a package included in an imported collection. The result can optionally be returned as JSON using --json
for
integration into other tools.
describe
can be used for both collections that have been previously added to the list of the user's configured collections, as well as to preview any other collections.
$ swift package-collection describe [--json] https://www.example.com/packages.json
Name: Sample Package Collection
Source: https://www.example.com/packages.json
Description: ...
Keywords: best, packages
Created At: 2020-05-30 12:33
Packages:
https://github.com/jpsim/yams
...
If a collection is signed, SwiftPM will check that the signature is valid before showing a preview.
$ swift package-collection describe https://www.example.com/bad-packages.json
The collection's signature is invalid. If you would like to continue please rerun command with '--skip-signature-check'.
Users may continue previewing the collection despite the error or preemptively skip the signature check on a package collection by passing the --skip-signature-check
flag:
$ swift package-collection describe https://www.example.com/packages.json --skip-signature-check
describe
can also show the metadata of a package included in an imported collection:
$ swift package-collection describe [--json] https://github.com/jpsim/yams
Description: A sweet and swifty YAML parser built on LibYAML.
Available Versions: 4.0.0, 3.0.0, ...
Stars: 14
Readme: https://github.com/jpsim/Yams/blob/master/README.md
Authors: @norio-nomura, @jpsim
--------------------------------------------------------------
Latest Version: 4.0.0
Package Name: Yams
Modules: Yams, CYaml
Supported Platforms: iOS, macOS, Linux, tvOS, watchOS
Supported Swift Versions: 5.3, 5.2, 5.1, 5.0
License: MIT
User may view additional metadata for a package version by passing --version
:
$ swift package-collection describe [--json] --version 4.0.0 https://github.com/jpsim/yams
Package Name: Yams
Version: 4.0.0
Modules: Yams, CYaml
Supported Platforms: iOS, macOS, Linux, tvOS, watchOS
Supported Swift Versions: 5.3, 5.2, 5.1, 5.0
License: MIT
This subcommand lists all collections that are configured by the user:
$ swift package-collection list [--json]
Sample Package Collection - https://example.com/packages.json
...
The result can optionally be returned as JSON using --json
for integration into other tools.
This subcommand refreshes any cached data manually:
$ swift package-collection refresh
Refreshed 5 configured package collections.
SwiftPM will also automatically refresh data under various conditions, but some queries such as search will rely on locally cached data.
This subcommand removes a collection from the user's list of configured collections:
$ swift package-collection remove https://www.example.com/packages.json
Removed "Sample Package Collection" from your package collections.
This subcommand searches for packages by keywords or module names within imported collections. The result can optionally be returned as JSON using --json
for
integration into other tools.
The search command does a string-based search when using the --keywords
option and returns the list of packages that matches the query:
$ swift package-collection search [--json] --keywords yaml
https://github.com/jpsim/yams: A sweet and swifty YAML parser built on LibYAML.
...
The search command does a search for a specific module name when using the --module
option:
$ swift package-collection search [--json] --module yams
Package Name: Yams
Latest Version: 4.0.0
Description: A sweet and swifty YAML parser built on LibYAML.
--------------------------------------------------------------
...
Configuration that pertains to package collections are stored in the file ~/.swiftpm/config/collections.json
. It keeps track of user's list of configured collections
and preferences such as those set by the --trust-unsigned
and --skip-signature-check
flags in the package-collection add
command.
This file is managed through SwiftPM commands and users are not expected to edit it by hand.
Package collections can be created and published by anyone. The swift-package-collection-generator project provides tooling intended for package collection publishers:
package-collection-generate
: Generate a package collection given a list of package URLspackage-collection-sign
: Sign a package collectionpackage-collection-validate
: Perform basic validations on a package collectionpackage-collection-diff
: Compare two package collections to see if their contents are different
All package collections must adhere to the collection data format for SwiftPM to be able to consume them. The recommended way
to create package collections is to use package-collection-generate
. For custom implementations, the data models are available through the PackageCollectionsModel
module.
Package collections can be signed to establish authenticity and protect their integrity. Doing this is optional. Users will be prompted for confirmation before they can add an unsigned collection.
package-collection-sign
helps publishers sign their package
collections. To generate a signature one must provide:
- The package collection file to be signed
- A code signing certificate (DER-encoded)
- The certificate's private key (PEM-encoded)
- The certificate's chain in its entirety
A signed package collection has an extra signature
object:
{
...,
"signature": {
"signature": "<SIGNATURE>",
"certificate": {
"subject": {
"commonName": "Jane Doe",
...
},
"issuer": {
"commonName": "Sample CA",
...
}
}
}
}
- The signature string (represented by
"<SIGNATURE>"
) is used to verify the contents of the collection file haven't been tampered with since it was signed when SwiftPM user adds the collection to their configured list of collections. It includes the certificate's public key and chain. certificate
contains details extracted from the signing certificate.subject.commonName
should be consistent with the name of the publisher so that it's recognizable by users. The root of the certificate must be installed and trusted on users' machines.
Certificates used for signing package collections must meet the following requirements, which are checked and enforced during signature generation (publishers) and verification (SwiftPM users):
- The timestamp at which signing/verification is done must fall within the signing certificate's validity period.
- The certificate's "Extended Key Usage" extension must include "Code Signing".
- The certificate must use either 256-bit EC (recommended for enhanced security) or 2048-bit RSA key.
- The certificate must not be revoked. The certificate authority must support OCSP, which means the certificate must have the "Certificate Authority Information Access" extension that includes OCSP as a method, specifying the responder's URL.
- The certificate chain is valid and root certificate must be trusted.
Non-expired, non-revoked Swift Package Collection certificates from developer.apple.com satisfy all of the criteria above.
With the package-collection-sign
tool, the root certificate provided as input for signing a collection is automatically trusted. When SwiftPM user tries to add the collection, however,
the root certificate must either be preinstalled with the OS (Apple platforms only) or found in the ~/.swiftpm/config/trust-root-certs
directory (all platforms) or shipped with
the certificate-pinning configuration, otherwise the signature check will fail. Collection publishers should make the DER-encoded
root certificate(s) that they use downloadable so that users can adjust their setup if needed.
Signing can provide some degree of protection on package collections and reduce the risks of their contents being modified by malicious actors, but it doesn't prevent the following attack vectors:
- Signature stripping: This involves attackers removing signature from a signed collection, causing it to be downloaded as an unsigned collection and bypassing signature check. In this case, publishers should make it known that the collection is signed, and SwiftPM users should abort the
add
operation when the "unsigned" warning appears on a supposedly signed collection. - Signature replacement: Attackers may modify a collection then re-sign it using a different certificate, either pretend to be the same entity or as some other entity, and SwiftPM will accept it as long as the signature is valid.
To defend against these attacks, SwiftPM has certificate-pinning configuration that allows collection publishers to:
- Require signature check on their collections — this defends against "signature stripping".
- Restrict what certificate can be used for signing — this defends against "signature replacement".
The process for collection publishers to define their certificate-pinning configuration is as follows:
- Edit
PackageCollectionSourceCertificatePolicy
and add an entry to thedefaultSourceCertPolicies
dictionary:
private static let defaultSourceCertPolicies: [String: CertificatePolicyConfig] = [
// The key should be the "host" component of the package collection URL.
// This would require all package collections hosted on this domain to be signed.
"www.example.com": CertificatePolicyConfig(
// The signing certificate must have this subject user ID
certPolicyKey: CertificatePolicyKey.default(subjectUserID: "exampleUserID"),
/*
To compute base64-encoded string of a certificate:
let certificateURL = URL(fileURLWithPath: <path to DER-encoded root certificate file>)
let certificateData = try Data(contentsOf: certificateURL)
let base64EncoodedCertificate = certificateData.base64EncodedString()
*/
base64EncodedRootCerts: ["<base64-encoded root certificate>"]
)
]
- Open a pull request for review. The requestor must be able to provide proof of their identity and ownership on the domain:
- The requestor must provide the actual certificate files (DER-encoded). The SwiftPM team will verify that the certificate chain is valid and the values provided in the PR are correct.
- The requestor must add a TXT record referencing the pull request. The SwiftPM team will run
dig -t txt <DOMAIN>
to verify. This would act as proof of domain ownership.
- After the changes are accepted, they will take effect in the next SwiftPM release.
Since certificate-pinning configuration is associated with web domains, it can only be applied to signed collections hosted on the web (i.e., URL begins with https://
) and does
not cover those found on local file system (i.e., URL begins with file://
).