...

Source file src/github.com/sassoftware/relic/lib/x509tools/x509cmd.go

Documentation: github.com/sassoftware/relic/lib/x509tools

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    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  // Add flags associated with X509 requests to the given command
    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  // Add flags associated with X509 certificate creation to the given command
    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  // Split a space- and/or comma-seperated string
    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  // Build a subject name from command-line arguments
    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  // Set both basic and extended key usage
   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  // Make a X509 certificate request using command-line arguments and return the
   185  // PEM string
   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  // Make a self-signed X509 certificate using command-line arguments and return
   200  // the PEM string
   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  // SignCSR takes a PKCS#10 signing request in PEM or DER format as input and
   215  // produces a signed certificate in PEM format. Any command-line flags set will
   216  // override the CSR contents.
   217  func SignCSR(csrBytes []byte, rand io.Reader, key crypto.Signer, cacert *x509.Certificate, copyExtensions bool) (string, error) {
   218  	// parse and validate CSR
   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  	// update fields
   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  // CrossSign takes a certificate as input and re-signs it using the given key.
   250  // Any command-line flags set will override the CSR contents.
   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  		// call CreateCertificate with a fake signer to get what the final cert will look like
   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