How to add a third-party repository safely¶
A vendor publishes its software in its own package repository and asks you to add that repository and its signing key to your system. This guide adds the repository so that its signature is always checked, its key is trusted for as little as the package manager allows, and it can't replace the distribution's own packages without you noticing.
A repository can run code as root
Packages run their installation scripts as root, so whoever controls a repository you add, or its signing key, controls every machine that installs or upgrades from it.
Add only repositories you trust as much as the distribution itself, and prefer the distribution's own packages where they're good enough.
Never turn signature checking off to make a repository work (trusted=yes on Debian and Ubuntu, gpgcheck=0 on the RHEL family, SigLevel = Never on Arch): an unsigned repository lets anyone between you and the server substitute packages.
Before you begin¶
- You need a shell with
sudoon the machine. - From the vendor's documentation you need three things: the repository's URL for your distribution and release, the URL of its signing key, and the key's full fingerprint. Get the fingerprint from the vendor's HTTPS documentation pages, not from the same place as the key, so that one compromised server can't supply both.
Replace these placeholders with the vendor's values:
| Placeholder | Meaning | Example |
|---|---|---|
<REPO_URL> |
The repository's base URL for your distribution | https://packages.example.com/debian |
<KEY_URL> |
The URL of the vendor's public signing key | https://packages.example.com/key.asc |
<FINGERPRINT> |
The key's full fingerprint, as 40 hexadecimal digits without spaces | 0123456789ABCDEF0123456789ABCDEF01234567 |
The steps use example as the repository's local name and example-hello as the package you want from it; use the vendor's names instead.
Download and check the signing key¶
-
Download the key to your home directory:
-
Make sure
gpgis available:Debian 13's minimal and cloud images don't include
gpg, becauseaptverifies signatures withsqvinstead. Install it ifcommand -v gpgprints nothing:gpgis installed by default.gpgis installed by default, becausepacmandepends on it.gpgis installed by default, because Portage uses it to verify the Gentoo repository. -
Show the key's fingerprint without importing it anywhere:
-
Compare the 40-digit fingerprint with
<FINGERPRINT>from the vendor's documentation. If it differs, or the file contains more keys than the vendor documents, stop: you don't have the vendor's key.
Add the repository¶
-
Install the key where only this repository will use it.
/etc/apt/keyrings/is the directory for keys that are referenced explicitly;aptdoesn't trust keys there for any repository by default:Don't put the key in
/etc/apt/trusted.gpg.d/: keys there are trusted for every repository.apt-keyno longer exists on Debian 13 and Ubuntu 26.04; older instructions that used it put keys in that directory. -
Describe the repository in a deb822
.sourcesfile, withSigned-Bynaming the key. Use the suite and components the vendor documents (herestableandmain):Types: deb URIs: <REPO_URL> Suites: stable Components: main Signed-By: /etc/apt/keyrings/example.ascSigned-Bymakesaptaccept this repository only if itsInReleasefile is signed by that key. Because the key is in/etc/apt/keyrings/rather thantrusted.gpg.d, no other repository accepts it. -
Give the repository a low priority, so that
aptinstalls only the packages you name from it and never prefers it for packages the distribution also ships.<REPO_HOST>is the host name in<REPO_URL>, for examplepackages.example.com:Package: * Pin: origin <REPO_HOST> Pin-Priority: 100 Package: example-hello Pin: origin <REPO_HOST> Pin-Priority: 500At priority 100, packages from the repository are installed only when nothing else provides them, and installed ones are still upgraded from it; the second entry restores normal priority for the package you want. List each package you install from the repository in the second entry, separated by spaces.
-
Refresh the package lists and install the package:
If
apt-get updatereports that the signature couldn't be verified (Missing key …, which is needed to verify signatureon Debian 13,NO_PUBKEY …on Ubuntu 26.04), the key or theSigned-Bypath is wrong. Fix it; don't work around it.
-
Install the key in the directory where the distribution keeps its own:
-
Describe the repository in a
.repofile:[example] name=Example Vendor baseurl=<REPO_URL> enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example includepkgs=example-hellogpgcheck=1checks the signature on every package from the repository;repo_gpgcheck=1also checks the signature on its metadata, if the vendor signs it (remove the line ifdnfreports thatrepomd.xml.ascis missing).includepkgslimits whatdnfwill take from this repository to the packages you list (separated by commas, with*wildcards allowed), so that the repository can't replace a distribution package with its own build.
-
Import the key and install the package.
dnfasks to import the key on first use; importing it explicitly lets you check it once more:
RPM has a single keyring for the whole system (rpm -q gpg-pubkey lists it): once imported, the vendor's key is accepted for packages from any repository, not only this one.
includepkgs stops the vendor's repository replacing distribution packages, but a package signed with the vendor's key would still be accepted from another repository, for example from a compromised mirror.
The key stays trusted for the whole system until you remove it, as described in Undo.
-
Add the key to the keyring of
pacmanand sign it locally, which is how you tellpacmanthat you trust it:pacman-key --lsign-keyshows the key it signs; check that it's the fingerprint you verified. -
Add a section for the repository at the end of
/etc/pacman.conf, after[core]and[extra]:sudo tee -a /etc/pacman.conf > /dev/null <<'EOF' [example] SigLevel = Required Server = <REPO_URL> EOFWhen two repositories offer a package with the same name,
pacmantakes it from the one listed first, whatever the versions. Listing the vendor's repository last means it can only add packages, not replace the distribution's.SigLevel = Requiredrequires valid signatures on both its packages and its database. -
Synchronise the databases, upgrade the system and install the package in one transaction (Arch doesn't support refreshing the databases without upgrading):
pacman has a single keyring: a key you've locally signed is trusted for packages from any repository, not only this one.
The repository order stops the vendor's repository replacing distribution packages, so check that the section stays at the end when you edit pacman.conf; but a package signed with the vendor's key would still be accepted from another repository, for example from a compromised mirror.
The key stays trusted for the whole system until you remove it, as described in Undo.
Third-party software for Gentoo usually comes as an ebuild repository (an "overlay") synchronised with Git. Portage can refuse to use it unless its newest commit is signed by a key you name.
-
Install Git if it isn't already:
-
Install the key in a directory of your choice, for example
/etc/portage/keys/: -
Describe the repository in
/etc/portage/repos.conf/:[example] location = /var/db/repos/example sync-type = git sync-uri = <REPO_URL> sync-git-verify-commit-signature = yes sync-openpgp-key-path = /etc/portage/keys/example.asc sync-openpgp-key-refresh = no auto-sync = yesWith
sync-git-verify-commit-signature, every sync checks the signature on the newest commit against that key only, and fails if it's missing or made by another key. The key isn't used for anything else.By default, Portage also refreshes the key from the vendor's web key directory (WKD) or a key server before each sync, so that it notices if the key is revoked, and the sync fails if the refresh fails.
sync-openpgp-key-refresh = noturns that off for a vendor that publishes its key only as a file, as here; Portage then warns on each sync, and replacing the file when the vendor announces a new key is up to you. If the vendor's key is available through WKD or a key server, leave the line out. -
Mask everything in the repository except the packages you want, so that it can't replace packages from the Gentoo repository with its own ebuilds:
sudo install -d /etc/portage/package.mask /etc/portage/package.unmask echo '*/*::example' | sudo tee /etc/portage/package.mask/example echo 'app-misc/example-hello::example' | sudo tee /etc/portage/package.unmask/exampleinstall -dcreatespackage.maskandpackage.unmaskas directories if they don't exist yet. If either already exists as a file, add the line to that file instead. -
Synchronise the repository and install the package:
Binary package hosts in /etc/portage/binrepos.conf are checked against the keyring in /etc/portage/gnupg/, which is shared by every binary host.
Add a third-party binary host only if you trust it with every package name.
Verify¶
-
Check that the package came from the vendor's repository and runs:
-
Check that the repository's scope is what you intended: no distribution package should now come from it.
The repository as a whole has priority 100:
$ apt-cache policy | grep -B2 'origin <REPO_HOST>$' 100 https://packages.example.com/debian stable/main amd64 Packages release o=Example,a=stable,n=stable,l=Example,c=main,b=amd64 origin packages.example.comIn
apt-cache policy example-helloabove, the repository line shows this priority, and the***line shows the priority 500 you gave the package itself.Only the packages in
includepkgsare visible in the repository:Every other package in the repository is masked by
*/*::example.emerge --pretendfor any of them reportsmasked by: package.mask.
Troubleshooting¶
apt-get updatereportsMissing key …, which is needed to verify signature(Debian 13) orNO_PUBKEY …(Ubuntu 26.04)- The key in
Signed-Byisn't the one that signed the repository'sInReleasefile, or the path is wrong. For a repository it had already verified,aptadds that it keeps using the previous index files, so nothing changes until the signature is right. Check the path, and check the fingerprint against the vendor's documentation again; if it still matches, ask the vendor whether it has changed its key. dnfreportsGPG check FAILEDorrepomd.xml GPG signature verification error- A package or the metadata isn't signed by the key in
gpgkey. If the vendor doesn't sign its metadata, removerepo_gpgcheck=1; never setgpgcheck=0. pacmanreportssignature from "…" is unknown trustorinvalid or corrupted package (PGP signature)- The key hasn't been locally signed, or a different key signed the package. Run
sudo pacman-key --lsign-key <FINGERPRINT>with the fingerprint you checked; if the error stays, the package wasn't signed by that key. emaint syncfails withNo valid signature foundon Gentoo- The newest commit isn't signed by the key in
sync-openpgp-key-path. The local copy of the repository stays at the last commit that was verified. Check that the key file is the vendor's, and ask the vendor whether it signs every commit.
Undo¶
To remove the repository, uninstall its packages first, then remove the files you added:
sudo dnf remove -y example-hello
sudo rm /etc/yum.repos.d/example.repo /etc/pki/rpm-gpg/RPM-GPG-KEY-example
Then remove the key from the RPM keyring.
RPM stores each key as a gpg-pubkey pseudo-package named after the last eight digits of its fingerprint, in lower case:
$ rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
gpg-pubkey-c2a1e572-668fe8ef AlmaLinux OS 10 <packager@almalinux.org> public key
gpg-pubkey-01234567-690e1a2b Example Vendor <packages@example.com> public key
$ sudo rpm -e gpg-pubkey-<KEY_ID>
<KEY_ID> is the eight-digit value from the vendor's line, here 01234567.
Remove the package, delete the [example] section from /etc/pacman.conf (this sed command deletes from the [example] line to the next Server line), and remove the key:
Related¶
- Reference: Repository configuration
- Reference: Package manager commands
- Explanation: How package management works
- Explanation: Binary and source distributions
- How-to: How to find which package provides a file
- How-to: How to hold a package at its current version
- How-to: How to roll back a package update