aboutsummaryrefslogtreecommitdiff
path: root/bpkg/repository-signing.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/repository-signing.cxx')
-rw-r--r--bpkg/repository-signing.cxx205
1 files changed, 205 insertions, 0 deletions
diff --git a/bpkg/repository-signing.cxx b/bpkg/repository-signing.cxx
new file mode 100644
index 0000000..66366cb
--- /dev/null
+++ b/bpkg/repository-signing.cxx
@@ -0,0 +1,205 @@
+// -*- C++ -*-
+//
+// This file was generated by CLI, a command line interface
+// compiler for C++.
+//
+
+// Begin prologue.
+//
+#include <bpkg/types-parsers.hxx>
+//
+// End prologue.
+
+#include <bpkg/repository-signing.hxx>
+
+#include <map>
+
+namespace bpkg
+{
+ ::bpkg::cli::usage_para
+ print_bpkg_repository_signing_usage (::std::ostream& os, ::bpkg::cli::usage_para p)
+ {
+ CLI_POTENTIALLY_UNUSED (os);
+
+ if (p != ::bpkg::cli::usage_para::none)
+ os << ::std::endl;
+
+ os << "\033[1mSYNOPSIS\033[0m" << ::std::endl
+ << ::std::endl
+ << "\033[1mbpkg rep-create --key\033[0m ...\033[0m" << ::std::endl
+ << ::std::endl
+ << "\033[1mDESCRIPTION\033[0m" << ::std::endl
+ << ::std::endl
+ << "The purpose of signing a repository is to prevent tampering with packages" << ::std::endl
+ << "either during transmission or on the repository \033[4mhost machine\033[0m. Ideally, you" << ::std::endl
+ << "would generate and sign the repository manifests on a separate \033[4mbuild machine\033[0m" << ::std::endl
+ << "that is behind a firewall. This way, if (or, really, when) your host machine is" << ::std::endl
+ << "compromised, it will be difficult for an attacker to compromise the repository" << ::std::endl
+ << "packages without being noticed. Since the repository key is kept on the build" << ::std::endl
+ << "machine (or, better yet, on a \033[4mone-way\033[0m PIV/PKCS#11 device; see below) they will" << ::std::endl
+ << "not be able to re-sign the modified repository." << ::std::endl
+ << ::std::endl
+ << "\033[1mbpkg\033[0m uses X.509 public key cryptography for repository signing. Currently, only" << ::std::endl
+ << "the explicit \033[4mfirst use\033[0m certificate authentication is implemented. That is, for" << ::std::endl
+ << "an unknown (to this \033[1mbpkg\033[0m configuration) repository certificate its subject" << ::std::endl
+ << "information and fingerprint are presented to the user. If the user confirms the" << ::std::endl
+ << "authenticity of the certificate, then it is added to the configuration and any" << ::std::endl
+ << "repository that in the future presents this certificate is trusted without" << ::std::endl
+ << "further confirmations, provided its name matches the certificate's subject (see" << ::std::endl
+ << "below). In the future a certificate authority (CA)-based model may be added." << ::std::endl
+ << ::std::endl
+ << "The rest of this guide shows how to create a key/certificate pair for \033[1mpkg\033[0m" << ::std::endl
+ << "repository signing and use it to sign a repository. At the end it also briefly" << ::std::endl
+ << "explains how to store the private key on a PIV/PKCS#11 device using Yubikey 4" << ::std::endl
+ << "as an example." << ::std::endl
+ << ::std::endl
+ << "1. Generate Private Key" << ::std::endl
+ << ::std::endl
+ << " The first step is to generate the private key:" << ::std::endl
+ << ::std::endl
+ << " $ openssl genrsa -aes256 2048 >key.pem" << ::std::endl
+ << ::std::endl
+ << " If you would like to generate a key without password protection (not a good" << ::std::endl
+ << " idea except for testing), leave the \033[1m-aes256\033[0m option out. You may also need" << ::std::endl
+ << " to add \033[1m-nodes\033[0m depending on your \033[1mopenssl(1)\033[0m configuration." << ::std::endl
+ << ::std::endl
+ << "2. Generate Certificate" << ::std::endl
+ << ::std::endl
+ << " Next create the certificate configuration file by saving the following into" << ::std::endl
+ << " \033[1mcert.conf\033[0m. You may want to keep it around in case you need to renew an" << ::std::endl
+ << " expired certificate, etc." << ::std::endl
+ << ::std::endl
+ << " name = example.com" << ::std::endl
+ << " org = Example, Inc" << ::std::endl
+ << " email = admin@example.com" << ::std::endl
+ << ::std::endl
+ << " [req]" << ::std::endl
+ << " distinguished_name = req_distinguished_name" << ::std::endl
+ << " x509_extensions = v3_req" << ::std::endl
+ << " prompt = no" << ::std::endl
+ << " utf8 = yes" << ::std::endl
+ << ::std::endl
+ << " [req_distinguished_name]" << ::std::endl
+ << " O = $org" << ::std::endl
+ << " CN = name:$name" << ::std::endl
+ << ::std::endl
+ << " [v3_req]" << ::std::endl
+ << " keyUsage = critical,digitalSignature" << ::std::endl
+ << " extendedKeyUsage = critical,codeSigning" << ::std::endl
+ << " subjectAltName = email:$email" << ::std::endl
+ << ::std::endl
+ << " Adjust the first three lines to match your details. If the repository is" << ::std::endl
+ << " hosted by an organization, use the organization's name for \033[1morg\033[0m. If you host" << ::std::endl
+ << " it as an individual, put your full, real name there. Using any kind of" << ::std::endl
+ << " aliases or nicknames is a bad idea (except, again, for testing). Remember," << ::std::endl
+ << " users of your repository will be presented with this information and if" << ::std::endl
+ << " they see it was signed by someone named SmellySnook, they will unlikely" << ::std::endl
+ << " trust it. Also use a working email address in case users need to contact" << ::std::endl
+ << " you about issues with your certificate. Note that the \033[1mname:\033[0m prefix in the" << ::std::endl
+ << " \033[1mCN\033[0m value is not a typo." << ::std::endl
+ << ::std::endl
+ << " The \033[1mname\033[0m field is a canonical repository name prefix with the \033[1mpkg:\033[0m type" << ::std::endl
+ << " part stripped. Any repository with a canonical name that starts with this" << ::std::endl
+ << " prefix can be authenticated by this certificate (see the repository" << ::std::endl
+ << " manifest documentation for more information on canonical names). For" << ::std::endl
+ << " example, name \033[1mexample.com\033[0m will match any repository hosted on" << ::std::endl
+ << " \033[1m{,www.,pkg.,bpkg.}example.com\033[0m. While name \033[1mexample.com/math\033[0m will match" << ::std::endl
+ << " \033[1m{...}example.com/pkg/1/math\033[0m but not \033[1m{...}example.com/pkg/1/misc\033[0m." << ::std::endl
+ << ::std::endl
+ << " A certificate name can also contain a subdomain wildcard. A wildcard name" << ::std::endl
+ << " in the \033[1m*.example.com\033[0m form matches any single-level subdomain, for example" << ::std::endl
+ << " \033[1mfoo.example.com\033[0m but not \033[1mfoo.bar.example.com\033[0m while a wildcard name in the" << ::std::endl
+ << " \033[1m**.example.com\033[0m form matches any subdomain, including multi-level. The above" << ::std::endl
+ << " two forms do not match the domain itself (\033[1mexample.com\033[0m in the above" << ::std::endl
+ << " example). If this is desired, the \033[1m*example.com\033[0m and \033[1m**example.com\033[0m forms" << ::std::endl
+ << " should be used instead. Note that these forms still only match subdomains." << ::std::endl
+ << " In other words, they won't match \033[1mfooexample.com\033[0m. Wildcard names are less" << ::std::endl
+ << " secure and therefore are normally only used for testing and/or internal" << ::std::endl
+ << " repositories." << ::std::endl
+ << ::std::endl
+ << " Once the configuration file is ready, generate the certificate:" << ::std::endl
+ << ::std::endl
+ << " openssl req -x509 -new -sha256 -key key.pem \\" << ::std::endl
+ << " -config cert.conf -days 730 >cert.pem" << ::std::endl
+ << ::std::endl
+ << " To verify the certificate information, run:" << ::std::endl
+ << ::std::endl
+ << " openssl x509 -noout -nameopt RFC2253,sep_multiline \\" << ::std::endl
+ << " -subject -dates -email <cert.pem" << ::std::endl
+ << ::std::endl
+ << "3. Add Certificate to Repository" << ::std::endl
+ << ::std::endl
+ << " Add the \033[1mcertificate:\033[0m field for the base repository (\033[1mrole: base\033[0m) in the" << ::std::endl
+ << " \033[1mrepositories\033[0m manifest file(s):" << ::std::endl
+ << ::std::endl
+ << " certificate:" << ::std::endl
+ << " \\" << ::std::endl
+ << " <cert>" << ::std::endl
+ << " \\" << ::std::endl
+ << ::std::endl
+ << " Replace \033[4mcert\033[0m with the entire contents of \033[1mcert.pem\033[0m (including the BEGIN" << ::std::endl
+ << " CERTIFICATE\033[0m and END CERTIFICATE\033[0m lines). So you will have an entry like" << ::std::endl
+ << " this:" << ::std::endl
+ << ::std::endl
+ << " certificate:" << ::std::endl
+ << " \\" << ::std::endl
+ << " -----BEGIN CERTIFICATE-----" << ::std::endl
+ << " MIIDQjCCAiqgAwIBAgIJAIUgsIqSnesGMA0GCSqGSIb3DQEBCwUAMDkxFzAVBgNV" << ::std::endl
+ << " ." << ::std::endl
+ << " ." << ::std::endl
+ << " ." << ::std::endl
+ << " +NOVBamEvjn58ZcLfWh2oKee7ulIZg==" << ::std::endl
+ << " -----END CERTIFICATE-----" << ::std::endl
+ << " \\" << ::std::endl
+ << ::std::endl
+ << "4. Sign Repository" << ::std::endl
+ << ::std::endl
+ << " When generating the repository manifests with the \033[1mbpkg-rep-create(1)\033[0m" << ::std::endl
+ << " command, specify the path to \033[1mkey.pem\033[0m with the \033[1m--key\033[0m option:" << ::std::endl
+ << ::std::endl
+ << " bpkg rep-create --key /path/to/key.pem /path/to/repository" << ::std::endl
+ << ::std::endl
+ << " You will be prompted for a password to unlock the private key." << ::std::endl
+ << ::std::endl
+ << "5. Using PIV/PKCS#11 Device" << ::std::endl
+ << ::std::endl
+ << " This optional step shows how to load the private key into Yubikey 4 and" << ::std::endl
+ << " then use it instead of the private key itself for signing the repository." << ::std::endl
+ << " Note that you will need OpenSSL 1.0.2 or later for the signing part to" << ::std::endl
+ << " work." << ::std::endl
+ << ::std::endl
+ << " First change the Yubikey MKEY, PUK, and PIN if necessary. You should" << ::std::endl
+ << " definitely do this if it still has the factory defaults. Then import the" << ::std::endl
+ << " private key and the certificate into Yubikey (replace \033[4mmkey\033[0m with the" << ::std::endl
+ << " management key):" << ::std::endl
+ << ::std::endl
+ << " yubico-piv-tool --key=<mkey> -a import-key -s 9c <key.pem" << ::std::endl
+ << " yubico-piv-tool --key=<mkey> -a import-certificate -s 9c <cert.pem" << ::std::endl
+ << ::std::endl
+ << " After this you will normally save the certificate/private key onto backup" << ::std::endl
+ << " media, store it in a secure, offline location, and remove the key from the" << ::std::endl
+ << " build machine." << ::std::endl
+ << ::std::endl
+ << " To sign the repository with Yubikey specify the following options instead" << ::std::endl
+ << " of just \033[1m--key\033[0m as at step 4 (\"SIGN key\"\033[0m is the label for the slot 9c\033[0m private" << ::std::endl
+ << " key):" << ::std::endl
+ << ::std::endl
+ << " bpkg rep-create \\" << ::std::endl
+ << " --openssl-option pkeyutl:-engine --openssl-option pkeyutl:pkcs11 \\" << ::std::endl
+ << " --openssl-option pkeyutl:-keyform --openssl-option pkeyutl:engine \\" << ::std::endl
+ << " --key \"pkcs11:object=SIGN%20key\" /path/to/repository" << ::std::endl
+ << ::std::endl
+ << " Note that for \033[1mopenssl\033[0m versions prior to \033[1m3.0.0\033[0m \033[1mbpkg\033[0m uses the \033[1mrsautl\033[0m command" << ::std::endl
+ << " instead of \033[1mpkeyutl\033[0m for the data signing operation." << ::std::endl;
+
+ p = ::bpkg::cli::usage_para::text;
+
+ return p;
+ }
+}
+
+// Begin epilogue.
+//
+//
+// End epilogue.
+