Skip to content

Latest commit

 

History

History
374 lines (280 loc) · 18.8 KB

PackageCollections.md

File metadata and controls

374 lines (280 loc) · 18.8 KB

Package Collections

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.

Table of Contents

Using 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 collection
  • describe: Get metadata for a collection or a package included in an imported collection
  • list: List configured collections
  • refresh: Refresh configured collections
  • remove: Remove a configured collection
  • search: Search for packages by keywords or module names within imported collections

add subcommand

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.

Signed 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.

Trusted root certificates

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.

Unsigned package collections

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.

describe subcommand

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.

Metadata and packages of a collection

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
    ...
Signed package collections

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

Metadata of a package

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

Metadata of a package version

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

list subcommand

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.

refresh subcommand

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.

remove subcommand

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.

search subcommand

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.

String-based search

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.
...

Module-based search

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 file

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.


Publishing package collections

Package collections can be created and published by anyone. The swift-package-collection-generator project provides tooling intended for package collection publishers:

Creating package collections

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 collection signing (optional)

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.

Requirements on signing certificate

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.

Trusted root certificates

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.

Protecting package collections

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:

  1. Edit PackageCollectionSourceCertificatePolicy and add an entry to the defaultSourceCertPolicies 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>"]
    )
]
  1. 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.
  2. 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://).