Skip to content

Commit

Permalink
add copy_extensions configuration to local signer to allow (#1082)
Browse files Browse the repository at this point in the history
* Added ability to copy Extensions from CSR

* Added Profile to determine if the Signer should
copy the extensions provided in the CSR across.

* Added config test

Co-authored-by: Nicholas Irving <nirving@darkedges.com>
  • Loading branch information
DarkEdges and nirving authored Mar 26, 2020
1 parent f30ae6a commit 6b49bea
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ profile.out
bin
*.deb
*.rpm
test

1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type SigningProfile struct {
ExpiryString string `json:"expiry"`
BackdateString string `json:"backdate"`
AuthKeyName string `json:"auth_key"`
CopyExtensions bool `json:"copy_extensions"`
PrevAuthKeyName string `json:"prev_auth_key"` // to suppport key rotation
RemoteName string `json:"remote"`
NotBefore time.Time `json:"not_before"`
Expand Down
41 changes: 41 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,25 @@ var validLocalConfigsWithCAConstraint = []string{
}`,
}

var copyExtensionWantedlLocalConfig = `
{
"signing": {
"default": {
"expiry": "8000h",
"copy_extensions": true
}
}
}`

var copyExtensionNotWantedlLocalConfig = `
{
"signing": {
"default": {
"expiry": "8000h"
}
}
}`

func TestInvalidProfile(t *testing.T) {
if invalidProfileConfig.Signing.Profiles["invalid"].validProfile(false) {
t.Fatal("invalid profile accepted as valid")
Expand Down Expand Up @@ -580,3 +599,25 @@ func TestValidCAConstraint(t *testing.T) {
}
}
}

func TestWantCopyExtension(t *testing.T) {
localConfig, err := LoadConfig([]byte(copyExtensionWantedlLocalConfig))
if localConfig.Signing.Default.CopyExtensions != true {
t.Fatal("incorrect TestWantCopyExtension().")
}

if err != nil {
t.Fatal(err)
}
}

func TestDontWantCopyExtension(t *testing.T) {
localConfig, err := LoadConfig([]byte(copyExtensionNotWantedlLocalConfig))
if localConfig.Signing.Default.CopyExtensions != false {
t.Fatal("incorrect TestDontWantCopyExtension().")
}

if err != nil {
t.Fatal(err)
}
}
29 changes: 23 additions & 6 deletions csr/csr.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ type CertificateRequest struct {
KeyRequest *KeyRequest `json:"key,omitempty" yaml:"key,omitempty"`
CA *CAConfig `json:"ca,omitempty" yaml:"ca,omitempty"`
SerialNumber string `json:"serialnumber,omitempty" yaml:"serialnumber,omitempty"`
Extensions []pkix.Extension `json:"extensions,omitempty" yaml:"extensions,omitempty"`
}

// New returns a new, empty CertificateRequest with a
Expand Down Expand Up @@ -382,6 +383,8 @@ func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err erro
}
}

tpl.ExtraExtensions = []pkix.Extension{}

if req.CA != nil {
err = appendCAInfoToCSR(req.CA, &tpl)
if err != nil {
Expand All @@ -390,6 +393,14 @@ func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err erro
}
}

if req.Extensions != nil {
err = appendExtensionsToCSR(req.Extensions, &tpl)
if err != nil {
err = cferr.Wrap(cferr.CSRError, cferr.GenerationFailed, err)
return
}
}

csr, err = x509.CreateCertificateRequest(rand.Reader, &tpl, priv)
if err != nil {
log.Errorf("failed to generate a CSR: %v", err)
Expand Down Expand Up @@ -418,13 +429,19 @@ func appendCAInfoToCSR(reqConf *CAConfig, csr *x509.CertificateRequest) error {
return err
}

csr.ExtraExtensions = []pkix.Extension{
{
Id: asn1.ObjectIdentifier{2, 5, 29, 19},
csr.ExtraExtensions = append(csr.ExtraExtensions, pkix.Extension{
Id: asn1.ObjectIdentifier{2, 5, 29, 19},
Value: val,
Critical: true,
},
}
Critical: true,
})

return nil
}

// appendCAInfoToCSR appends user-defined extension to a CSR
func appendExtensionsToCSR(extensions []pkix.Extension, csr *x509.CertificateRequest) error {
for _, extension := range extensions {
csr.ExtraExtensions = append(csr.ExtraExtensions, extension)
}
return nil
}
35 changes: 34 additions & 1 deletion csr/csr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"io/ioutil"
Expand Down Expand Up @@ -110,12 +111,44 @@ func TestParseRequest(t *testing.T) {
},
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1", "jdoe@example.com", "https://www.cloudflare.com"},
KeyRequest: NewKeyRequest(),
Extensions: []pkix.Extension{
pkix.Extension{
Id: asn1.ObjectIdentifier{1, 2, 3, 4, 5},
Value: []byte("AgEB"),
},
},
}

csrBytes, _, err := ParseRequest(cr)
if err != nil {
t.Fatalf("%v", err)
}

block, _ := pem.Decode(csrBytes)
if block == nil {
t.Fatalf("%v", err)
}

if block.Type != "CERTIFICATE REQUEST" {
t.Fatalf("Incorrect block type: %s", block.Type)
}

_, _, err := ParseRequest(cr)
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
t.Fatalf("%v", err)
}

found := false
for _, ext := range csr.Extensions {
if ext.Id.Equal(asn1.ObjectIdentifier{1, 2, 3, 4, 5}) {
found = true
break
}
}

if !found {
t.Fatalf("CSR did not include Custom Extension")
}
}

// TestParseRequestCA ensures that a valid CA certificate request does not
Expand Down
2 changes: 1 addition & 1 deletion signer/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
cferr.BadRequest, errors.New("not a csr"))
}

csrTemplate, err := signer.ParseCertificateRequest(s, block.Bytes)
csrTemplate, err := signer.ParseCertificateRequest(s, profile, block.Bytes)
if err != nil {
return nil, err
}
Expand Down
9 changes: 8 additions & 1 deletion signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func DefaultSigAlgo(priv crypto.Signer) x509.SignatureAlgorithm {

// ParseCertificateRequest takes an incoming certificate request and
// builds a certificate template from it.
func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certificate, err error) {
func ParseCertificateRequest(s Signer, p *config.SigningProfile, csrBytes []byte) (template *x509.Certificate, err error) {
csrv, err := x509.ParseCertificateRequest(csrBytes)
if err != nil {
err = cferr.Wrap(cferr.CSRError, cferr.ParseFailed, err)
Expand All @@ -193,6 +193,8 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
IPAddresses: csrv.IPAddresses,
EmailAddresses: csrv.EmailAddresses,
URIs: csrv.URIs,
Extensions: csrv.Extensions,
ExtraExtensions: []pkix.Extension{},
}

for _, val := range csrv.Extensions {
Expand All @@ -212,6 +214,11 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
template.IsCA = constraints.IsCA
template.MaxPathLen = constraints.MaxPathLen
template.MaxPathLenZero = template.MaxPathLen == 0
} else {
// If the profile has 'copy_extensions' to true then lets add it
if (p.CopyExtensions) {
template.ExtraExtensions = append(template.ExtraExtensions, val)
}
}
}

Expand Down

0 comments on commit 6b49bea

Please sign in to comment.