Implement sign certificate command
- 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
This commit is contained in:
parent
9c17a6730f
commit
ad6b987c91
14 changed files with 1644 additions and 990 deletions
|
@ -21,10 +21,15 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
@ -52,9 +57,11 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
|||
}
|
||||
|
||||
const (
|
||||
healthInterval = 5 * time.Second
|
||||
crlInterval = 15 * time.Minute
|
||||
startPause = 3 * time.Second
|
||||
healthInterval = 5 * time.Second
|
||||
crlInterval = 15 * time.Minute
|
||||
startPause = 3 * time.Second
|
||||
minSignInterval = 5 * time.Second
|
||||
maxSignInterval = 10 * time.Second
|
||||
)
|
||||
|
||||
g.logger.Info("start generating commands")
|
||||
|
@ -63,11 +70,12 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
|||
|
||||
g.commands <- &protocol.Command{
|
||||
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
||||
Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"},
|
||||
Command: &messages.FetchCRLCommand{IssuerID: "ecc_person_2022"},
|
||||
}
|
||||
|
||||
healthTimer := time.NewTimer(healthInterval)
|
||||
crlTimer := time.NewTimer(crlInterval)
|
||||
signTimer := time.NewTimer(minSignInterval)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
@ -91,12 +99,48 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
|||
case <-crlTimer.C:
|
||||
g.commands <- &protocol.Command{
|
||||
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
||||
Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"},
|
||||
Command: &messages.FetchCRLCommand{IssuerID: "ecc_person_2022"},
|
||||
}
|
||||
case <-signTimer.C:
|
||||
g.commands <- &protocol.Command{
|
||||
Announce: messages.BuildCommandAnnounce(messages.CmdSignCertificate),
|
||||
Command: &messages.SignCertificateCommand{
|
||||
IssuerID: "ecc_person_2022",
|
||||
ProfileName: "person",
|
||||
CSRData: g.generateCsr("Test Person"),
|
||||
CommonName: "Test Person",
|
||||
EmailAddresses: []string{"test@example.org"},
|
||||
PreferredHash: crypto.SHA256,
|
||||
},
|
||||
}
|
||||
|
||||
newRandomDuration := minSignInterval + time.Duration(mathRand.Int63n(int64(maxSignInterval)))
|
||||
|
||||
signTimer.Reset(newRandomDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *TestCommandGenerator) generateCsr(cn string) []byte {
|
||||
keyPair, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
g.logger.WithError(err).Panic("could not generate key pair")
|
||||
}
|
||||
|
||||
template := &x509.CertificateRequest{
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
PublicKey: keyPair.Public(),
|
||||
Subject: pkix.Name{CommonName: cn},
|
||||
}
|
||||
|
||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, template, keyPair)
|
||||
if err != nil {
|
||||
g.logger.WithError(err).Panic("could not create signing request")
|
||||
}
|
||||
|
||||
return csrBytes
|
||||
}
|
||||
|
||||
type clientSimulator struct {
|
||||
clientHandler protocol.ClientHandler
|
||||
framesIn chan []byte
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"git.cacert.org/cacert-gosigner/internal/hsm"
|
||||
"git.cacert.org/cacert-gosigner/internal/serial"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -94,18 +95,21 @@ func main() {
|
|||
|
||||
healthHandler := health.New(version, access)
|
||||
|
||||
revokingRepositories, err := configureRepositories(caConfig, logger)
|
||||
revokingRepositories, signingRepositories, err := configureRepositories(caConfig, logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).Fatal("could not setup revoking repositories")
|
||||
logger.WithError(err).Fatal("could not setup repositories")
|
||||
}
|
||||
|
||||
fetchCRLHandler := revoking.NewFetchCRLHandler(revokingRepositories)
|
||||
|
||||
signX509Handler := signing.NewSignCertificateHandler(signingRepositories)
|
||||
|
||||
proto, err := handler.New(
|
||||
logger,
|
||||
handler.RegisterHealthHandler(healthHandler),
|
||||
handler.RegisterFetchCRLHandler(fetchCRLHandler),
|
||||
handler.RegisterCAInfoHandler(access),
|
||||
handler.RegisterCertificateSigningHandler(signX509Handler),
|
||||
)
|
||||
if err != nil {
|
||||
logger.WithError(err).Fatal("could not setup protocol handler")
|
||||
|
@ -151,26 +155,32 @@ func runSigner(logger *logrus.Logger, serialHandler *serial.Handler) error {
|
|||
func configureRepositories(
|
||||
caConfig *config.SignerConfig,
|
||||
logger *logrus.Logger,
|
||||
) (map[string]*revoking.X509Revoking, error) {
|
||||
) (map[string]*revoking.X509Revoking, map[string]map[string]*signing.X509Signing, error) {
|
||||
var err error
|
||||
|
||||
result := make(map[string]*revoking.X509Revoking)
|
||||
revokers := make(map[string]*revoking.X509Revoking)
|
||||
signers := make(map[string]map[string]*signing.X509Signing)
|
||||
|
||||
for _, name := range caConfig.RootCAs() {
|
||||
result[name], err = buildX509Revoking(caConfig, name, logger)
|
||||
revokers[name], err = buildX509Revoking(caConfig, name, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range caConfig.SubordinateCAs() {
|
||||
result[name], err = buildX509Revoking(caConfig, name, logger)
|
||||
revokers[name], err = buildX509Revoking(caConfig, name, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
signers[name], err = buildX509Signing(caConfig, name, logger)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return revokers, signers, nil
|
||||
}
|
||||
|
||||
func buildX509Revoking(
|
||||
|
@ -197,6 +207,33 @@ func buildX509Revoking(
|
|||
), nil
|
||||
}
|
||||
|
||||
func buildX509Signing(
|
||||
caConfig *config.SignerConfig,
|
||||
name string,
|
||||
logger *logrus.Logger,
|
||||
) (map[string]*signing.X509Signing, error) {
|
||||
caDef, err := caConfig.GetCADefinition(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get CA definition for %s: %w", name, err)
|
||||
}
|
||||
|
||||
repo, err := caConfig.Repository(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get repository for %s: %w", name, err)
|
||||
}
|
||||
|
||||
result := make(map[string]*signing.X509Signing)
|
||||
|
||||
for _, prof := range caDef.Profiles {
|
||||
result[prof.Name] = signing.NewX509Signing(signing.NewProfile(
|
||||
logger, caDef.KeyPair, caDef.Certificate, prof.Name,
|
||||
[]string{caConfig.BuildOCSPURL(caDef)}, prof.UseFor, prof.Years, prof.Months, prof.Days,
|
||||
), repo)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func initializeHSM(caConfig *config.SignerConfig, setupMode, verbose bool, logger *logrus.Logger) *hsm.Access {
|
||||
opts := make([]hsm.ConfigOption, 0)
|
||||
|
||||
|
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
|||
|
||||
package cainfo
|
||||
|
||||
import "git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
import (
|
||||
"git.cacert.org/cacert-gosigner/internal/config"
|
||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Certificate []byte
|
||||
|
@ -27,3 +30,17 @@ type Result struct {
|
|||
type Handler interface {
|
||||
GetCAInfo(string) (*Result, error)
|
||||
}
|
||||
|
||||
func MapProfiles(profiles []config.Profile) []messages.CAProfile {
|
||||
value := make([]messages.CAProfile, len(profiles))
|
||||
|
||||
for i, p := range profiles {
|
||||
value[i] = messages.CAProfile{
|
||||
Name: p.Name,
|
||||
Description: p.UseFor.Description(),
|
||||
UseFor: p.UseFor,
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
|
|
@ -31,8 +31,6 @@ import (
|
|||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/openssl"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
|
@ -533,6 +531,40 @@ func nameToCurve(name string) (elliptic.Curve, error) {
|
|||
}
|
||||
}
|
||||
|
||||
var validProfileUsages = map[string]signing.ProfileUsage{
|
||||
"ocsp": signing.UsageOCSP,
|
||||
|
||||
"client": signing.UsageClient,
|
||||
"code": signing.UsageCode,
|
||||
"person": signing.UsagePerson,
|
||||
"server": signing.UsageServer,
|
||||
"server_client": signing.UsageServerClient,
|
||||
|
||||
"org_client": signing.UsageOrgClient,
|
||||
"org_code": signing.UsageOrgCode,
|
||||
"org_email": signing.UsageOrgEmail,
|
||||
"org_person": signing.UsageOrgPerson,
|
||||
"org_server": signing.UsageOrgServer,
|
||||
"org_server_client": signing.UsageOrgServerClient,
|
||||
}
|
||||
|
||||
func ParseUsage(u string) (signing.ProfileUsage, error) {
|
||||
usage, ok := validProfileUsages[u]
|
||||
if !ok {
|
||||
return signing.UsageInvalid, fmt.Errorf("unsupported profile usage: %s", u)
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Name string
|
||||
UseFor signing.ProfileUsage
|
||||
Years int
|
||||
Months int
|
||||
Days int
|
||||
}
|
||||
|
||||
type CaCertificateEntry struct {
|
||||
KeyInfo *PrivateKeyInfo
|
||||
CommonName string
|
||||
|
@ -542,7 +574,7 @@ type CaCertificateEntry struct {
|
|||
KeyPair crypto.Signer
|
||||
Parent string
|
||||
Storage string
|
||||
Profiles []messages.CAProfile
|
||||
Profiles []Profile
|
||||
}
|
||||
|
||||
func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
||||
|
@ -555,8 +587,13 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
|||
Parent string `yaml:"parent"`
|
||||
Storage string `yaml:"storage"`
|
||||
Profiles []struct {
|
||||
Name string `yaml:"name"`
|
||||
UseFor string `yaml:"use-for"`
|
||||
Name string `yaml:"name"`
|
||||
UseFor string `yaml:"use-for"`
|
||||
Validity struct {
|
||||
Years int `yaml:"years"`
|
||||
Months int `yaml:"months"`
|
||||
Days int `yaml:"days"`
|
||||
} `yaml:"validity"`
|
||||
} `yaml:"profiles"`
|
||||
}
|
||||
|
||||
|
@ -598,17 +635,24 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
|||
}
|
||||
|
||||
if m.Profiles != nil {
|
||||
c.Profiles = make([]messages.CAProfile, len(m.Profiles))
|
||||
c.Profiles = make([]Profile, len(m.Profiles))
|
||||
|
||||
for i, prof := range m.Profiles {
|
||||
usage, err := messages.ParseUsage(prof.UseFor)
|
||||
usage, err := ParseUsage(prof.UseFor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config error: %w", err)
|
||||
}
|
||||
|
||||
c.Profiles[i] = messages.CAProfile{
|
||||
if prof.Validity.Years > 0 || prof.Validity.Months > 0 || prof.Validity.Days > 0 {
|
||||
prof.Validity.Years, prof.Validity.Months, prof.Validity.Days = defaultValidity(usage)
|
||||
}
|
||||
|
||||
c.Profiles[i] = Profile{
|
||||
Name: prof.Name,
|
||||
UseFor: usage,
|
||||
Years: prof.Validity.Years,
|
||||
Months: prof.Validity.Months,
|
||||
Days: prof.Validity.Days,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -616,6 +660,15 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func defaultValidity(useFor signing.ProfileUsage) (years int, months int, days int) {
|
||||
switch useFor {
|
||||
case signing.UsagePerson, signing.UsageOrgPerson, signing.UsageOrgEmail:
|
||||
return 2, 0, 0
|
||||
default:
|
||||
return 1, 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CaCertificateEntry) IsRoot() bool {
|
||||
return c.Parent == ""
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/internal/config"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
)
|
||||
|
||||
type TestCurve struct {
|
||||
|
@ -992,3 +993,26 @@ func TestSignerConfig_GetKeyStorage(t *testing.T) {
|
|||
assert.ErrorContains(t, err, "could not find storage definition with label")
|
||||
assert.Nil(t, keyStorage)
|
||||
}
|
||||
|
||||
func TestParseUsage(t *testing.T) {
|
||||
okValues := []string{
|
||||
"ocsp", "client", "code", "person", "server", "server_client",
|
||||
"org_client", "org_code", "org_email", "org_person", "org_server", "org_server_client",
|
||||
}
|
||||
|
||||
for _, v := range okValues {
|
||||
t.Run(v, func(t *testing.T) {
|
||||
u, err := config.ParseUsage(v)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, u, signing.ProfileUsage(0))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
u, err := config.ParseUsage("foo")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, u, signing.UsageInvalid)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package handler
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -30,6 +32,7 @@ import (
|
|||
"git.cacert.org/cacert-gosigner/internal/cainfo"
|
||||
"git.cacert.org/cacert-gosigner/internal/health"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
"git.cacert.org/cacert-gosigner/pkg/protocol"
|
||||
)
|
||||
|
@ -44,6 +47,7 @@ type MsgPackHandler struct {
|
|||
healthHandler *health.Handler
|
||||
certificateAuthorityInfoHandler cainfo.Handler
|
||||
fetchCRLHandler *revoking.FetchCRLHandler
|
||||
x509SigningHandler signing.Handler
|
||||
}
|
||||
|
||||
func (m *MsgPackHandler) CommandAnnounce(ctx context.Context, frames <-chan []byte) (*protocol.Command, error) {
|
||||
|
@ -174,6 +178,18 @@ func (m *MsgPackHandler) parseFetchCRLCommand(frame []byte) (*messages.FetchCRLC
|
|||
return &command, nil
|
||||
}
|
||||
|
||||
func (m *MsgPackHandler) parseSignCertificateCommand(frame []byte) (*messages.SignCertificateCommand, error) {
|
||||
var command messages.SignCertificateCommand
|
||||
|
||||
if err := msgpack.Unmarshal(frame, &command); err != nil {
|
||||
m.logger.WithError(err).Error("unmarshal failed")
|
||||
|
||||
return nil, errors.New("could not unmarshal sign certificate command")
|
||||
}
|
||||
|
||||
return &command, nil
|
||||
}
|
||||
|
||||
func (m *MsgPackHandler) handleCommand(command *protocol.Command) (*protocol.Response, error) {
|
||||
var (
|
||||
responseCode messages.ResponseCode
|
||||
|
@ -202,6 +218,13 @@ func (m *MsgPackHandler) handleCommand(command *protocol.Command) (*protocol.Res
|
|||
}
|
||||
|
||||
responseCode, responseData = messages.RespFetchCRL, response
|
||||
case *messages.SignCertificateCommand:
|
||||
response, err := m.handleSignCertificateCommand(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseCode, responseData = messages.RespSignCertificate, response
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled command %s", command.Announce)
|
||||
}
|
||||
|
@ -242,6 +265,13 @@ func (m *MsgPackHandler) parseCommand(frame []byte, command *protocol.Command) e
|
|||
}
|
||||
|
||||
command.Command = fetchCRLCommand
|
||||
case messages.CmdSignCertificate:
|
||||
signCertificateCommand, err := m.parseSignCertificateCommand(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
command.Command = signCertificateCommand
|
||||
default:
|
||||
return fmt.Errorf("unhandled command code %s", command.Announce.Code)
|
||||
}
|
||||
|
@ -315,6 +345,43 @@ func (m *MsgPackHandler) handleFetchCRLCommand(command *messages.FetchCRLCommand
|
|||
return response, nil
|
||||
}
|
||||
|
||||
func (m *MsgPackHandler) handleSignCertificateCommand(
|
||||
command *messages.SignCertificateCommand,
|
||||
) (*messages.SignCertificateResponse, error) {
|
||||
csr, err := x509.ParseCertificateRequest(command.CSRData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse certificate signing request: %w", err)
|
||||
}
|
||||
|
||||
signerRequest := &signing.SignerRequest{
|
||||
CSR: csr,
|
||||
SubjectDN: pkix.Name{CommonName: command.CommonName},
|
||||
Emails: command.EmailAddresses,
|
||||
DNSNames: command.Hostnames,
|
||||
PreferredHash: command.PreferredHash,
|
||||
}
|
||||
|
||||
if command.Organization != "" {
|
||||
signerRequest.SubjectDN.Organization = []string{command.Organization}
|
||||
}
|
||||
|
||||
if command.OrganizationalUnit != "" {
|
||||
signerRequest.SubjectDN.OrganizationalUnit = []string{command.OrganizationalUnit}
|
||||
}
|
||||
|
||||
x509Signing, err := m.x509SigningHandler.GetSigner(command.IssuerID, command.ProfileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get X.509 signing component: %w", err)
|
||||
}
|
||||
|
||||
res, err := x509Signing.Sign(signerRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not sign certificate: %w", err)
|
||||
}
|
||||
|
||||
return &messages.SignCertificateResponse{CertificateData: res.Certificate.Raw}, nil
|
||||
}
|
||||
|
||||
func New(logger *logrus.Logger, handlers ...RegisterHandler) (protocol.ServerHandler, error) {
|
||||
messages.RegisterGeneratedResolver()
|
||||
|
||||
|
@ -348,3 +415,9 @@ func RegisterCAInfoHandler(caInfoHandler cainfo.Handler) func(handler *MsgPackHa
|
|||
h.certificateAuthorityInfoHandler = caInfoHandler
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterCertificateSigningHandler(signingHandler signing.Handler) func(handler *MsgPackHandler) {
|
||||
return func(h *MsgPackHandler) {
|
||||
h.x509SigningHandler = signingHandler
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ func (a *Access) GetCAInfo(name string) (*cainfo.Result, error) {
|
|||
|
||||
return &cainfo.Result{
|
||||
Certificate: certificate.Raw,
|
||||
Profiles: def.Profiles,
|
||||
Profiles: cainfo.MapProfiles(def.Profiles),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -19,35 +19,17 @@ limitations under the License.
|
|||
package signing
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SignerRequest struct {
|
||||
CSR *x509.CertificateRequest
|
||||
SubjectDN pkix.Name
|
||||
Emails []string
|
||||
DNSNames []string
|
||||
Duration time.Duration
|
||||
SignatureAlgorithm x509.SignatureAlgorithm
|
||||
}
|
||||
|
||||
func NewSignerRequest(
|
||||
csr *x509.CertificateRequest,
|
||||
subjectDN pkix.Name,
|
||||
emails, dnsNames []string,
|
||||
duration time.Duration,
|
||||
signatureAlgorithm x509.SignatureAlgorithm,
|
||||
) *SignerRequest {
|
||||
return &SignerRequest{
|
||||
CSR: csr,
|
||||
SubjectDN: subjectDN,
|
||||
Emails: emails,
|
||||
DNSNames: dnsNames,
|
||||
Duration: duration,
|
||||
SignatureAlgorithm: signatureAlgorithm,
|
||||
}
|
||||
CSR *x509.CertificateRequest
|
||||
SubjectDN pkix.Name
|
||||
Emails []string
|
||||
DNSNames []string
|
||||
PreferredHash crypto.Hash
|
||||
}
|
||||
|
||||
type SignerResponse struct {
|
||||
|
|
|
@ -18,12 +18,23 @@ limitations under the License.
|
|||
package signing
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"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
|
||||
|
@ -33,32 +44,6 @@ func NewX509Signing(signer Signer, repo Repository) *X509Signing {
|
|||
return &X509Signing{signer: signer, repo: repo}
|
||||
}
|
||||
|
||||
type RequestSignature struct {
|
||||
rawCSRData []byte
|
||||
subjectCommonName string
|
||||
emails []string
|
||||
dnsNames []string
|
||||
duration time.Duration
|
||||
signatureAlgorithm x509.SignatureAlgorithm
|
||||
}
|
||||
|
||||
func NewRequestSignature(
|
||||
csrBytes []byte,
|
||||
cn string,
|
||||
emails, dnsNames []string,
|
||||
duration time.Duration,
|
||||
signatureAlgorithm x509.SignatureAlgorithm,
|
||||
) *RequestSignature {
|
||||
return &RequestSignature{
|
||||
rawCSRData: csrBytes,
|
||||
subjectCommonName: cn,
|
||||
emails: emails,
|
||||
dnsNames: dnsNames,
|
||||
duration: duration,
|
||||
signatureAlgorithm: signatureAlgorithm,
|
||||
}
|
||||
}
|
||||
|
||||
type CertificateSigned struct {
|
||||
certificate *x509.Certificate
|
||||
}
|
||||
|
@ -67,23 +52,8 @@ func (c CertificateSigned) Certificate() *x509.Certificate {
|
|||
return c.certificate
|
||||
}
|
||||
|
||||
func (x *X509Signing) Sign(signingRequest *RequestSignature) (*CertificateSigned, error) {
|
||||
// validate request content
|
||||
csr, err := x509.ParseCertificateRequest(signingRequest.rawCSRData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse CSR data: %w", err)
|
||||
}
|
||||
|
||||
certificateFromSigner, err := x.signer.SignCertificate(
|
||||
NewSignerRequest(
|
||||
csr,
|
||||
pkix.Name{CommonName: signingRequest.subjectCommonName},
|
||||
signingRequest.emails,
|
||||
signingRequest.dnsNames,
|
||||
signingRequest.duration,
|
||||
signingRequest.signatureAlgorithm,
|
||||
),
|
||||
)
|
||||
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)
|
||||
}
|
||||
|
@ -93,5 +63,255 @@ func (x *X509Signing) Sign(signingRequest *RequestSignature) (*CertificateSigned
|
|||
return nil, fmt.Errorf("could not store certificate: %w", err)
|
||||
}
|
||||
|
||||
return &CertificateSigned{certificate: certificateFromSigner.Certificate}, nil
|
||||
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}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package signing_test
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
|
@ -45,6 +47,83 @@ func randomSerial(t *testing.T) *big.Int {
|
|||
return serial
|
||||
}
|
||||
|
||||
func TestProfileUsage_String(t *testing.T) {
|
||||
okValues := []struct {
|
||||
Name string
|
||||
Usage signing.ProfileUsage
|
||||
}{
|
||||
{"invalid", signing.UsageInvalid},
|
||||
{"ocsp", signing.UsageOCSP},
|
||||
{"client", signing.UsageClient},
|
||||
{"code", signing.UsageCode},
|
||||
{"person", signing.UsagePerson},
|
||||
{"server", signing.UsageServer},
|
||||
{"server-client", signing.UsageServerClient},
|
||||
{"org-client", signing.UsageOrgClient},
|
||||
{"org-code", signing.UsageOrgCode},
|
||||
{"org-email", signing.UsageOrgEmail},
|
||||
{"org-person", signing.UsageOrgPerson},
|
||||
{"org-server", signing.UsageOrgServer},
|
||||
{"org-server-client", signing.UsageOrgServerClient},
|
||||
}
|
||||
|
||||
for _, v := range okValues {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
str := v.Usage.String()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.NotContains(t, str, "unknown profile usage")
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("undefined", func(t *testing.T) {
|
||||
str := signing.ProfileUsage(255).String()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.Contains(t, str, "unknown profile usage")
|
||||
assert.Contains(t, str, "255")
|
||||
})
|
||||
}
|
||||
|
||||
func TestProfileUsage_Description(t *testing.T) {
|
||||
okValues := []struct {
|
||||
Name string
|
||||
Usage signing.ProfileUsage
|
||||
}{
|
||||
{"invalid", signing.UsageInvalid},
|
||||
{"ocsp", signing.UsageOCSP},
|
||||
{"client", signing.UsageClient},
|
||||
{"code", signing.UsageCode},
|
||||
{"person", signing.UsagePerson},
|
||||
{"server", signing.UsageServer},
|
||||
{"server-client", signing.UsageServerClient},
|
||||
{"org-client", signing.UsageOrgClient},
|
||||
{"org-code", signing.UsageOrgCode},
|
||||
{"org-email", signing.UsageOrgEmail},
|
||||
{"org-person", signing.UsageOrgPerson},
|
||||
{"org-server", signing.UsageOrgServer},
|
||||
{"org-server-client", signing.UsageOrgServerClient},
|
||||
}
|
||||
|
||||
for _, v := range okValues {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
str := v.Usage.Description()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.NotContains(t, str, "unknown profile usage")
|
||||
assert.Greater(t, len(str), len(v.Name))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("undefined", func(t *testing.T) {
|
||||
str := signing.ProfileUsage(255).Description()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.Contains(t, str, "unknown profile usage")
|
||||
assert.Contains(t, str, "255")
|
||||
})
|
||||
}
|
||||
|
||||
type testRepo struct {
|
||||
certs map[string]x509.Certificate
|
||||
}
|
||||
|
@ -72,10 +151,10 @@ func (s *testSigner) SignCertificate(request *signing.SignerRequest) (*signing.S
|
|||
SerialNumber: randomSerial(s.t),
|
||||
EmailAddresses: request.Emails,
|
||||
NotBefore: startDate,
|
||||
NotAfter: startDate.Add(request.Duration),
|
||||
NotAfter: startDate.AddDate(1, 0, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection},
|
||||
SignatureAlgorithm: request.SignatureAlgorithm,
|
||||
SignatureAlgorithm: s.determineSignatureAlgorithm(request.PreferredHash),
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, s.certificate, request.CSR.PublicKey, s.key)
|
||||
|
@ -91,6 +170,17 @@ func (s *testSigner) SignCertificate(request *signing.SignerRequest) (*signing.S
|
|||
return newTestSignerResponse(certificate), nil
|
||||
}
|
||||
|
||||
func (s *testSigner) determineSignatureAlgorithm(hash crypto.Hash) x509.SignatureAlgorithm {
|
||||
switch hash {
|
||||
case crypto.SHA512:
|
||||
return x509.ECDSAWithSHA512
|
||||
case crypto.SHA384:
|
||||
return x509.ECDSAWithSHA384
|
||||
default:
|
||||
return x509.ECDSAWithSHA384
|
||||
}
|
||||
}
|
||||
|
||||
func TestSigning(t *testing.T) {
|
||||
testRepository := testRepo{certs: make(map[string]x509.Certificate)}
|
||||
testSigner := newTestSigner(t)
|
||||
|
@ -103,26 +193,20 @@ func TestSigning(t *testing.T) {
|
|||
|
||||
csrTemplate := &x509.CertificateRequest{PublicKey: csrKey.Public()}
|
||||
|
||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, csrKey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
testRequest := &signing.SignerRequest{
|
||||
CSR: csrTemplate,
|
||||
SubjectDN: pkix.Name{CommonName: "Test Subject"},
|
||||
Emails: []string{"test@example.org"},
|
||||
DNSNames: nil,
|
||||
PreferredHash: crypto.SHA384,
|
||||
}
|
||||
|
||||
testRequest := signing.NewRequestSignature(
|
||||
csrBytes,
|
||||
"Test Subject",
|
||||
[]string{"test@example.org"},
|
||||
nil,
|
||||
365*24*time.Hour,
|
||||
x509.SHA384WithRSA,
|
||||
)
|
||||
|
||||
signed, err := s.Sign(testRequest)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cert := signed.Certificate()
|
||||
cert := signed.Certificate
|
||||
assert.Contains(t, testRepository.certs, cert.SerialNumber.Text(16))
|
||||
assert.Equal(t, cert.Subject.CommonName, "Test Subject")
|
||||
assert.Contains(t, cert.EmailAddresses, "test@example.org")
|
||||
|
@ -131,7 +215,7 @@ func TestSigning(t *testing.T) {
|
|||
func newTestSigner(t *testing.T) *testSigner {
|
||||
t.Helper()
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 3072)
|
||||
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate key pair: %v", err)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ limitations under the License.
|
|||
package messages
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
@ -32,6 +33,8 @@ import (
|
|||
// required for msgpackgen
|
||||
_ "github.com/dave/jennifer"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
)
|
||||
|
||||
type CommandCode int8
|
||||
|
@ -126,12 +129,23 @@ func BuildResponseAnnounce(code ResponseCode, commandID string) *ResponseAnnounc
|
|||
return &ResponseAnnounce{Code: code, ID: commandID, Created: time.Now().UTC()}
|
||||
}
|
||||
|
||||
type HealthCommand struct{}
|
||||
|
||||
func (h *HealthCommand) String() string {
|
||||
return ""
|
||||
type CAProfile struct {
|
||||
Name string `msgpack:"name"`
|
||||
Description string `msgpack:"description"`
|
||||
UseFor signing.ProfileUsage `msgpack:"use-for"`
|
||||
}
|
||||
|
||||
func (p CAProfile) String() string {
|
||||
return fmt.Sprintf("profile['%s': '%s']", p.Name, p.UseFor)
|
||||
}
|
||||
|
||||
type CertificateStatus string
|
||||
|
||||
const (
|
||||
CertStatusOk CertificateStatus = "ok"
|
||||
CertStatusFailed CertificateStatus = "failed"
|
||||
)
|
||||
|
||||
type CAInfoCommand struct {
|
||||
Name string `msgpack:"name"`
|
||||
}
|
||||
|
@ -140,6 +154,25 @@ func (r *CAInfoCommand) String() string {
|
|||
return fmt.Sprintf("name=%s", r.Name)
|
||||
}
|
||||
|
||||
type CAInfoResponse struct {
|
||||
Name string `msgpack:"name"`
|
||||
Certificate []byte `msgpack:"certificate"`
|
||||
Signing bool `msgpack:"signing"`
|
||||
Profiles []CAProfile `msgpack:"profiles"`
|
||||
}
|
||||
|
||||
func (i CAInfoResponse) String() string {
|
||||
return fmt.Sprintf("certificate name=%s, signing=%t, profiles=[%s]", i.Name, i.Signing, i.Profiles)
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Message string `msgpack:"message"`
|
||||
}
|
||||
|
||||
func (e *ErrorResponse) String() string {
|
||||
return fmt.Sprintf("message=%s", e.Message)
|
||||
}
|
||||
|
||||
type FetchCRLCommand struct {
|
||||
IssuerID string `msgpack:"issuer_id"`
|
||||
LastKnownID []byte `msgpack:"last_known_id"`
|
||||
|
@ -157,121 +190,64 @@ func (f *FetchCRLCommand) String() string {
|
|||
return builder.String()
|
||||
}
|
||||
|
||||
type ProfileUsage uint8
|
||||
|
||||
const (
|
||||
UsageInvalid ProfileUsage = iota
|
||||
|
||||
UsageOCSP
|
||||
|
||||
UsageClient
|
||||
UsageCode
|
||||
UsagePerson
|
||||
UsageServer
|
||||
UsageServerClient
|
||||
|
||||
UsageOrgClient
|
||||
UsageOrgCode
|
||||
UsageOrgEmail
|
||||
UsageOrgPerson
|
||||
UsageOrgServer
|
||||
UsageOrgServerClient
|
||||
)
|
||||
|
||||
var validProfileUsages = map[string]ProfileUsage{
|
||||
"ocsp": UsageOCSP,
|
||||
|
||||
"client": UsageClient,
|
||||
"code": UsageCode,
|
||||
"person": UsagePerson,
|
||||
"server": UsageServer,
|
||||
"server_client": UsageServerClient,
|
||||
|
||||
"org_client": UsageOrgClient,
|
||||
"org_code": UsageOrgCode,
|
||||
"org_email": UsageOrgEmail,
|
||||
"org_person": UsageOrgPerson,
|
||||
"org_server": UsageOrgServer,
|
||||
"org_server_client": UsageOrgServerClient,
|
||||
type FetchCRLResponse struct {
|
||||
IssuerID string `msgpack:"issuer_id"`
|
||||
IsDelta bool `msgpack:"is_delta"`
|
||||
UnChanged bool `msgpack:"unchanged"`
|
||||
CRLData []byte `msgpack:"crl_data"`
|
||||
CRLNumber []byte `msgpack:"crl_number"`
|
||||
}
|
||||
|
||||
var profileUsageDetails = map[ProfileUsage]struct {
|
||||
Name string
|
||||
Description string
|
||||
}{
|
||||
UsageInvalid: {"invalid", "Invalid certificate profile, not to be used"},
|
||||
func (r *FetchCRLResponse) String() string {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
UsageOCSP: {"ocsp", "OCSP responder signing certificate"},
|
||||
_, _ = fmt.Fprintf(
|
||||
builder,
|
||||
"issuer id=%s, delta=%t, unchanged=%t, CRL number=0x%x",
|
||||
r.IssuerID,
|
||||
r.IsDelta,
|
||||
r.UnChanged,
|
||||
new(big.Int).SetBytes(r.CRLNumber),
|
||||
)
|
||||
|
||||
UsageClient: {"client", "machine TLS client certificate"},
|
||||
UsageCode: {"code", "individual code signing certificate"},
|
||||
UsagePerson: {"person", "person identity certificate"},
|
||||
UsageServer: {"server", "TLS server certificate"},
|
||||
UsageServerClient: {"server_client", "combined TLS server and client certificate"},
|
||||
|
||||
UsageOrgClient: {"org_client", "organization machine TLS client certificate"},
|
||||
UsageOrgCode: {"org_code", "organization code signing certificate"},
|
||||
UsageOrgEmail: {"org_email", "organization email certificate"},
|
||||
UsageOrgPerson: {"org_person", "organizational person identity certificate"},
|
||||
UsageOrgServer: {"org_server", "organization TLS server certificate"},
|
||||
UsageOrgServerClient: {
|
||||
"org_server_client",
|
||||
"combined organization TLS server and client certificate",
|
||||
},
|
||||
}
|
||||
|
||||
func (p ProfileUsage) String() string {
|
||||
name, ok := profileUsageDetails[p]
|
||||
if !ok {
|
||||
return fmt.Sprintf("unknown profile usage %d", p)
|
||||
if r.UnChanged {
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
return name.Name
|
||||
}
|
||||
if r.IsDelta {
|
||||
_, _ = fmt.Fprintf(builder, ", delta CRL data of %d bytes not shown", len(r.CRLData))
|
||||
|
||||
func (p ProfileUsage) Description() string {
|
||||
name, ok := profileUsageDetails[p]
|
||||
if !ok {
|
||||
return fmt.Sprintf("unknown profile usage %d", p)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
return name.Description
|
||||
}
|
||||
revocationList, err := x509.ParseRevocationList(r.CRLData)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(builder, ", could not parse CRL: %s", err.Error())
|
||||
|
||||
func ParseUsage(u string) (ProfileUsage, error) {
|
||||
usage, ok := validProfileUsages[u]
|
||||
if !ok {
|
||||
return UsageInvalid, fmt.Errorf("unsupported profile usage: %s", u)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
_, _ = fmt.Fprintf(
|
||||
builder,
|
||||
", CRL info: issuer=%s, number=0x%x, next update=%s, revoked certificates=%d",
|
||||
revocationList.Issuer,
|
||||
revocationList.Number,
|
||||
revocationList.NextUpdate,
|
||||
len(revocationList.RevokedCertificates),
|
||||
)
|
||||
_, _ = builder.WriteString(", CRL data:\n")
|
||||
_ = pem.Encode(builder, &pem.Block{
|
||||
Type: "CERTIFICATE REVOCATION LIST",
|
||||
Bytes: r.CRLData,
|
||||
})
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
type CAProfile struct {
|
||||
Name string `msgpack:"name"`
|
||||
UseFor ProfileUsage `msgpack:"use-for"`
|
||||
}
|
||||
type HealthCommand struct{}
|
||||
|
||||
func (p CAProfile) String() string {
|
||||
return fmt.Sprintf("profile['%s': '%s']", p.Name, p.UseFor)
|
||||
}
|
||||
|
||||
type CertificateStatus string
|
||||
|
||||
const (
|
||||
CertStatusOk CertificateStatus = "ok"
|
||||
CertStatusFailed CertificateStatus = "failed"
|
||||
)
|
||||
|
||||
type CAInfoResponse struct {
|
||||
Name string `msgpack:"name"`
|
||||
Certificate []byte `msgpack:"certificate"`
|
||||
Signing bool `msgpack:"signing"`
|
||||
Profiles []CAProfile `msgpack:"profiles,omitempty"`
|
||||
}
|
||||
|
||||
func (i CAInfoResponse) String() string {
|
||||
return fmt.Sprintf("certificate name=%s, signing=%t, profiles=[%s]", i.Name, i.Signing, i.Profiles)
|
||||
func (h *HealthCommand) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type HealthInfo struct {
|
||||
|
@ -331,64 +307,56 @@ func (h *HealthResponse) String() string {
|
|||
return builder.String()
|
||||
}
|
||||
|
||||
type FetchCRLResponse struct {
|
||||
IssuerID string `msgpack:"issuer_id"`
|
||||
IsDelta bool `msgpack:"is_delta"`
|
||||
UnChanged bool `msgpack:"unchanged"`
|
||||
CRLData []byte `msgpack:"crl_data"`
|
||||
CRLNumber []byte `msgpack:"crl_number"`
|
||||
type SignCertificateCommand struct {
|
||||
IssuerID string `msgpack:"issuer_id"`
|
||||
ProfileName string `msgpack:"profile_name"`
|
||||
CSRData []byte `msgpack:"csr_data"`
|
||||
CommonName string `msgpack:"cn"`
|
||||
Organization string `msgpack:"o"`
|
||||
OrganizationalUnit string `msgpack:"ou"`
|
||||
Hostnames []string `msgpack:"hostnames"`
|
||||
EmailAddresses []string `msgpack:"email_addresses"`
|
||||
PreferredHash crypto.Hash `msgpack:"preferred_hash"`
|
||||
}
|
||||
|
||||
func (r *FetchCRLResponse) String() string {
|
||||
func (s *SignCertificateCommand) String() string {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
_, _ = fmt.Fprintf(
|
||||
builder,
|
||||
"issuer id=%s, delta=%t, unchanged=%t, CRL number=0x%x",
|
||||
r.IssuerID,
|
||||
r.IsDelta,
|
||||
r.UnChanged,
|
||||
new(big.Int).SetBytes(r.CRLNumber),
|
||||
builder, "issuer_id=%s, profile_name=%s, cn=%s", s.IssuerID, s.ProfileName, s.CommonName,
|
||||
)
|
||||
|
||||
if r.UnChanged {
|
||||
return builder.String()
|
||||
if s.Organization != "" {
|
||||
_, _ = fmt.Fprintf(builder, ", o=%s", s.Organization)
|
||||
}
|
||||
|
||||
if r.IsDelta {
|
||||
_, _ = fmt.Fprintf(builder, ", delta CRL data of %d bytes not shown", len(r.CRLData))
|
||||
|
||||
return builder.String()
|
||||
if s.OrganizationalUnit != "" {
|
||||
_, _ = fmt.Fprintf(builder, ", ou=%s", s.OrganizationalUnit)
|
||||
}
|
||||
|
||||
revocationList, err := x509.ParseRevocationList(r.CRLData)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(builder, ", could not parse CRL: %s", err.Error())
|
||||
if len(s.Hostnames) > 0 {
|
||||
builder.WriteString(", hostnames=[")
|
||||
|
||||
return builder.String()
|
||||
builder.WriteString(strings.Join(s.Hostnames, ", "))
|
||||
|
||||
builder.WriteRune(']')
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(
|
||||
builder,
|
||||
", CRL info: issuer=%s, number=0x%x, next update=%s, revoked certificates=%d",
|
||||
revocationList.Issuer,
|
||||
revocationList.Number,
|
||||
revocationList.NextUpdate,
|
||||
len(revocationList.RevokedCertificates),
|
||||
)
|
||||
_, _ = builder.WriteString(", CRL data:\n")
|
||||
_ = pem.Encode(builder, &pem.Block{
|
||||
Type: "CERTIFICATE REVOCATION LIST",
|
||||
Bytes: r.CRLData,
|
||||
})
|
||||
if len(s.EmailAddresses) > 0 {
|
||||
builder.WriteString(", email_addresses=[")
|
||||
|
||||
builder.WriteString(strings.Join(s.Hostnames, ", "))
|
||||
|
||||
builder.WriteRune(']')
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Message string `msgpack:"message"`
|
||||
type SignCertificateResponse struct {
|
||||
CertificateData []byte `msgpack:"cert_data"`
|
||||
}
|
||||
|
||||
func (e *ErrorResponse) String() string {
|
||||
return fmt.Sprintf("message=%s", e.Message)
|
||||
func (r *SignCertificateResponse) String() string {
|
||||
return fmt.Sprintf("cert_data of %d bytes", len(r.CertificateData))
|
||||
}
|
||||
|
|
|
@ -28,32 +28,10 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
)
|
||||
|
||||
func TestParseUsage(t *testing.T) {
|
||||
okValues := []string{
|
||||
"ocsp", "client", "code", "person", "server", "server_client",
|
||||
"org_client", "org_code", "org_email", "org_person", "org_server", "org_server_client",
|
||||
}
|
||||
|
||||
for _, v := range okValues {
|
||||
t.Run(v, func(t *testing.T) {
|
||||
u, err := messages.ParseUsage(v)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, u, messages.ProfileUsage(0))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
u, err := messages.ParseUsage("foo")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, u, messages.UsageInvalid)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildCommandAnnounce(t *testing.T) {
|
||||
commands := []messages.CommandCode{
|
||||
messages.CmdUndef,
|
||||
|
@ -159,7 +137,7 @@ func TestMsgPackSerialization(t *testing.T) {
|
|||
Profiles: []messages.CAProfile{
|
||||
{
|
||||
Name: "client",
|
||||
UseFor: messages.UsageClient,
|
||||
UseFor: signing.UsageClient,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -33,6 +33,8 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||
)
|
||||
|
||||
func TestCommandCode_String(t *testing.T) {
|
||||
|
@ -153,97 +155,20 @@ func TestFetchCRLCommand_String(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestProfileUsage_String(t *testing.T) {
|
||||
okValues := []struct {
|
||||
Name string
|
||||
Usage ProfileUsage
|
||||
}{
|
||||
{"invalid", UsageInvalid},
|
||||
{"ocsp", UsageOCSP},
|
||||
{"client", UsageClient},
|
||||
{"code", UsageCode},
|
||||
{"person", UsagePerson},
|
||||
{"server", UsageServer},
|
||||
{"server-client", UsageServerClient},
|
||||
{"org-client", UsageOrgClient},
|
||||
{"org-code", UsageOrgCode},
|
||||
{"org-email", UsageOrgEmail},
|
||||
{"org-person", UsageOrgPerson},
|
||||
{"org-server", UsageOrgServer},
|
||||
{"org-server-client", UsageOrgServerClient},
|
||||
}
|
||||
|
||||
for _, v := range okValues {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
str := v.Usage.String()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.NotContains(t, str, "unknown profile usage")
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("undefined", func(t *testing.T) {
|
||||
str := ProfileUsage(255).String()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.Contains(t, str, "unknown profile usage")
|
||||
assert.Contains(t, str, "255")
|
||||
})
|
||||
}
|
||||
|
||||
func TestProfileUsage_Description(t *testing.T) {
|
||||
okValues := []struct {
|
||||
Name string
|
||||
Usage ProfileUsage
|
||||
}{
|
||||
{"invalid", UsageInvalid},
|
||||
{"ocsp", UsageOCSP},
|
||||
{"client", UsageClient},
|
||||
{"code", UsageCode},
|
||||
{"person", UsagePerson},
|
||||
{"server", UsageServer},
|
||||
{"server-client", UsageServerClient},
|
||||
{"org-client", UsageOrgClient},
|
||||
{"org-code", UsageOrgCode},
|
||||
{"org-email", UsageOrgEmail},
|
||||
{"org-person", UsageOrgPerson},
|
||||
{"org-server", UsageOrgServer},
|
||||
{"org-server-client", UsageOrgServerClient},
|
||||
}
|
||||
|
||||
for _, v := range okValues {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
str := v.Usage.Description()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.NotContains(t, str, "unknown profile usage")
|
||||
assert.Greater(t, len(str), len(v.Name))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("undefined", func(t *testing.T) {
|
||||
str := ProfileUsage(255).Description()
|
||||
|
||||
assert.NotEmpty(t, str)
|
||||
assert.Contains(t, str, "unknown profile usage")
|
||||
assert.Contains(t, str, "255")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCAProfile_String(t *testing.T) {
|
||||
profiles := []CAProfile{
|
||||
{Name: "test-client", UseFor: UsageClient},
|
||||
{Name: "test-code", UseFor: UsageCode},
|
||||
{Name: "test-ocsp", UseFor: UsageOCSP},
|
||||
{Name: "test-org-client", UseFor: UsageOrgClient},
|
||||
{Name: "test-org-code", UseFor: UsageOrgCode},
|
||||
{Name: "test-org-email", UseFor: UsageOrgEmail},
|
||||
{Name: "test-org-person", UseFor: UsageOrgPerson},
|
||||
{Name: "test-org-server", UseFor: UsageOrgServer},
|
||||
{Name: "test-org-server-client", UseFor: UsageOrgServerClient},
|
||||
{Name: "test-person", UseFor: UsagePerson},
|
||||
{Name: "test-server", UseFor: UsageServer},
|
||||
{Name: "test-server-client", UseFor: UsageServerClient},
|
||||
{Name: "test-client", UseFor: signing.UsageClient},
|
||||
{Name: "test-code", UseFor: signing.UsageCode},
|
||||
{Name: "test-ocsp", UseFor: signing.UsageOCSP},
|
||||
{Name: "test-org-client", UseFor: signing.UsageOrgClient},
|
||||
{Name: "test-org-code", UseFor: signing.UsageOrgCode},
|
||||
{Name: "test-org-email", UseFor: signing.UsageOrgEmail},
|
||||
{Name: "test-org-person", UseFor: signing.UsageOrgPerson},
|
||||
{Name: "test-org-server", UseFor: signing.UsageOrgServer},
|
||||
{Name: "test-org-server-client", UseFor: signing.UsageOrgServerClient},
|
||||
{Name: "test-person", UseFor: signing.UsagePerson},
|
||||
{Name: "test-server", UseFor: signing.UsageServer},
|
||||
{Name: "test-server-client", UseFor: signing.UsageServerClient},
|
||||
}
|
||||
|
||||
for _, p := range profiles {
|
||||
|
@ -278,7 +203,7 @@ func TestCAInfoResponse_String(t *testing.T) {
|
|||
Signing: true,
|
||||
Profiles: []CAProfile{{
|
||||
Name: "test",
|
||||
UseFor: UsageServer,
|
||||
UseFor: signing.UsageServer,
|
||||
}},
|
||||
Certificate: certBytes,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue