You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
8.6 KiB
Go

/*
Copyright 2021-2022 CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package signing
import (
"crypto"
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"fmt"
"time"
"github.com/sirupsen/logrus"
"git.cacert.org/cacert-gosigner/internal/x509/helper"
)
// PolicyIdentifierCAcertClass3Policy is the ASN.1 object identifier for the CAcert Class3 policy from [OIDAllocation]
//
// [OIDAllocation]: https://wiki.cacert.org/OidAllocation
var PolicyIdentifierCAcertClass3Policy = asn1.ObjectIdentifier{1, 3, 6, 4, 1, 18506, 2, 3}
type X509Signing struct {
signer Signer
repo Repository
}
func NewX509Signing(signer Signer, repo Repository) *X509Signing {
return &X509Signing{signer: signer, repo: repo}
}
type CertificateSigned struct {
certificate *x509.Certificate
}
func (c CertificateSigned) Certificate() *x509.Certificate {
return c.certificate
}
func (x *X509Signing) Sign(signingRequest *SignerRequest) (*SignerResponse, error) {
certificateFromSigner, err := x.signer.SignCertificate(signingRequest)
if err != nil {
return nil, fmt.Errorf("could not sign certificate: %w", err)
}
err = x.repo.StoreCertificate(certificateFromSigner.Certificate)
if err != nil {
return nil, fmt.Errorf("could not store certificate: %w", err)
}
return certificateFromSigner, nil
}
type Handler interface {
GetSigner(id string, profile string) (*X509Signing, error)
}
type ProfileUsage uint8
const (
UsageInvalid ProfileUsage = iota
UsageOCSP
UsageClient
UsageCode
UsagePerson
UsageServer
UsageServerClient
UsageOrgClient
UsageOrgCode
UsageOrgEmail
UsageOrgPerson
UsageOrgServer
UsageOrgServerClient
)
var profileUsageNames = map[ProfileUsage]string{
UsageInvalid: "invalid",
UsageOCSP: "ocsp",
UsageClient: "client",
UsageCode: "code",
UsagePerson: "person",
UsageServer: "server",
UsageServerClient: "server_client",
UsageOrgClient: "org_client",
UsageOrgCode: "org_code",
UsageOrgEmail: "org_email",
UsageOrgPerson: "org_person",
UsageOrgServer: "org_server",
UsageOrgServerClient: "org_server_client",
}
func (p ProfileUsage) String() string {
name, ok := profileUsageNames[p]
if !ok {
return fmt.Sprintf("unknown profile usage %d", p)
}
return name
}
var profileUsageDescriptions = map[ProfileUsage]string{
UsageInvalid: "Invalid certificate profile, not to be used",
UsageOCSP: "OCSP responder signing certificate",
UsageClient: "machine TLS client certificate",
UsageCode: "individual code signing certificate",
UsagePerson: "person identity certificate",
UsageServer: "TLS server certificate",
UsageServerClient: "combined TLS server and client certificate",
UsageOrgClient: "organization machine TLS client certificate",
UsageOrgCode: "organization code signing certificate",
UsageOrgEmail: "organization email certificate",
UsageOrgPerson: "organizational person identity certificate",
UsageOrgServer: "organization TLS server certificate",
UsageOrgServerClient: "combined organization TLS server and client certificate",
}
func (p ProfileUsage) Description() string {
description, ok := profileUsageDescriptions[p]
if !ok {
return fmt.Sprintf("unknown profile usage %d", p)
}
return description
}
type Profile struct {
logger *logrus.Logger
privateKey crypto.Signer
certificate *x509.Certificate
ocspURLs []string
name string
usage ProfileUsage
years int
months int
days int
}
func (p *Profile) SignCertificate(request *SignerRequest) (*SignerResponse, error) {
signatureAlgorithm, err := p.determineSignatureAlgorithm(request.PreferredHash)
if err != nil {
return nil, err
}
notBefore := time.Now().UTC()
serialNumber, err := helper.GenerateRandomSerial()
if err != nil {
return nil, fmt.Errorf("could not generate certificate serial number: %w", err)
}
const x509v3 = 3
template := &x509.Certificate{
SignatureAlgorithm: signatureAlgorithm,
Version: x509v3,
SerialNumber: serialNumber,
Issuer: p.certificate.Subject,
Subject: request.SubjectDN,
NotBefore: notBefore,
NotAfter: p.setNotAfter(notBefore),
KeyUsage: p.keyUsage(),
ExtKeyUsage: p.extKeyUsage(),
IsCA: false,
OCSPServer: p.ocspURLs,
DNSNames: request.DNSNames,
EmailAddresses: request.Emails,
CRLDistributionPoints: p.certificate.CRLDistributionPoints,
PolicyIdentifiers: p.policyIdentifiers(),
}
certBytes, err := x509.CreateCertificate(rand.Reader, template, p.certificate, request.CSR.PublicKey, p.privateKey)
if err != nil {
return nil, fmt.Errorf("certificate signing failed: %w", err)
}
signedCertificate, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, fmt.Errorf("failed to build certificate from DER data: %w", err)
}
return &SignerResponse{Certificate: signedCertificate}, nil
}
func (p *Profile) setNotAfter(before time.Time) time.Time {
return before.AddDate(p.years, p.months, p.days)
}
func (p *Profile) determineSignatureAlgorithm(hash crypto.Hash) (x509.SignatureAlgorithm, error) {
switch p.certificate.PublicKeyAlgorithm {
case x509.RSA:
switch hash {
case crypto.SHA512:
return x509.SHA512WithRSA, nil
case crypto.SHA384:
return x509.SHA384WithRSA, nil
default:
return x509.SHA256WithRSA, nil
}
case x509.ECDSA:
switch hash {
case crypto.SHA512:
return x509.ECDSAWithSHA512, nil
case crypto.SHA384:
return x509.ECDSAWithSHA384, nil
default:
return x509.ECDSAWithSHA384, nil
}
default:
return x509.UnknownSignatureAlgorithm, fmt.Errorf(
"could not determine signature algorithm for public key algorithm %s",
p.certificate.PublicKeyAlgorithm,
)
}
}
func (p *Profile) keyUsage() x509.KeyUsage {
switch p.usage {
case UsageClient, UsagePerson, UsageServer, UsageServerClient, UsageOrgClient, UsageOrgEmail, UsageOrgPerson,
UsageOrgServer, UsageOrgServerClient:
return x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
default:
return x509.KeyUsageDigitalSignature
}
}
func (p *Profile) extKeyUsage() []x509.ExtKeyUsage {
switch p.usage {
case UsageClient, UsageOrgClient:
return []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
case UsagePerson, UsageOrgPerson:
return []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection}
case UsageOrgEmail:
return []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}
case UsageCode, UsageOrgCode:
return []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}
case UsageServer, UsageOrgServer:
return []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
case UsageServerClient, UsageOrgServerClient:
return []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
case UsageOCSP:
return []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}
default:
return nil
}
}
func (p *Profile) policyIdentifiers() []asn1.ObjectIdentifier {
switch p.usage {
case UsageOCSP:
return nil
default:
return []asn1.ObjectIdentifier{PolicyIdentifierCAcertClass3Policy}
}
}
func NewProfile(
logger *logrus.Logger, privateKey crypto.Signer, certificate *x509.Certificate, name string, ocspURLs []string,
useFor ProfileUsage, years int, months int, days int,
) *Profile {
return &Profile{
privateKey: privateKey,
certificate: certificate,
ocspURLs: ocspURLs,
name: name,
usage: useFor,
years: years,
months: months,
days: days,
logger: logger,
}
}
type SignCertificateHandler struct {
profiles map[string]map[string]*X509Signing
}
func (s *SignCertificateHandler) GetSigner(issuerID string, profileName string) (*X509Signing, error) {
profiles, ok := s.profiles[issuerID]
if !ok {
return nil, fmt.Errorf("no CA definition found for issuer id %s", issuerID)
}
profile, ok := profiles[profileName]
if !ok {
return nil, fmt.Errorf("unknown profile %s for issuer %s", profileName, issuerID)
}
return profile, nil
}
func NewSignCertificateHandler(profiles map[string]map[string]*X509Signing) *SignCertificateHandler {
return &SignCertificateHandler{profiles: profiles}
}