Compare commits
2 commits
40b3219c7e
...
ad6b987c91
Author | SHA1 | Date | |
---|---|---|---|
ad6b987c91 | |||
9c17a6730f |
15 changed files with 1647 additions and 991 deletions
|
@ -21,10 +21,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
mathRand "math/rand"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
@ -55,6 +60,8 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
||||||
healthInterval = 5 * time.Second
|
healthInterval = 5 * time.Second
|
||||||
crlInterval = 15 * time.Minute
|
crlInterval = 15 * time.Minute
|
||||||
startPause = 3 * time.Second
|
startPause = 3 * time.Second
|
||||||
|
minSignInterval = 5 * time.Second
|
||||||
|
maxSignInterval = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
g.logger.Info("start generating commands")
|
g.logger.Info("start generating commands")
|
||||||
|
@ -63,11 +70,12 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
||||||
|
|
||||||
g.commands <- &protocol.Command{
|
g.commands <- &protocol.Command{
|
||||||
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
||||||
Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"},
|
Command: &messages.FetchCRLCommand{IssuerID: "ecc_person_2022"},
|
||||||
}
|
}
|
||||||
|
|
||||||
healthTimer := time.NewTimer(healthInterval)
|
healthTimer := time.NewTimer(healthInterval)
|
||||||
crlTimer := time.NewTimer(crlInterval)
|
crlTimer := time.NewTimer(crlInterval)
|
||||||
|
signTimer := time.NewTimer(minSignInterval)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -91,10 +99,46 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
||||||
case <-crlTimer.C:
|
case <-crlTimer.C:
|
||||||
g.commands <- &protocol.Command{
|
g.commands <- &protocol.Command{
|
||||||
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
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 {
|
type clientSimulator struct {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"git.cacert.org/cacert-gosigner/internal/hsm"
|
"git.cacert.org/cacert-gosigner/internal/hsm"
|
||||||
"git.cacert.org/cacert-gosigner/internal/serial"
|
"git.cacert.org/cacert-gosigner/internal/serial"
|
||||||
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
||||||
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -94,18 +95,21 @@ func main() {
|
||||||
|
|
||||||
healthHandler := health.New(version, access)
|
healthHandler := health.New(version, access)
|
||||||
|
|
||||||
revokingRepositories, err := configureRepositories(caConfig, logger)
|
revokingRepositories, signingRepositories, err := configureRepositories(caConfig, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Fatal("could not setup revoking repositories")
|
logger.WithError(err).Fatal("could not setup repositories")
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCRLHandler := revoking.NewFetchCRLHandler(revokingRepositories)
|
fetchCRLHandler := revoking.NewFetchCRLHandler(revokingRepositories)
|
||||||
|
|
||||||
|
signX509Handler := signing.NewSignCertificateHandler(signingRepositories)
|
||||||
|
|
||||||
proto, err := handler.New(
|
proto, err := handler.New(
|
||||||
logger,
|
logger,
|
||||||
handler.RegisterHealthHandler(healthHandler),
|
handler.RegisterHealthHandler(healthHandler),
|
||||||
handler.RegisterFetchCRLHandler(fetchCRLHandler),
|
handler.RegisterFetchCRLHandler(fetchCRLHandler),
|
||||||
handler.RegisterCAInfoHandler(access),
|
handler.RegisterCAInfoHandler(access),
|
||||||
|
handler.RegisterCertificateSigningHandler(signX509Handler),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Fatal("could not setup protocol handler")
|
logger.WithError(err).Fatal("could not setup protocol handler")
|
||||||
|
@ -151,26 +155,32 @@ func runSigner(logger *logrus.Logger, serialHandler *serial.Handler) error {
|
||||||
func configureRepositories(
|
func configureRepositories(
|
||||||
caConfig *config.SignerConfig,
|
caConfig *config.SignerConfig,
|
||||||
logger *logrus.Logger,
|
logger *logrus.Logger,
|
||||||
) (map[string]*revoking.X509Revoking, error) {
|
) (map[string]*revoking.X509Revoking, map[string]map[string]*signing.X509Signing, error) {
|
||||||
var err 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() {
|
for _, name := range caConfig.RootCAs() {
|
||||||
result[name], err = buildX509Revoking(caConfig, name, logger)
|
revokers[name], err = buildX509Revoking(caConfig, name, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range caConfig.SubordinateCAs() {
|
for _, name := range caConfig.SubordinateCAs() {
|
||||||
result[name], err = buildX509Revoking(caConfig, name, logger)
|
revokers[name], err = buildX509Revoking(caConfig, name, logger)
|
||||||
if err != nil {
|
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(
|
func buildX509Revoking(
|
||||||
|
@ -197,6 +207,33 @@ func buildX509Revoking(
|
||||||
), nil
|
), 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 {
|
func initializeHSM(caConfig *config.SignerConfig, setupMode, verbose bool, logger *logrus.Logger) *hsm.Access {
|
||||||
opts := make([]hsm.ConfigOption, 0)
|
opts := make([]hsm.ConfigOption, 0)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
||||||
|
|
||||||
package cainfo
|
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 {
|
type Result struct {
|
||||||
Certificate []byte
|
Certificate []byte
|
||||||
|
@ -27,3 +30,17 @@ type Result struct {
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
GetCAInfo(string) (*Result, error)
|
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"
|
"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/openssl"
|
||||||
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
||||||
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
"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 {
|
type CaCertificateEntry struct {
|
||||||
KeyInfo *PrivateKeyInfo
|
KeyInfo *PrivateKeyInfo
|
||||||
CommonName string
|
CommonName string
|
||||||
|
@ -542,7 +574,7 @@ type CaCertificateEntry struct {
|
||||||
KeyPair crypto.Signer
|
KeyPair crypto.Signer
|
||||||
Parent string
|
Parent string
|
||||||
Storage string
|
Storage string
|
||||||
Profiles []messages.CAProfile
|
Profiles []Profile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
@ -557,6 +589,11 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
||||||
Profiles []struct {
|
Profiles []struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
UseFor string `yaml:"use-for"`
|
UseFor string `yaml:"use-for"`
|
||||||
|
Validity struct {
|
||||||
|
Years int `yaml:"years"`
|
||||||
|
Months int `yaml:"months"`
|
||||||
|
Days int `yaml:"days"`
|
||||||
|
} `yaml:"validity"`
|
||||||
} `yaml:"profiles"`
|
} `yaml:"profiles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,17 +635,24 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Profiles != nil {
|
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 {
|
for i, prof := range m.Profiles {
|
||||||
usage, err := messages.ParseUsage(prof.UseFor)
|
usage, err := ParseUsage(prof.UseFor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config error: %w", err)
|
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,
|
Name: prof.Name,
|
||||||
UseFor: usage,
|
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
|
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 {
|
func (c *CaCertificateEntry) IsRoot() bool {
|
||||||
return c.Parent == ""
|
return c.Parent == ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"git.cacert.org/cacert-gosigner/internal/config"
|
"git.cacert.org/cacert-gosigner/internal/config"
|
||||||
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestCurve struct {
|
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.ErrorContains(t, err, "could not find storage definition with label")
|
||||||
assert.Nil(t, keyStorage)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -30,6 +32,7 @@ import (
|
||||||
"git.cacert.org/cacert-gosigner/internal/cainfo"
|
"git.cacert.org/cacert-gosigner/internal/cainfo"
|
||||||
"git.cacert.org/cacert-gosigner/internal/health"
|
"git.cacert.org/cacert-gosigner/internal/health"
|
||||||
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
"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/messages"
|
||||||
"git.cacert.org/cacert-gosigner/pkg/protocol"
|
"git.cacert.org/cacert-gosigner/pkg/protocol"
|
||||||
)
|
)
|
||||||
|
@ -44,6 +47,7 @@ type MsgPackHandler struct {
|
||||||
healthHandler *health.Handler
|
healthHandler *health.Handler
|
||||||
certificateAuthorityInfoHandler cainfo.Handler
|
certificateAuthorityInfoHandler cainfo.Handler
|
||||||
fetchCRLHandler *revoking.FetchCRLHandler
|
fetchCRLHandler *revoking.FetchCRLHandler
|
||||||
|
x509SigningHandler signing.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MsgPackHandler) CommandAnnounce(ctx context.Context, frames <-chan []byte) (*protocol.Command, error) {
|
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
|
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) {
|
func (m *MsgPackHandler) handleCommand(command *protocol.Command) (*protocol.Response, error) {
|
||||||
var (
|
var (
|
||||||
responseCode messages.ResponseCode
|
responseCode messages.ResponseCode
|
||||||
|
@ -202,6 +218,13 @@ func (m *MsgPackHandler) handleCommand(command *protocol.Command) (*protocol.Res
|
||||||
}
|
}
|
||||||
|
|
||||||
responseCode, responseData = messages.RespFetchCRL, response
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unhandled command %s", command.Announce)
|
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
|
command.Command = fetchCRLCommand
|
||||||
|
case messages.CmdSignCertificate:
|
||||||
|
signCertificateCommand, err := m.parseSignCertificateCommand(frame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
command.Command = signCertificateCommand
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unhandled command code %s", command.Announce.Code)
|
return fmt.Errorf("unhandled command code %s", command.Announce.Code)
|
||||||
}
|
}
|
||||||
|
@ -315,6 +345,43 @@ func (m *MsgPackHandler) handleFetchCRLCommand(command *messages.FetchCRLCommand
|
||||||
return response, nil
|
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) {
|
func New(logger *logrus.Logger, handlers ...RegisterHandler) (protocol.ServerHandler, error) {
|
||||||
messages.RegisterGeneratedResolver()
|
messages.RegisterGeneratedResolver()
|
||||||
|
|
||||||
|
@ -348,3 +415,9 @@ func RegisterCAInfoHandler(caInfoHandler cainfo.Handler) func(handler *MsgPackHa
|
||||||
h.certificateAuthorityInfoHandler = caInfoHandler
|
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{
|
return &cainfo.Result{
|
||||||
Certificate: certificate.Raw,
|
Certificate: certificate.Raw,
|
||||||
Profiles: def.Profiles,
|
Profiles: cainfo.MapProfiles(def.Profiles),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,12 +69,14 @@ type indexEntry struct {
|
||||||
func (ie *indexEntry) String() string {
|
func (ie *indexEntry) String() string {
|
||||||
var revoked, fileName string
|
var revoked, fileName string
|
||||||
|
|
||||||
if ie.revokedAt != nil {
|
if ie.revokedAt != nil && !ie.revokedAt.IsZero() {
|
||||||
revoked = fmt.Sprintf("%s,%s", ie.revokedAt.Format(TimeSpec), ie.revocationReason)
|
revoked = fmt.Sprintf("%s,%s", ie.revokedAt.Format(TimeSpec), ie.revocationReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ie.fileName == "" {
|
if ie.fileName == "" {
|
||||||
fileName = "unknown"
|
fileName = "unknown"
|
||||||
|
} else {
|
||||||
|
fileName = ie.fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join([]string{
|
return strings.Join([]string{
|
||||||
|
|
|
@ -19,9 +19,9 @@ limitations under the License.
|
||||||
package signing
|
package signing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SignerRequest struct {
|
type SignerRequest struct {
|
||||||
|
@ -29,25 +29,7 @@ type SignerRequest struct {
|
||||||
SubjectDN pkix.Name
|
SubjectDN pkix.Name
|
||||||
Emails []string
|
Emails []string
|
||||||
DNSNames []string
|
DNSNames []string
|
||||||
Duration time.Duration
|
PreferredHash crypto.Hash
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignerResponse struct {
|
type SignerResponse struct {
|
||||||
|
|
|
@ -18,12 +18,23 @@ limitations under the License.
|
||||||
package signing
|
package signing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"encoding/asn1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"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 {
|
type X509Signing struct {
|
||||||
signer Signer
|
signer Signer
|
||||||
repo Repository
|
repo Repository
|
||||||
|
@ -33,32 +44,6 @@ func NewX509Signing(signer Signer, repo Repository) *X509Signing {
|
||||||
return &X509Signing{signer: signer, repo: repo}
|
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 {
|
type CertificateSigned struct {
|
||||||
certificate *x509.Certificate
|
certificate *x509.Certificate
|
||||||
}
|
}
|
||||||
|
@ -67,23 +52,8 @@ func (c CertificateSigned) Certificate() *x509.Certificate {
|
||||||
return c.certificate
|
return c.certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *X509Signing) Sign(signingRequest *RequestSignature) (*CertificateSigned, error) {
|
func (x *X509Signing) Sign(signingRequest *SignerRequest) (*SignerResponse, error) {
|
||||||
// validate request content
|
certificateFromSigner, err := x.signer.SignCertificate(signingRequest)
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not sign certificate: %w", err)
|
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 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 (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -45,6 +47,83 @@ func randomSerial(t *testing.T) *big.Int {
|
||||||
return serial
|
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 {
|
type testRepo struct {
|
||||||
certs map[string]x509.Certificate
|
certs map[string]x509.Certificate
|
||||||
}
|
}
|
||||||
|
@ -72,10 +151,10 @@ func (s *testSigner) SignCertificate(request *signing.SignerRequest) (*signing.S
|
||||||
SerialNumber: randomSerial(s.t),
|
SerialNumber: randomSerial(s.t),
|
||||||
EmailAddresses: request.Emails,
|
EmailAddresses: request.Emails,
|
||||||
NotBefore: startDate,
|
NotBefore: startDate,
|
||||||
NotAfter: startDate.Add(request.Duration),
|
NotAfter: startDate.AddDate(1, 0, 0),
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection},
|
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)
|
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
|
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) {
|
func TestSigning(t *testing.T) {
|
||||||
testRepository := testRepo{certs: make(map[string]x509.Certificate)}
|
testRepository := testRepo{certs: make(map[string]x509.Certificate)}
|
||||||
testSigner := newTestSigner(t)
|
testSigner := newTestSigner(t)
|
||||||
|
@ -103,26 +193,20 @@ func TestSigning(t *testing.T) {
|
||||||
|
|
||||||
csrTemplate := &x509.CertificateRequest{PublicKey: csrKey.Public()}
|
csrTemplate := &x509.CertificateRequest{PublicKey: csrKey.Public()}
|
||||||
|
|
||||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, csrKey)
|
testRequest := &signing.SignerRequest{
|
||||||
if err != nil {
|
CSR: csrTemplate,
|
||||||
t.Error(err)
|
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)
|
signed, err := s.Sign(testRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := signed.Certificate()
|
cert := signed.Certificate
|
||||||
assert.Contains(t, testRepository.certs, cert.SerialNumber.Text(16))
|
assert.Contains(t, testRepository.certs, cert.SerialNumber.Text(16))
|
||||||
assert.Equal(t, cert.Subject.CommonName, "Test Subject")
|
assert.Equal(t, cert.Subject.CommonName, "Test Subject")
|
||||||
assert.Contains(t, cert.EmailAddresses, "test@example.org")
|
assert.Contains(t, cert.EmailAddresses, "test@example.org")
|
||||||
|
@ -131,7 +215,7 @@ func TestSigning(t *testing.T) {
|
||||||
func newTestSigner(t *testing.T) *testSigner {
|
func newTestSigner(t *testing.T) *testSigner {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
caKey, err := rsa.GenerateKey(rand.Reader, 3072)
|
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not generate key pair: %v", err)
|
t.Fatalf("could not generate key pair: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ limitations under the License.
|
||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -32,6 +33,8 @@ import (
|
||||||
// required for msgpackgen
|
// required for msgpackgen
|
||||||
_ "github.com/dave/jennifer"
|
_ "github.com/dave/jennifer"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandCode int8
|
type CommandCode int8
|
||||||
|
@ -126,12 +129,23 @@ func BuildResponseAnnounce(code ResponseCode, commandID string) *ResponseAnnounc
|
||||||
return &ResponseAnnounce{Code: code, ID: commandID, Created: time.Now().UTC()}
|
return &ResponseAnnounce{Code: code, ID: commandID, Created: time.Now().UTC()}
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthCommand struct{}
|
type CAProfile struct {
|
||||||
|
Name string `msgpack:"name"`
|
||||||
func (h *HealthCommand) String() string {
|
Description string `msgpack:"description"`
|
||||||
return ""
|
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 {
|
type CAInfoCommand struct {
|
||||||
Name string `msgpack:"name"`
|
Name string `msgpack:"name"`
|
||||||
}
|
}
|
||||||
|
@ -140,6 +154,25 @@ func (r *CAInfoCommand) String() string {
|
||||||
return fmt.Sprintf("name=%s", r.Name)
|
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 {
|
type FetchCRLCommand struct {
|
||||||
IssuerID string `msgpack:"issuer_id"`
|
IssuerID string `msgpack:"issuer_id"`
|
||||||
LastKnownID []byte `msgpack:"last_known_id"`
|
LastKnownID []byte `msgpack:"last_known_id"`
|
||||||
|
@ -157,121 +190,64 @@ func (f *FetchCRLCommand) String() string {
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileUsage uint8
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
func (r *FetchCRLResponse) String() string {
|
||||||
UsageInvalid ProfileUsage = iota
|
builder := &strings.Builder{}
|
||||||
|
|
||||||
UsageOCSP
|
_, _ = fmt.Fprintf(
|
||||||
|
builder,
|
||||||
UsageClient
|
"issuer id=%s, delta=%t, unchanged=%t, CRL number=0x%x",
|
||||||
UsageCode
|
r.IssuerID,
|
||||||
UsagePerson
|
r.IsDelta,
|
||||||
UsageServer
|
r.UnChanged,
|
||||||
UsageServerClient
|
new(big.Int).SetBytes(r.CRLNumber),
|
||||||
|
|
||||||
UsageOrgClient
|
|
||||||
UsageOrgCode
|
|
||||||
UsageOrgEmail
|
|
||||||
UsageOrgPerson
|
|
||||||
UsageOrgServer
|
|
||||||
UsageOrgServerClient
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var validProfileUsages = map[string]ProfileUsage{
|
if r.UnChanged {
|
||||||
"ocsp": UsageOCSP,
|
return builder.String()
|
||||||
|
|
||||||
"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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var profileUsageDetails = map[ProfileUsage]struct {
|
if r.IsDelta {
|
||||||
Name string
|
_, _ = fmt.Fprintf(builder, ", delta CRL data of %d bytes not shown", len(r.CRLData))
|
||||||
Description string
|
|
||||||
}{
|
|
||||||
UsageInvalid: {"invalid", "Invalid certificate profile, not to be used"},
|
|
||||||
|
|
||||||
UsageOCSP: {"ocsp", "OCSP responder signing certificate"},
|
return builder.String()
|
||||||
|
|
||||||
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 {
|
revocationList, err := x509.ParseRevocationList(r.CRLData)
|
||||||
name, ok := profileUsageDetails[p]
|
if err != nil {
|
||||||
if !ok {
|
_, _ = fmt.Fprintf(builder, ", could not parse CRL: %s", err.Error())
|
||||||
return fmt.Sprintf("unknown profile usage %d", p)
|
|
||||||
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return name.Name
|
_, _ = fmt.Fprintf(
|
||||||
}
|
builder,
|
||||||
|
", CRL info: issuer=%s, number=0x%x, next update=%s, revoked certificates=%d",
|
||||||
func (p ProfileUsage) Description() string {
|
revocationList.Issuer,
|
||||||
name, ok := profileUsageDetails[p]
|
revocationList.Number,
|
||||||
if !ok {
|
revocationList.NextUpdate,
|
||||||
return fmt.Sprintf("unknown profile usage %d", p)
|
len(revocationList.RevokedCertificates),
|
||||||
}
|
|
||||||
|
|
||||||
return name.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseUsage(u string) (ProfileUsage, error) {
|
|
||||||
usage, ok := validProfileUsages[u]
|
|
||||||
if !ok {
|
|
||||||
return UsageInvalid, fmt.Errorf("unsupported profile usage: %s", u)
|
|
||||||
}
|
|
||||||
|
|
||||||
return usage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CAProfile struct {
|
|
||||||
Name string `msgpack:"name"`
|
|
||||||
UseFor 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"
|
|
||||||
)
|
)
|
||||||
|
_, _ = builder.WriteString(", CRL data:\n")
|
||||||
|
_ = pem.Encode(builder, &pem.Block{
|
||||||
|
Type: "CERTIFICATE REVOCATION LIST",
|
||||||
|
Bytes: r.CRLData,
|
||||||
|
})
|
||||||
|
|
||||||
type CAInfoResponse struct {
|
return builder.String()
|
||||||
Name string `msgpack:"name"`
|
|
||||||
Certificate []byte `msgpack:"certificate"`
|
|
||||||
Signing bool `msgpack:"signing"`
|
|
||||||
Profiles []CAProfile `msgpack:"profiles,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i CAInfoResponse) String() string {
|
type HealthCommand struct{}
|
||||||
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 {
|
type HealthInfo struct {
|
||||||
|
@ -331,64 +307,56 @@ func (h *HealthResponse) String() string {
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type FetchCRLResponse struct {
|
type SignCertificateCommand struct {
|
||||||
IssuerID string `msgpack:"issuer_id"`
|
IssuerID string `msgpack:"issuer_id"`
|
||||||
IsDelta bool `msgpack:"is_delta"`
|
ProfileName string `msgpack:"profile_name"`
|
||||||
UnChanged bool `msgpack:"unchanged"`
|
CSRData []byte `msgpack:"csr_data"`
|
||||||
CRLData []byte `msgpack:"crl_data"`
|
CommonName string `msgpack:"cn"`
|
||||||
CRLNumber []byte `msgpack:"crl_number"`
|
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{}
|
builder := &strings.Builder{}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(
|
_, _ = fmt.Fprintf(
|
||||||
builder,
|
builder, "issuer_id=%s, profile_name=%s, cn=%s", s.IssuerID, s.ProfileName, s.CommonName,
|
||||||
"issuer id=%s, delta=%t, unchanged=%t, CRL number=0x%x",
|
|
||||||
r.IssuerID,
|
|
||||||
r.IsDelta,
|
|
||||||
r.UnChanged,
|
|
||||||
new(big.Int).SetBytes(r.CRLNumber),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if r.UnChanged {
|
if s.Organization != "" {
|
||||||
return builder.String()
|
_, _ = fmt.Fprintf(builder, ", o=%s", s.Organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.IsDelta {
|
if s.OrganizationalUnit != "" {
|
||||||
_, _ = fmt.Fprintf(builder, ", delta CRL data of %d bytes not shown", len(r.CRLData))
|
_, _ = fmt.Fprintf(builder, ", ou=%s", s.OrganizationalUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.Hostnames) > 0 {
|
||||||
|
builder.WriteString(", hostnames=[")
|
||||||
|
|
||||||
|
builder.WriteString(strings.Join(s.Hostnames, ", "))
|
||||||
|
|
||||||
|
builder.WriteRune(']')
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.EmailAddresses) > 0 {
|
||||||
|
builder.WriteString(", email_addresses=[")
|
||||||
|
|
||||||
|
builder.WriteString(strings.Join(s.Hostnames, ", "))
|
||||||
|
|
||||||
|
builder.WriteRune(']')
|
||||||
|
}
|
||||||
|
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
revocationList, err := x509.ParseRevocationList(r.CRLData)
|
type SignCertificateResponse struct {
|
||||||
if err != nil {
|
CertificateData []byte `msgpack:"cert_data"`
|
||||||
_, _ = fmt.Fprintf(builder, ", could not parse CRL: %s", err.Error())
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(
|
func (r *SignCertificateResponse) String() string {
|
||||||
builder,
|
return fmt.Sprintf("cert_data of %d bytes", len(r.CertificateData))
|
||||||
", 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 ErrorResponse struct {
|
|
||||||
Message string `msgpack:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrorResponse) String() string {
|
|
||||||
return fmt.Sprintf("message=%s", e.Message)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,32 +28,10 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
"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) {
|
func TestBuildCommandAnnounce(t *testing.T) {
|
||||||
commands := []messages.CommandCode{
|
commands := []messages.CommandCode{
|
||||||
messages.CmdUndef,
|
messages.CmdUndef,
|
||||||
|
@ -159,7 +137,7 @@ func TestMsgPackSerialization(t *testing.T) {
|
||||||
Profiles: []messages.CAProfile{
|
Profiles: []messages.CAProfile{
|
||||||
{
|
{
|
||||||
Name: "client",
|
Name: "client",
|
||||||
UseFor: messages.UsageClient,
|
UseFor: signing.UsageClient,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,6 +33,8 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCommandCode_String(t *testing.T) {
|
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) {
|
func TestCAProfile_String(t *testing.T) {
|
||||||
profiles := []CAProfile{
|
profiles := []CAProfile{
|
||||||
{Name: "test-client", UseFor: UsageClient},
|
{Name: "test-client", UseFor: signing.UsageClient},
|
||||||
{Name: "test-code", UseFor: UsageCode},
|
{Name: "test-code", UseFor: signing.UsageCode},
|
||||||
{Name: "test-ocsp", UseFor: UsageOCSP},
|
{Name: "test-ocsp", UseFor: signing.UsageOCSP},
|
||||||
{Name: "test-org-client", UseFor: UsageOrgClient},
|
{Name: "test-org-client", UseFor: signing.UsageOrgClient},
|
||||||
{Name: "test-org-code", UseFor: UsageOrgCode},
|
{Name: "test-org-code", UseFor: signing.UsageOrgCode},
|
||||||
{Name: "test-org-email", UseFor: UsageOrgEmail},
|
{Name: "test-org-email", UseFor: signing.UsageOrgEmail},
|
||||||
{Name: "test-org-person", UseFor: UsageOrgPerson},
|
{Name: "test-org-person", UseFor: signing.UsageOrgPerson},
|
||||||
{Name: "test-org-server", UseFor: UsageOrgServer},
|
{Name: "test-org-server", UseFor: signing.UsageOrgServer},
|
||||||
{Name: "test-org-server-client", UseFor: UsageOrgServerClient},
|
{Name: "test-org-server-client", UseFor: signing.UsageOrgServerClient},
|
||||||
{Name: "test-person", UseFor: UsagePerson},
|
{Name: "test-person", UseFor: signing.UsagePerson},
|
||||||
{Name: "test-server", UseFor: UsageServer},
|
{Name: "test-server", UseFor: signing.UsageServer},
|
||||||
{Name: "test-server-client", UseFor: UsageServerClient},
|
{Name: "test-server-client", UseFor: signing.UsageServerClient},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range profiles {
|
for _, p := range profiles {
|
||||||
|
@ -278,7 +203,7 @@ func TestCAInfoResponse_String(t *testing.T) {
|
||||||
Signing: true,
|
Signing: true,
|
||||||
Profiles: []CAProfile{{
|
Profiles: []CAProfile{{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
UseFor: UsageServer,
|
UseFor: signing.UsageServer,
|
||||||
}},
|
}},
|
||||||
Certificate: certBytes,
|
Certificate: certBytes,
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue