diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 49f53860..a5825b9b 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -2,6 +2,7 @@ package provisioner import ( "context" + "fmt" "time" "github.com/pkg/errors" @@ -17,9 +18,11 @@ type SCEP struct { ForceCN bool `json:"forceCN,omitempty"` ChallengePassword string `json:"challenge,omitempty"` Capabilities []string `json:"capabilities,omitempty"` - Options *Options `json:"options,omitempty"` - Claims *Claims `json:"claims,omitempty"` - claimer *Claimer + // MinimumPublicKeyLength is the minimum length for public keys in CSRs + MinimumPublicKeyLength int `json:"minimumPublicKeyLength,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer secretChallengePassword string } @@ -79,6 +82,15 @@ func (s *SCEP) Init(config Config) (err error) { s.secretChallengePassword = s.ChallengePassword s.ChallengePassword = "*** redacted ***" + // Default to 2048 bits minimum public key length (for CSRs) if not set + if s.MinimumPublicKeyLength == 0 { + s.MinimumPublicKeyLength = 2048 + } + + if s.MinimumPublicKeyLength%8 != 0 { + return fmt.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + } + // TODO: add other, SCEP specific, options? return err @@ -94,7 +106,7 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e newForceCNOption(s.ForceCN), profileDefaultDuration(s.claimer.DefaultTLSCertDuration()), // validators - defaultPublicKeyValidator{}, + newPublicKeyMinimumLengthValidator(s.MinimumPublicKeyLength), newValidityValidator(s.claimer.MinTLSCertDuration(), s.claimer.MaxTLSCertDuration()), }, nil } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 3b52d497..20b6f8c0 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/json" + "fmt" "net" "net/url" "reflect" @@ -117,6 +118,36 @@ func (v defaultPublicKeyValidator) Valid(req *x509.CertificateRequest) error { return nil } +// publicKeyMinimumLengthValidator validates the length (in bits) of the public key +// of a certificate request is at least a certain length +type publicKeyMinimumLengthValidator struct { + length int +} + +// newPublicKeyMinimumLengthValidator creates a new publicKeyMinimumLengthValidator +// with the given length as its minimum value +// TODO: change the defaultPublicKeyValidator to have a configurable length instead? +func newPublicKeyMinimumLengthValidator(length int) publicKeyMinimumLengthValidator { + return publicKeyMinimumLengthValidator{ + length: length, + } +} + +// Valid checks that certificate request common name matches the one configured. +func (v publicKeyMinimumLengthValidator) Valid(req *x509.CertificateRequest) error { + switch k := req.PublicKey.(type) { + case *rsa.PublicKey: + minimumLengthInBytes := v.length / 8 + if k.Size() < minimumLengthInBytes { + return fmt.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) + } + case *ecdsa.PublicKey, ed25519.PublicKey: + default: + return errors.Errorf("unrecognized public key of type '%T' in CSR", k) + } + return nil +} + // commonNameValidator validates the common name of a certificate request. type commonNameValidator string