Jan Dittberner
ad6b987c91
- decouple config and messages - cainfo maps from config.Profile to messages.CAProfile - config parses profile usage - validity can be configured per certificate profile, defaults are defined in a defaultValidity method of the profile usage - the client simulator emits certificate signing requests at random intervals - add implementation of SingCertificateCommand to MsgPackHandler - remove indirection signing.RequestSignature
317 lines
8.6 KiB
Go
317 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}
|
|
}
|