diff --git a/cmd/scepserver/scepserver.go b/cmd/scepserver/scepserver.go index 97c0170..e5012cb 100644 --- a/cmd/scepserver/scepserver.go +++ b/cmd/scepserver/scepserver.go @@ -4,14 +4,18 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding/asn1" "encoding/pem" + "errors" "flag" "fmt" + "github.com/smallstep/pkcs7" "net/http" "os" "os/signal" "path/filepath" "strconv" + "strings" "syscall" "github.com/micromdm/scep/v2/csrverifier" @@ -29,6 +33,19 @@ var ( version = "unknown" ) +var digestStringOIMap = map[string]asn1.ObjectIdentifier{ + "SHA1": pkcs7.OIDDigestAlgorithmSHA1, + "SHA256": pkcs7.OIDDigestAlgorithmSHA256, + "SHA384": pkcs7.OIDDigestAlgorithmSHA384, + "SHA512": pkcs7.OIDDigestAlgorithmSHA512, + "DSA": pkcs7.OIDDigestAlgorithmDSA, + "DSASHA1": pkcs7.OIDDigestAlgorithmDSASHA1, + "ECDSASHA1": pkcs7.OIDDigestAlgorithmECDSASHA1, + "ECDSASHA256": pkcs7.OIDDigestAlgorithmECDSASHA256, + "ECDSASHA384": pkcs7.OIDDigestAlgorithmECDSASHA384, + "ECDSASHA512": pkcs7.OIDDigestAlgorithmECDSASHA512, +} + func main() { var caCMD = flag.NewFlagSet("ca", flag.ExitOnError) { @@ -40,7 +57,6 @@ func main() { } } - //main flags var ( flVersion = flag.Bool("version", false, "prints version information") flHTTPAddr = flag.String("http-addr", envString("SCEP_HTTP_ADDR", ""), "http listen address. defaults to \":8080\"") @@ -51,6 +67,7 @@ func main() { flClAllowRenewal = flag.String("allowrenew", envString("SCEP_CERT_RENEW", "14"), "do not allow renewal until n days before expiry, set to 0 to always allow") flChallengePassword = flag.String("challenge", envString("SCEP_CHALLENGE_PASSWORD", ""), "enforce a challenge password") flCSRVerifierExec = flag.String("csrverifierexec", envString("SCEP_CSR_VERIFIER_EXEC", ""), "will be passed the CSRs for verification") + flDigestAlgo = flag.String("digest-algo", envString("SCEP_DIGEST_ALGO", "SHA256"), "digest algorithm for pkcs7") flDebug = flag.Bool("debug", envBool("SCEP_LOG_DEBUG"), "enable debug logging") flLogJSON = flag.Bool("log-json", envBool("SCEP_LOG_JSON"), "output JSON logs") flSignServerAttrs = flag.Bool("sign-server-attrs", envBool("SCEP_SIGN_SERVER_ATTRS"), "sign cert attrs for server usage") @@ -83,6 +100,17 @@ func main() { httpAddr = ":" + *flPort } + var digestAlgo asn1.ObjectIdentifier + digestSet := setByUser("digest-algo", "SCEP_DIGEST_ALGO") + if digestSet { + d, err := parseUserDefinedDigestAlgo(*flDigestAlgo) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + digestAlgo = d + } + var logger log.Logger { @@ -154,7 +182,7 @@ func main() { if csrVerifier != nil { signer = csrverifier.Middleware(csrVerifier, signer) } - svc, err = scepserver.NewService(crts[0], key, signer, scepserver.WithLogger(logger)) + svc, err = scepserver.NewService(crts[0], key, signer, scepserver.WithLogger(logger), scepserver.WithDigestAlgo(digestAlgo)) if err != nil { lginfo.Log("err", err) os.Exit(1) @@ -317,3 +345,14 @@ func setByUser(flagName, envName string) bool { _, envSet := os.LookupEnv(envName) return flagSet || envSet } + +func parseUserDefinedDigestAlgo(s string) (asn1.ObjectIdentifier, error) { + if s == "" { + //no value is fine, it will default to SHA256 + return nil, nil + } + if v, ok := digestStringOIMap[strings.ToUpper(s)]; ok { + return v, nil + } + return nil, errors.New("invalid value for digest algo") +} diff --git a/scep/scep.go b/scep/scep.go index 8c81615..8980be4 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -162,13 +162,25 @@ func WithCertsSelector(selector CertsSelector) Option { } } +// WithDigestAlgorithm sets the PKCS #7 digest algorithm. Note that +// in go versions >=1.18 setting the algo as SHA1 will cause x509 +// verify function to failed due to unsecure algo. +// This option is effective when used with NewCSRRequest function. In +// this case, the PKCS #7 digest algo will be set to the specified value +func WithDigestAlgorithm(identifier asn1.ObjectIdentifier) Option { + return func(c *config) { + c.digestAlgorithm = identifier + } +} + // Option specifies custom configuration for SCEP. type Option func(*config) type config struct { - logger log.Logger - caCerts []*x509.Certificate // specified if CA certificates have already been retrieved - certsSelector CertsSelector + logger log.Logger + caCerts []*x509.Certificate // specified if CA certificates have already been retrieved + certsSelector CertsSelector + digestAlgorithm asn1.ObjectIdentifier } // PKIMessage defines the possible SCEP message types @@ -387,7 +399,7 @@ func (msg *PKIMessage) DecryptPKIEnvelope(cert *x509.Certificate, key *rsa.Priva } } -func (msg *PKIMessage) Fail(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, info FailInfo) (*PKIMessage, error) { +func (msg *PKIMessage) Fail(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, info FailInfo, digestAlgo asn1.ObjectIdentifier) (*PKIMessage, error) { config := pkcs7.SignerInfoConfig{ ExtraSignedAttributes: []pkcs7.Attribute{ { @@ -418,6 +430,12 @@ func (msg *PKIMessage) Fail(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, } sd, err := pkcs7.NewSignedData(nil) + if digestAlgo != nil && len(digestAlgo) > 0 { + sd.SetDigestAlgorithm(digestAlgo) + } else { + //default to sha256 since golang 1.18 prohibits sha1 + sd.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256) + } if err != nil { return nil, err } @@ -451,7 +469,7 @@ func (msg *PKIMessage) Fail(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, } // Success returns a new PKIMessage with CertRep data using an already-issued certificate -func (msg *PKIMessage) Success(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, crt *x509.Certificate) (*PKIMessage, error) { +func (msg *PKIMessage) Success(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, crt *x509.Certificate, digestAlgo asn1.ObjectIdentifier) (*PKIMessage, error) { // check if CSRReqMessage has already been decrypted if msg.CSRReqMessage.CSR == nil { if err := msg.DecryptPKIEnvelope(crtAuth, keyAuth); err != nil { @@ -498,6 +516,12 @@ func (msg *PKIMessage) Success(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKe } signedData, err := pkcs7.NewSignedData(e7) + if digestAlgo != nil && len(digestAlgo) > 0 { + signedData.SetDigestAlgorithm(digestAlgo) + } else { + //default to sha256 since golang 1.18 prohibits sha1 + signedData.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256) + } if err != nil { return nil, err } @@ -571,12 +595,19 @@ func NewCSRRequest(csr *x509.CertificateRequest, tmpl *PKIMessage, opts ...Optio } return nil, errors.New("no CA/RA recipients") } + e7, err := pkcs7.Encrypt(derBytes, recipients) if err != nil { return nil, err } signedData, err := pkcs7.NewSignedData(e7) + if conf.digestAlgorithm != nil && len(conf.digestAlgorithm) > 0 { + signedData.SetDigestAlgorithm(conf.digestAlgorithm) + } else { + //default to sha256 since golang 1.18 prohibits sha1 + signedData.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256) + } if err != nil { return nil, err } diff --git a/scep/scep_test.go b/scep/scep_test.go index a9c6838..e4b8e72 100644 --- a/scep/scep_test.go +++ b/scep/scep_test.go @@ -122,7 +122,7 @@ func TestSignCSR(t *testing.T) { if err != nil { t.Fatal(err) } - certRep, err := msg.Success(cacert, cakey, crt) + certRep, err := msg.Success(cacert, cakey, crt, nil) if err != nil { t.Fatal(err) } diff --git a/server/service.go b/server/service.go index b20eb47..037067c 100644 --- a/server/service.go +++ b/server/service.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rsa" "crypto/x509" + "encoding/asn1" "errors" "github.com/micromdm/scep/v2/scep" @@ -51,6 +52,9 @@ type service struct { /// info logging is implemented in the service middleware layer. debugLogger log.Logger + + //disgest algo + digestAlgo asn1.ObjectIdentifier } func (svc *service) GetCACaps(ctx context.Context) ([]byte, error) { @@ -86,11 +90,11 @@ func (svc *service) PKIOperation(ctx context.Context, data []byte) ([]byte, erro } if err != nil { svc.debugLogger.Log("msg", "failed to sign CSR", "err", err) - certRep, err := msg.Fail(svc.crt, svc.key, scep.BadRequest) + certRep, err := msg.Fail(svc.crt, svc.key, scep.BadRequest, svc.digestAlgo) return certRep.Raw, err } - certRep, err := msg.Success(svc.crt, svc.key, crt) + certRep, err := msg.Success(svc.crt, svc.key, crt, svc.digestAlgo) return certRep.Raw, err } @@ -110,6 +114,13 @@ func WithLogger(logger log.Logger) ServiceOption { } } +func WithDigestAlgo(identifier asn1.ObjectIdentifier) ServiceOption { + return func(s *service) error { + s.digestAlgo = identifier + return nil + } +} + // WithAddlCA appends an additional certificate to the slice of CA certs func WithAddlCA(ca *x509.Certificate) ServiceOption { return func(s *service) error {