Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
hdecarne committed Dec 9, 2023
1 parent eedb83a commit f0416c9
Show file tree
Hide file tree
Showing 16 changed files with 576 additions and 276 deletions.
20 changes: 10 additions & 10 deletions certs/acme/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,20 @@ func (providerConfig *ProviderConfig) newClientHelper(registrationFile *os.File,
}

func (providerConfig *ProviderConfig) keyTypeFromKeyPairFactory(keyPairFactory keys.KeyPairFactory) (certcrypto.KeyType, error) {
kpfName := keyPairFactory.Name()
switch kpfName {
case "ECDSA P-256":
return certcrypto.EC256, nil
case "ECDSA P-384":
return certcrypto.EC384, nil
case "RSA 2048":
alg := keyPairFactory.Alg()
switch alg {
case keys.RSA2048:
return certcrypto.RSA2048, nil
case "RSA 4096":
case keys.RSA4096:
return certcrypto.RSA4096, nil
case "RSA 8192":
case keys.RSA8192:
return certcrypto.RSA8192, nil
case keys.ECDSA256:
return certcrypto.EC256, nil
case keys.ECDSA384:
return certcrypto.EC384, nil
}
return "", fmt.Errorf("unrecognized key type '%s'", kpfName)
return "", fmt.Errorf("unrecognized key algorithm '%s'", alg.Name())
}

// A DomainConfig defines a domain pattern as well as the challenge types for the matching domains.
Expand Down
14 changes: 7 additions & 7 deletions certs/acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ func TestACMECertificateFactory(t *testing.T) {
require.NoError(t, err)
defer os.RemoveAll(tempDir)
config := loadAndPrepareACMEConfig(t, "./acme/testdata/acme-test.yaml", tempDir)
newCertificate(t, config, "Test1", "RSA 2048")
newCertificate(t, config, "Test1", "RSA 4096")
newCertificate(t, config, "Test1", "RSA 8192")
newCertificate(t, config, "Test2", "ECDSA P-256")
newCertificate(t, config, "Test2", "ECDSA P-384")
newCertificate(t, config, "Test1", keys.RSA2048)
newCertificate(t, config, "Test1", keys.RSA4096)
newCertificate(t, config, "Test1", keys.RSA8192)
newCertificate(t, config, "Test2", keys.ECDSA256)
newCertificate(t, config, "Test2", keys.ECDSA384)
}

func newCertificate(t *testing.T, config *acme.Config, provider string, kpf string) {
func newCertificate(t *testing.T, config *acme.Config, provider string, alg keys.Algorithm) {
host, err := os.Hostname()
require.NoError(t, err)
request, err := config.ResolveCertificateRequest([]string{host}, provider)
require.NotNil(t, request)
require.NoError(t, err)
cf := certs.NewACMECertificateFactory(request, keys.ProviderKeyPairFactory(kpf))
cf := certs.NewACMECertificateFactory(request, alg.NewKeyPairFactory())
require.NotNil(t, cf)
require.Equal(t, fmt.Sprintf("ACME[%s]", provider), cf.Name())
privateKey, cert, err := cf.New()
Expand Down
8 changes: 8 additions & 0 deletions certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ type CertificateRequestFactory interface {
// New creates a new X.509 certificate request.
New() (crypto.PrivateKey, *x509.CertificateRequest, error)
}

// RevocationListFactory interface provides a unified way to create X.509 revocation lists.
type RevocationListFactory interface {
// Name returns the name of this factory.
Name() string
// New creates a new X.509 revocation list.
New() (*x509.RevocationList, error)
}
41 changes: 40 additions & 1 deletion certs/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/rand"
"crypto/x509"
"fmt"
"math/big"

"github.com/hdecarne-github/go-certstore/keys"
"github.com/hdecarne-github/go-log"
Expand All @@ -35,15 +36,18 @@ func (factory *localCertificateFactory) New() (crypto.PrivateKey, *x509.Certific
if err != nil {
return nil, nil, err
}
createTemplate := factory.template
var certificateBytes []byte
if factory.parent != nil {
// parent signed
factory.logger.Info().Msg("creating signed local X.509 certificate...")
createTemplate.SerialNumber = big.NewInt(zerolog.TimestampFunc().UnixMicro())
certificateBytes, err = x509.CreateCertificate(rand.Reader, factory.template, factory.parent, keyPair.Public(), factory.signer)
} else {
// self-signed
factory.logger.Info().Msg("creating self-signed local X.509 certificate...")
certificateBytes, err = x509.CreateCertificate(rand.Reader, factory.template, factory.template, keyPair.Public(), keyPair.Private())
createTemplate.SerialNumber = big.NewInt(1)
certificateBytes, err = x509.CreateCertificate(rand.Reader, createTemplate, factory.template, keyPair.Public(), keyPair.Private())
}
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate (cause: %w)", err)
Expand All @@ -66,3 +70,38 @@ func NewLocalCertificateFactory(template *x509.Certificate, keyPairFactory keys.
logger: &logger,
}
}

type localRevocationListFactory struct {
template *x509.RevocationList
issuer *x509.Certificate
signer crypto.PrivateKey
logger *zerolog.Logger
}

func (factory *localRevocationListFactory) Name() string {
return localCertificateFactoryName
}

func (factory *localRevocationListFactory) New() (*x509.RevocationList, error) {
factory.logger.Info().Msg("creating local X.509 revocation list...")
revocationListBytes, err := x509.CreateRevocationList(rand.Reader, factory.template, factory.issuer, keys.KeyFromPrivate(factory.signer))
if err != nil {
return nil, fmt.Errorf("failed to create revocation list (cause: %w)", err)
}
revocationList, err := x509.ParseRevocationList(revocationListBytes)
if err != nil {
return nil, fmt.Errorf("failed parse revocation list bytes (cause: %w)", err)
}
return revocationList, nil
}

// NewLocalRevocationListFactory creates a new revocation list factory for locally issued certificates.
func NewLocalRevocationListFactory(template *x509.RevocationList, issuer *x509.Certificate, signer crypto.PrivateKey) RevocationListFactory {
logger := log.RootLogger().With().Str("Factory", localCertificateFactoryName).Logger()
return &localRevocationListFactory{
template: template,
issuer: issuer,
signer: signer,
logger: &logger,
}
}
66 changes: 43 additions & 23 deletions certs/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ package certs_test
import (
"crypto/x509"
"crypto/x509/pkix"
"math"
"math/big"
"math/rand"
"testing"
"time"

Expand All @@ -21,36 +19,58 @@ import (

func TestLocalCertificateFactory(t *testing.T) {
// self-signed
now1 := time.Now()
template1 := &x509.Certificate{
SerialNumber: big.NewInt(rand.Int63n(math.MaxInt64)),
Subject: pkix.Name{
Organization: []string{"Test1"},
},
NotBefore: now1,
NotAfter: now1.Add(time.Hour),
}
cf1 := certs.NewLocalCertificateFactory(template1, keys.ProviderKeyPairFactory("ECDSA P-224"), nil, nil)
template1 := newCertificateTemplate("Test1")
template1.IsCA = true
template1.KeyUsage = template1.KeyUsage | x509.KeyUsageCertSign
cf1 := certs.NewLocalCertificateFactory(template1, keys.ECDSA224.NewKeyPairFactory(), nil, nil)
require.NotNil(t, cf1)
require.Equal(t, "Local", cf1.Name())
privateKey1, cert1, err := cf1.New()
require.NoError(t, err)
require.NotNil(t, privateKey1)
require.NotNil(t, cert1)
require.NoError(t, err)
require.Equal(t, big.NewInt(1), cert1.SerialNumber)
require.Equal(t, template1.Subject.Organization, cert1.Subject.Organization)
// signed
now2 := time.Now()
template2 := &x509.Certificate{
SerialNumber: big.NewInt(rand.Int63n(math.MaxInt64)),
Subject: pkix.Name{
Organization: []string{"Test2"},
},
NotBefore: now2,
NotAfter: now2.Add(time.Hour),
}
cf2 := certs.NewLocalCertificateFactory(template2, keys.ProviderKeyPairFactory("ECDSA P-224"), cert1, privateKey1)
template2 := newCertificateTemplate("Test2")
cf2 := certs.NewLocalCertificateFactory(template2, keys.ECDSA224.NewKeyPairFactory(), cert1, privateKey1)
require.NotNil(t, cf2)
privateKey2, cert2, err := cf2.New()
require.NoError(t, err)
require.NotNil(t, privateKey2)
require.NotNil(t, cert2)
require.Equal(t, template2.Subject.Organization, cert2.Subject.Organization)
}
func TestLocalRevocationListFactory(t *testing.T) {
issuerTemplate := newCertificateTemplate("Issuer")
issuerTemplate.IsCA = true
issuerTemplate.KeyUsage = issuerTemplate.KeyUsage | x509.KeyUsageCRLSign
issuerFactory := certs.NewLocalCertificateFactory(issuerTemplate, keys.ECDSA224.NewKeyPairFactory(), nil, nil)
signer, issuer, err := issuerFactory.New()
require.NoError(t, err)
template := newRevocationListEmplate(1)
rlf := certs.NewLocalRevocationListFactory(template, issuer, signer)
revocationList, err := rlf.New()
require.NoError(t, err)
require.NotNil(t, revocationList)
}

func newCertificateTemplate(org string) *x509.Certificate {
now := time.Now()
return &x509.Certificate{
Subject: pkix.Name{
Organization: []string{org},
},
NotBefore: now,
NotAfter: now.Add(time.Hour),
}
}

func newRevocationListEmplate(number int64) *x509.RevocationList {
now := time.Now()
return &x509.RevocationList{
Number: big.NewInt(number),
ThisUpdate: now,
NextUpdate: now.Add(24 * time.Hour),
}
}
2 changes: 1 addition & 1 deletion certs/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestRemoteCertificateRequestFactory(t *testing.T) {
Organization: []string{"TestLocalCertificateFactory"},
},
}
crf := certs.NewRemoteCertificateRequestFactory(template, keys.ProviderKeyPairFactories("ECDSA")[0])
crf := certs.NewRemoteCertificateRequestFactory(template, keys.ECDSA224.NewKeyPairFactory())
require.NotNil(t, crf)
require.Equal(t, "Remote", crf.Name())
privateKey, request, err := crf.New()
Expand Down
52 changes: 33 additions & 19 deletions keys/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
algorithm "crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"io"

"github.com/hdecarne-github/go-log"
"github.com/rs/zerolog"
Expand All @@ -18,9 +19,14 @@ import (
const ecdsaProviderName = "ECDSA"

Check failure on line 19 in keys/ecdsa.go

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

const ecdsaProviderName is unused (U1000)

type ecdsaKeyPair struct {
alg Algorithm
key *algorithm.PrivateKey
}

func (keypair *ecdsaKeyPair) Alg() Algorithm {
return keypair.alg
}

func (keypair *ecdsaKeyPair) Public() crypto.PublicKey {
return keypair.key.Public()
}
Expand All @@ -29,42 +35,50 @@ func (keypair *ecdsaKeyPair) Private() crypto.PrivateKey {
return keypair.key
}

// NewECDSAKeyPair generates a new ECDSA key pair for the given curve.
func NewECDSAKeyPair(curve elliptic.Curve) (KeyPair, error) {
func newECDSAKeyPair(alg Algorithm, curve elliptic.Curve) (KeyPair, error) {
key, err := algorithm.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, err
}
return &ecdsaKeyPair{key: key}, nil
return &ecdsaKeyPair{alg: alg, key: key}, nil
}

type ecdsaKeyPairFactory struct {
alg Algorithm
curve elliptic.Curve
name string
logger *zerolog.Logger
}

func (factory *ecdsaKeyPairFactory) Name() string {
return factory.name
func (factory *ecdsaKeyPairFactory) Alg() Algorithm {
return factory.alg
}

func (factory *ecdsaKeyPairFactory) New() (KeyPair, error) {
factory.logger.Info().Msg("generating new ECSDA key pair...")
return NewECDSAKeyPair(factory.curve)
return newECDSAKeyPair(factory.alg, factory.curve)
}

// NewECDSAKeyPairFactory creates a new ECDSA key pair factory for the given curve.
func NewECDSAKeyPairFactory(curve elliptic.Curve) KeyPairFactory {
name := ecdsaProviderName + " " + curve.Params().Name
logger := log.RootLogger().With().Str("KeyPairFactory", name).Logger()
return &ecdsaKeyPairFactory{curve: curve, name: name, logger: &logger}
func newECDSAKeyPairFactory(alg Algorithm, curve elliptic.Curve) KeyPairFactory {
logger := log.RootLogger().With().Str("Algorithm", alg.Name()).Logger()
return &ecdsaKeyPairFactory{alg: alg, curve: curve, logger: &logger}
}

func ecdsaKeyPairFactories() []KeyPairFactory {
return []KeyPairFactory{
NewECDSAKeyPairFactory(elliptic.P224()),
NewECDSAKeyPairFactory(elliptic.P256()),
NewECDSAKeyPairFactory(elliptic.P384()),
NewECDSAKeyPairFactory(elliptic.P521()),
}
type ecdsaKey struct {
key *algorithm.PrivateKey
}

func (wrapped *ecdsaKey) Public() crypto.PublicKey {
return &wrapped.key.PublicKey
}

func (wrapped *ecdsaKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
return algorithm.SignASN1(rand, wrapped.key, opts.HashFunc().New().Sum(digest))
}

func (wrapped *ecdsaKey) Verify(signature []byte, digest []byte, opts crypto.SignerOpts) bool {
return algorithm.VerifyASN1(&wrapped.key.PublicKey, digest, signature)
}

func wrapECDSAKey(key *algorithm.PrivateKey) Key {
return &ecdsaKey{key: key}
}
28 changes: 0 additions & 28 deletions keys/ecdsa_test.go

This file was deleted.

Loading

0 comments on commit f0416c9

Please sign in to comment.