1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package x509tools
18
19 import (
20 "bytes"
21 "crypto"
22 "crypto/rand"
23 "crypto/x509"
24 "crypto/x509/pkix"
25 "encoding/pem"
26 "errors"
27 "fmt"
28 "io"
29 "math/big"
30 "os"
31 "strings"
32 "time"
33
34 "github.com/spf13/cobra"
35 "golang.org/x/crypto/ssh/terminal"
36 )
37
38 var (
39 ArgCountry string
40 ArgOrganization string
41 ArgOrganizationalUnit string
42 ArgLocality string
43 ArgProvince string
44 ArgCommonName string
45 ArgDNSNames string
46 ArgEmailNames string
47 ArgKeyUsage string
48 ArgExpireDays uint
49 ArgCertAuthority bool
50 ArgSerial string
51 ArgInteractive bool
52 ArgRSAPSS bool
53 )
54
55
56 func AddRequestFlags(cmd *cobra.Command) {
57 cmd.Flags().StringVar(&ArgCountry, "countryName", "", "Subject name")
58 cmd.Flags().StringVar(&ArgProvince, "stateOrProvinceName", "", "Subject name")
59 cmd.Flags().StringVar(&ArgLocality, "localityName", "", "Subject name")
60 cmd.Flags().StringVar(&ArgOrganization, "organizationName", "", "Subject name")
61 cmd.Flags().StringVar(&ArgOrganizationalUnit, "organizationalUnitName", "", "Subject name")
62 cmd.Flags().StringVarP(&ArgCommonName, "commonName", "n", "", "Subject commonName")
63 cmd.Flags().StringVar(&ArgDNSNames, "alternate-dns", "", "DNS subject alternate name (comma or space separated)")
64 cmd.Flags().StringVar(&ArgEmailNames, "alternate-email", "", "Email subject alternate name (comma or space separated)")
65 cmd.Flags().BoolVarP(&ArgInteractive, "interactive", "i", false, "Prompt before signing certificate")
66 cmd.Flags().BoolVar(&ArgRSAPSS, "rsa-pss", false, "Use RSA-PSS signature")
67 }
68
69
70 func AddCertFlags(cmd *cobra.Command) {
71 AddRequestFlags(cmd)
72 cmd.Flags().BoolVar(&ArgCertAuthority, "cert-authority", false, "If this certificate is an authority")
73 cmd.Flags().StringVarP(&ArgKeyUsage, "key-usage", "U", "", "Key usage, one of: serverAuth clientAuth codeSigning emailProtection keyCertSign")
74 cmd.Flags().UintVarP(&ArgExpireDays, "expire-days", "e", 36523, "Number of days before certificate expires")
75 cmd.Flags().StringVar(&ArgSerial, "serial", "", "Set the serial number of the certificate. Random if not specified.")
76 }
77
78
79 func splitAndTrim(s string) []string {
80 if s == "" {
81 return nil
82 }
83 s = strings.Replace(s, ",", " ", -1)
84 pieces := strings.Split(s, " ")
85 ret := make([]string, 0, len(pieces))
86 for _, p := range pieces {
87 p = strings.Trim(p, " ")
88 if p != "" {
89 ret = append(ret, p)
90 }
91 }
92 return ret
93 }
94
95
96 func subjName() (name pkix.Name) {
97 if ArgCountry != "" {
98 name.Country = []string{ArgCountry}
99 }
100 if ArgProvince != "" {
101 name.Province = []string{ArgProvince}
102 }
103 if ArgLocality != "" {
104 name.Locality = []string{ArgLocality}
105 }
106 if ArgOrganization != "" {
107 name.Organization = []string{ArgOrganization}
108 }
109 if ArgOrganizationalUnit != "" {
110 name.OrganizationalUnit = []string{ArgOrganizationalUnit}
111 }
112 name.CommonName = ArgCommonName
113 return
114 }
115
116
117 func setUsage(template *x509.Certificate) error {
118 usage := x509.KeyUsageDigitalSignature
119 var extended []x509.ExtKeyUsage
120 switch strings.ToLower(ArgKeyUsage) {
121 case "serverauth":
122 usage |= x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement
123 extended = append(extended, x509.ExtKeyUsageServerAuth)
124 case "clientauth":
125 usage |= x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement
126 extended = append(extended, x509.ExtKeyUsageClientAuth)
127 case "codesigning":
128 extended = append(extended, x509.ExtKeyUsageCodeSigning)
129 case "emailprotection":
130 usage |= x509.KeyUsageContentCommitment | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement
131 extended = append(extended, x509.ExtKeyUsageEmailProtection)
132 case "keycertsign":
133 usage |= x509.KeyUsageCertSign | x509.KeyUsageCRLSign
134 case "":
135 return nil
136 default:
137 return errors.New("invalid key-usage")
138 }
139 template.KeyUsage = usage
140 template.ExtKeyUsage = extended
141 return nil
142 }
143
144 func fillCertFields(template *x509.Certificate, subjectPub, issuerPub crypto.PublicKey) error {
145 if ArgSerial != "" {
146 serial, ok := new(big.Int).SetString(ArgSerial, 0)
147 if !ok {
148 return errors.New("invalid serial number, must be decimal or hexadecimal format")
149 }
150 template.SerialNumber = serial
151 } else if template.SerialNumber == nil {
152 template.SerialNumber = MakeSerial()
153 if template.SerialNumber == nil {
154 return errors.New("Failed to generate a serial number")
155 }
156 }
157 if ArgCommonName != "" {
158 template.Subject = subjName()
159 }
160 if ArgDNSNames != "" {
161 template.DNSNames = splitAndTrim(ArgDNSNames)
162 }
163 if ArgEmailNames != "" {
164 template.EmailAddresses = splitAndTrim(ArgEmailNames)
165 }
166 template.SignatureAlgorithm = X509SignatureAlgorithm(issuerPub)
167 template.NotBefore = time.Now().Add(time.Hour * -24)
168 template.NotAfter = time.Now().Add(time.Hour * 24 * time.Duration(ArgExpireDays))
169 template.IsCA = ArgCertAuthority
170 template.BasicConstraintsValid = true
171 ski, err := SubjectKeyID(subjectPub)
172 if err != nil {
173 return err
174 }
175 template.SubjectKeyId = ski
176 return setUsage(template)
177 }
178
179 func toPemString(der []byte, pemType string) string {
180 block := &pem.Block{Type: pemType, Bytes: der}
181 return string(pem.EncodeToMemory(block))
182 }
183
184
185
186 func MakeRequest(rand io.Reader, key crypto.Signer) (string, error) {
187 var template x509.CertificateRequest
188 template.Subject = subjName()
189 template.DNSNames = splitAndTrim(ArgDNSNames)
190 template.EmailAddresses = splitAndTrim(ArgEmailNames)
191 template.SignatureAlgorithm = X509SignatureAlgorithm(key.Public())
192 csr, err := x509.CreateCertificateRequest(rand, &template, key)
193 if err != nil {
194 return "", err
195 }
196 return toPemString(csr, "CERTIFICATE REQUEST"), nil
197 }
198
199
200
201 func MakeCertificate(rand io.Reader, key crypto.Signer) (string, error) {
202 var template x509.Certificate
203 if err := fillCertFields(&template, key.Public(), key.Public()); err != nil {
204 return "", err
205 }
206 template.Issuer = template.Subject
207 cert, err := confirmAndCreate(&template, &template, key.Public(), key)
208 if err != nil {
209 return "", err
210 }
211 return toPemString(cert, "CERTIFICATE"), nil
212 }
213
214
215
216
217 func SignCSR(csrBytes []byte, rand io.Reader, key crypto.Signer, cacert *x509.Certificate, copyExtensions bool) (string, error) {
218
219 csrBytes, err := parseMaybePEM(csrBytes, "CERTIFICATE REQUEST")
220 if err != nil {
221 return "", err
222 }
223 csr, err := x509.ParseCertificateRequest(csrBytes)
224 if err != nil {
225 return "", fmt.Errorf("parsing CSR: %s", err)
226 }
227 if err := csr.CheckSignature(); err != nil {
228 return "", fmt.Errorf("validating CSR: %s", err)
229 }
230
231 template := &x509.Certificate{Subject: csr.Subject}
232 if copyExtensions {
233 template.ExtraExtensions = csr.Extensions
234 template.DNSNames = csr.DNSNames
235 template.EmailAddresses = csr.EmailAddresses
236 template.IPAddresses = csr.IPAddresses
237 template.URIs = csr.URIs
238 }
239 if err := fillCertFields(template, csr.PublicKey, key.Public()); err != nil {
240 return "", err
241 }
242 certDer, err := confirmAndCreate(template, cacert, csr.PublicKey, key)
243 if err != nil {
244 return "", err
245 }
246 return toPemString(certDer, "CERTIFICATE"), nil
247 }
248
249
250
251 func CrossSign(certBytes []byte, rand io.Reader, key crypto.Signer, cacert *x509.Certificate) (string, error) {
252 certBytes, err := parseMaybePEM(certBytes, "CERTIFICATE")
253 if err != nil {
254 return "", err
255 }
256 template, err := x509.ParseCertificate(certBytes)
257 if err != nil {
258 return "", fmt.Errorf("parsing certificate: %s", err)
259 }
260 if err := fillCertFields(template, template.PublicKey, key.Public()); err != nil {
261 return "", err
262 }
263 newCert, err := confirmAndCreate(template, cacert, template.PublicKey, key)
264 if err != nil {
265 return "", err
266 }
267 return toPemString(newCert, "CERTIFICATE"), nil
268 }
269
270 type fakeSigner struct{ pub crypto.PublicKey }
271
272 func (f fakeSigner) Public() crypto.PublicKey {
273 return f.pub
274 }
275
276 func (f fakeSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
277 return nil, nil
278 }
279
280 func confirmAndCreate(template, parent *x509.Certificate, pub crypto.PublicKey, priv crypto.PrivateKey) ([]byte, error) {
281 if ArgInteractive {
282
283 pub := priv.(crypto.Signer).Public()
284 der, err := x509.CreateCertificate(rand.Reader, template, parent, pub, fakeSigner{pub})
285 if err != nil {
286 return nil, err
287 }
288 cert, err := x509.ParseCertificate(der)
289 if err != nil {
290 return nil, err
291 }
292 fmt.Fprintln(os.Stderr, "Signing certificate:")
293 fmt.Fprintln(os.Stderr)
294 FprintCertificate(os.Stderr, cert)
295 fmt.Fprintln(os.Stderr)
296 if !promptYN("Sign this cert? [Y/n] ") {
297 fmt.Fprintln(os.Stderr, "operation canceled")
298 os.Exit(2)
299 }
300 }
301 return x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
302 }
303
304 func promptYN(prompt string) bool {
305 fmt.Fprint(os.Stderr, prompt)
306 if !terminal.IsTerminal(0) {
307 fmt.Fprintln(os.Stderr, "input is not a terminal, assuming true")
308 return true
309 }
310 state, err := terminal.MakeRaw(0)
311 if err == nil {
312 defer fmt.Fprintln(os.Stderr)
313 defer terminal.Restore(0, state)
314 }
315 var d [1]byte
316 if _, err := os.Stdin.Read(d[:]); err != nil {
317 return false
318 }
319 if d[0] == 'Y' || d[0] == 'y' {
320 return true
321 }
322 return false
323 }
324
325 func parseMaybePEM(blob []byte, pemType string) ([]byte, error) {
326 if bytes.Contains(blob, []byte("-----BEGIN")) {
327 for {
328 var block *pem.Block
329 block, blob = pem.Decode(blob)
330 if block == nil {
331 break
332 } else if block.Type == pemType {
333 return block.Bytes, nil
334 }
335 }
336 } else if len(blob) > 0 && blob[0] == 0x30 {
337 return blob, nil
338 }
339 return nil, fmt.Errorf("expected a %s in PEM or DER format", strings.ToLower(pemType))
340 }
341
View as plain text