aboutsummaryrefslogtreecommitdiff
path: root/bpkg/repository-signing.cxx
blob: 66366cb5b4fca7819b91716e2497c1deef870d0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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.