func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile) (cert []byte, err error) {
	err = signer.FillTemplate(template, s.policy.Default, profile)
	if err != nil {
		return
	}

	var initRoot bool
	if s.ca == nil {
		if !template.IsCA {
			err = cferr.New(cferr.PolicyError, cferr.InvalidRequest)
			return
		}
		template.DNSNames = nil
		template.EmailAddresses = nil
		s.ca = template
		initRoot = true
		template.MaxPathLen = signer.MaxPathLen
		template.MaxPathLenZero = signer.MaxPathLenZero
	} else if template.IsCA {
		template.MaxPathLen = 1
		template.DNSNames = nil
		template.EmailAddresses = nil
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
	if err != nil {
		return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err)
	}
	if initRoot {
		s.ca, err = x509.ParseCertificate(derBytes)
		if err != nil {
			return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, err)
		}
	}

	cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	log.Infof("signed certificate with serial number %d", template.SerialNumber)
	return
}
Beispiel #2
0
func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile) (cert []byte, err error) {
	pub := template.PublicKey
	encodedpub, err := x509.MarshalPKIXPublicKey(pub)
	if err != nil {
		return
	}
	pubhash := sha1.New()
	pubhash.Write(encodedpub)

	if profile == nil {
		profile = s.Policy.Default
	}

	var (
		eku             []x509.ExtKeyUsage
		ku              x509.KeyUsage
		expiry          time.Duration
		crlURL, ocspURL string
	)

	// The third value returned from Usages is a list of unknown key usages.
	// This should be used when validating the profile at load, and isn't used
	// here.
	ku, eku, _ = profile.Usages()
	expiry = profile.Expiry
	if profile.IssuerURL == nil {
		profile.IssuerURL = s.Policy.Default.IssuerURL
	}

	if ku == 0 && len(eku) == 0 {
		err = cferr.New(cferr.PolicyError, cferr.NoKeyUsages, errors.New("no key usage available"))
		return
	}

	if expiry == 0 {
		expiry = s.Policy.Default.Expiry
	}

	if crlURL = profile.CRL; crlURL == "" {
		crlURL = s.Policy.Default.CRL
	}
	if ocspURL = profile.OCSP; ocspURL == "" {
		ocspURL = s.Policy.Default.OCSP
	}

	now := time.Now()
	serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
	if err != nil {
		err = cferr.New(cferr.CertificateError, cferr.Unknown, err)
	}

	template.SerialNumber = serialNumber
	template.NotBefore = now.Add(-5 * time.Minute).UTC()
	template.NotAfter = now.Add(expiry).UTC()
	template.KeyUsage = ku
	template.ExtKeyUsage = eku
	template.BasicConstraintsValid = true
	template.IsCA = profile.CA
	template.SubjectKeyId = pubhash.Sum(nil)

	if ocspURL != "" {
		template.OCSPServer = []string{ocspURL}
	}
	if crlURL != "" {
		template.CRLDistributionPoints = []string{crlURL}
	}

	if len(profile.IssuerURL) != 0 {
		template.IssuingCertificateURL = profile.IssuerURL
	}

	var initRoot bool
	if s.CA == nil {
		if !template.IsCA {
			err = cferr.New(cferr.PolicyError, cferr.InvalidRequest, nil)
			return
		}
		template.DNSNames = nil
		s.CA = template
		initRoot = true
		template.MaxPathLen = 2
	} else if template.IsCA {
		template.MaxPathLen = 1
		template.DNSNames = nil
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, template, s.CA, pub, s.Priv)
	if err != nil {
		return
	}
	if initRoot {
		s.CA, err = x509.ParseCertificate(derBytes)
		if err != nil {
			err = cferr.New(cferr.CertificateError, cferr.ParseFailed, err)
			return
		}
	}
	cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	return
}
Beispiel #3
0
// FillTemplate is a utility function that tries to load as much of
// the certificate template as possible from the profiles and current
// template. It fills in the key uses, expiration, revocation URLs
// and SKI.
func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.SigningProfile) error {
	ski, err := ComputeSKI(template)

	var (
		eku             []x509.ExtKeyUsage
		ku              x509.KeyUsage
		backdate        time.Duration
		expiry          time.Duration
		notBefore       time.Time
		notAfter        time.Time
		crlURL, ocspURL string
		issuerURL       = profile.IssuerURL
	)

	// The third value returned from Usages is a list of unknown key usages.
	// This should be used when validating the profile at load, and isn't used
	// here.
	ku, eku, _ = profile.Usages()
	if profile.IssuerURL == nil {
		issuerURL = defaultProfile.IssuerURL
	}

	if ku == 0 && len(eku) == 0 {
		return cferr.New(cferr.PolicyError, cferr.NoKeyUsages)
	}

	if expiry = profile.Expiry; expiry == 0 {
		expiry = defaultProfile.Expiry
	}

	if crlURL = profile.CRL; crlURL == "" {
		crlURL = defaultProfile.CRL
	}
	if ocspURL = profile.OCSP; ocspURL == "" {
		ocspURL = defaultProfile.OCSP
	}
	if backdate = profile.Backdate; backdate == 0 {
		backdate = -5 * time.Minute
	} else {
		backdate = -1 * profile.Backdate
	}

	if !profile.NotBefore.IsZero() {
		notBefore = profile.NotBefore.UTC()
	} else {
		notBefore = time.Now().Round(time.Minute).Add(backdate).UTC()
	}

	if !profile.NotAfter.IsZero() {
		notAfter = profile.NotAfter.UTC()
	} else {
		notAfter = notBefore.Add(expiry).UTC()
	}

	template.NotBefore = notBefore
	template.NotAfter = notAfter
	template.KeyUsage = ku
	template.ExtKeyUsage = eku
	template.BasicConstraintsValid = true
	template.IsCA = profile.CAConstraint.IsCA
	if template.IsCA {
		template.MaxPathLen = profile.CAConstraint.MaxPathLen
		if template.MaxPathLen == 0 {
			template.MaxPathLenZero = profile.CAConstraint.MaxPathLenZero
		}
		template.DNSNames = nil
		template.EmailAddresses = nil
	}
	template.SubjectKeyId = ski

	if ocspURL != "" {
		template.OCSPServer = []string{ocspURL}
	}
	if crlURL != "" {
		template.CRLDistributionPoints = []string{crlURL}
	}

	if len(issuerURL) != 0 {
		template.IssuingCertificateURL = issuerURL
	}
	if len(profile.Policies) != 0 {
		err = addPolicies(template, profile.Policies)
		if err != nil {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
		}
	}
	if profile.OCSPNoCheck {
		ocspNoCheckExtension := pkix.Extension{
			Id:       asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5},
			Critical: false,
			Value:    []byte{0x05, 0x00},
		}
		template.ExtraExtensions = append(template.ExtraExtensions, ocspNoCheckExtension)
	}

	return nil
}
Beispiel #4
0
// GenerateKeyPair generates or reloads an RSA keypair with key usages
// determined by `purpose`. The disk files that are generated or reused are
// named `name`-key.pem and `name`-cert.pem for the private and public halves,
// respectively. `commonName` and `hosts` are the corresponding fields in the
// certificate.
//
// cc.Serial is incremented for each key that is freshly generated.
func (cc *CertCreator) GenerateKeyPair(purpose Purpose, parent *KeyPair, name string, commonName string, hosts ...string) (*KeyPair, error) {
	if pair, err := LoadKeyPairFromDisk(name); err == nil {
		return pair, nil
	}

	keyFile, certFile := name+"-key.pem", name+"-cert.pem"
	if _, err := os.Stat(certFile); !os.IsNotExist(err) {
		return nil, fmt.Errorf("Cert file %q already exists", certFile)
	}

	var extUsages []x509.ExtKeyUsage
	if purpose == CA {
		extUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
	} else if purpose == SERVER {
		extUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
	} else {
		extUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
	}

	serial := new(big.Int).SetInt64(cc.Serial)
	cc.Serial++
	template := x509.Certificate{
		Subject: pkix.Name{
			Country:      []string{cc.Country},
			Province:     []string{cc.State},
			Locality:     []string{cc.City},
			Organization: []string{cc.Organization},
			CommonName:   commonName,
			SerialNumber: serial.String(),
		},

		NotBefore:    cc.NotBefore,
		NotAfter:     cc.NotAfter,
		SerialNumber: serial,

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           extUsages,
		BasicConstraintsValid: true, // enforces IsCA and KeyUsage
	}
	if purpose == CA {
		template.IsCA = true
		template.KeyUsage = x509.KeyUsageCertSign
	} else {
		template.MaxPathLen = 1
	}

	// generate IP and DNS SANs
	for _, h := range hosts {
		if ip := net.ParseIP(h); ip != nil {
			template.IPAddresses = append(template.IPAddresses, ip)
		} else {
			template.DNSNames = append(template.DNSNames, h)
		}
	}

	var privKey *rsa.PrivateKey
	var reusedKey bool
	if keyBytes, err := ioutil.ReadFile(keyFile); err == nil {
		// reuse existing key
		keyDERBlock, _ := pem.Decode(keyBytes)
		if keyDERBlock == nil {
			return nil, errors.New("failed to parse key PEM data")
		}
		if keyDERBlock.Type != "RSA PRIVATE KEY" {
			return nil, fmt.Errorf("key is not a RSA PRIVATE KEY: %s", keyDERBlock.Type)
		}
		privKey, err = x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes)
		if err != nil {
			return nil, err
		}
		reusedKey = true
		vlog.VLogf("Reusing key %q\n", keyFile)
	} else if os.IsNotExist(err) {
		// generate a new key
		privKey, err = rsa.GenerateKey(rand.Reader, cc.KeySize)
		if err != nil {
			return nil, fmt.Errorf("Failed to generate private key: %s", err)
		}
	} else {
		return nil, err
	}

	origParent := parent
	if parent == nil {
		// CA signs itself
		parent = &KeyPair{&template, privKey}
	}

	// sign the key
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent.Cert, &privKey.PublicKey, parent.PrivKey)
	if err != nil {
		return nil, err
	}

	// check that the cert can be parsed
	cert, err := x509.ParseCertificate(derBytes)
	if err != nil {
		return nil, fmt.Errorf("Cert doesn't verify against its CA: %s", err)
	}

	// check that the cert verifies against its own CA
	roots := x509.NewCertPool()
	if origParent == nil {
		roots.AddCert(cert)
	} else {
		roots.AddCert(parent.Cert)
	}
	_, err = cert.Verify(x509.VerifyOptions{Roots: roots, KeyUsages: extUsages})
	if err != nil {
		return nil, fmt.Errorf("Couldn't verify %q against its own CA: %s", name, err)
	}

	// write key and cert to disk
	if !reusedKey {
		keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
		if err != nil {
			return nil, fmt.Errorf("Failed to open %q for writing: %s", keyFile, err)
		}
		pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)})
		keyOut.Close()
		vlog.VLogf("Wrote key %q\n", keyFile)
	}

	certOut, err := os.Create(certFile)
	if err != nil {
		return nil, fmt.Errorf("Failed to open %q for writing: %s", certFile, err)
	}
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	certOut.Close()
	vlog.VLogf("Wrote cert %q\n", certFile)

	return LoadKeyPairFromDisk(name)
}