From 37da03cc180cd99037bfda8d85d1bb6d59a6692c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 18 May 2023 14:36:59 +0200 Subject: [PATCH 1/2] Add `-key-encipherment-selector` and use CA certs for verification When a SCEP server returns multiple certificates, it is possible that not all certificates can or should be used for encryption. There already was a `KeyEnciphermentsSelector`, but that wasn't readily usable with the provided SCEP client. A new flag was added to enable this selector: `-key-encipherment-selector`. It will filter out certificates that aren't marked as being usable for key or data encryption. When verifying the `PKIMessage` from the CA only the `Recipients` in the outgoing message were being looked through when selecting a certificate to verify the signature. This commit changes that by including all certificates returned by the CA when the client performs the `GetCACerts` operation. --- cmd/scepclient/scepclient.go | 75 +++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/cmd/scepclient/scepclient.go b/cmd/scepclient/scepclient.go index 3cfe22d..96762be 100644 --- a/cmd/scepclient/scepclient.go +++ b/cmd/scepclient/scepclient.go @@ -115,15 +115,15 @@ func run(cfg runCfg) error { if err != nil { return err } - var certs []*x509.Certificate + var caCerts []*x509.Certificate { if certNum > 1 { - certs, err = scep.CACerts(resp) + caCerts, err = scep.CACerts(resp) if err != nil { return err } } else { - certs, err = x509.ParseCertificates(resp) + caCerts, err = x509.ParseCertificates(resp) if err != nil { return err } @@ -131,7 +131,7 @@ func run(cfg runCfg) error { } if cfg.debug { - logCerts(level.Debug(logger), certs) + logCerts(level.Debug(logger), caCerts) } var signerCert *x509.Certificate @@ -155,7 +155,7 @@ func run(cfg runCfg) error { tmpl := &scep.PKIMessage{ MessageType: msgType, - Recipients: certs, + Recipients: caCerts, SignerKey: key, SignerCert: signerCert, } @@ -182,7 +182,7 @@ func run(cfg runCfg) error { return errors.Wrapf(err, "PKIOperation for %s", msgType) } - respMsg, err = scep.ParsePKIMessage(respBytes, scep.WithLogger(logger), scep.WithCACerts(msg.Recipients)) + respMsg, err = scep.ParsePKIMessage(respBytes, scep.WithLogger(logger), scep.WithCACerts(caCerts)) if err != nil { return errors.Wrapf(err, "parsing pkiMessage response %s", msgType) } @@ -253,7 +253,36 @@ func validateFingerprint(fingerprint string) (hash []byte, err error) { return } -func validateFlags(keyPath, serverURL string) error { +var ( + flVersion = flag.Bool("version", false, "prints version information") + flServerURL = flag.String("server-url", "", "SCEP server url") + flChallengePassword = flag.String("challenge", "", "enforce a challenge password") + flPKeyPath = flag.String("private-key", "", "private key path, if there is no key, scepclient will create one") + flCertPath = flag.String("certificate", "", "certificate path, if there is no key, scepclient will create one") + flKeySize = flag.Int("keySize", 2048, "rsa key size") + flOrg = flag.String("organization", "scep-client", "organization for cert") + flCName = flag.String("cn", "scepclient", "common name for certificate") + flOU = flag.String("ou", "MDM", "organizational unit for certificate") + flLoc = flag.String("locality", "", "locality for certificate") + flProvince = flag.String("province", "", "province for certificate") + flCountry = flag.String("country", "US", "country code in certificate") + flCACertMessage = flag.String("cacert-message", "", "message sent with GetCACert operation") + flDNSName = flag.String("dnsname", "", "DNS name to be included in the certificate (SAN)") + + // in case of multiple certificate authorities, we need to figure out who the recipient of the encrypted + // data is. + flCAFingerprint = flag.String("ca-fingerprint", "", "SHA-256 digest of CA certificate for NDES server. Note: Changed from MD5.") + flKeyEnciphermentSelector = flag.Bool("key-encipherment-selector", false, "Filter CA certificates by key encipherment usage") + + flDebugLogging = flag.Bool("debug", false, "enable debug logging") + flLogJSON = flag.Bool("log-json", false, "use JSON for log output") +) + +func validateFlags() error { + keyPath := *flPKeyPath + serverURL := *flServerURL + caFingerprint := *flCAFingerprint + useKeyEnciphermentSelector := *flKeyEnciphermentSelector if keyPath == "" { return errors.New("must specify private key path") } @@ -264,33 +293,14 @@ func validateFlags(keyPath, serverURL string) error { if err != nil { return fmt.Errorf("invalid server-url flag parameter %s", err) } + if caFingerprint != "" && useKeyEnciphermentSelector { + return errors.New("ca-fingerprint and key-encipherment-selector can't be used at the same time") + } return nil } func main() { - var ( - flVersion = flag.Bool("version", false, "prints version information") - flServerURL = flag.String("server-url", "", "SCEP server url") - flChallengePassword = flag.String("challenge", "", "enforce a challenge password") - flPKeyPath = flag.String("private-key", "", "private key path, if there is no key, scepclient will create one") - flCertPath = flag.String("certificate", "", "certificate path, if there is no key, scepclient will create one") - flKeySize = flag.Int("keySize", 2048, "rsa key size") - flOrg = flag.String("organization", "scep-client", "organization for cert") - flCName = flag.String("cn", "scepclient", "common name for certificate") - flOU = flag.String("ou", "MDM", "organizational unit for certificate") - flLoc = flag.String("locality", "", "locality for certificate") - flProvince = flag.String("province", "", "province for certificate") - flCountry = flag.String("country", "US", "country code in certificate") - flCACertMessage = flag.String("cacert-message", "", "message sent with GetCACert operation") - flDNSName = flag.String("dnsname", "", "DNS name to be included in the certificate (SAN)") - - // in case of multiple certificate authorities, we need to figure out who the recipient of the encrypted - // data is. - flCAFingerprint = flag.String("ca-fingerprint", "", "SHA-256 digest of CA certificate for NDES server. Note: Changed from MD5.") - - flDebugLogging = flag.Bool("debug", false, "enable debug logging") - flLogJSON = flag.Bool("log-json", false, "use JSON for log output") - ) + flag.Parse() // print version information @@ -299,7 +309,7 @@ func main() { os.Exit(0) } - if err := validateFlags(*flPKeyPath, *flServerURL); err != nil { + if err := validateFlags(); err != nil { fmt.Println(err) os.Exit(1) } @@ -313,6 +323,9 @@ func main() { } caCertsSelector = scep.FingerprintCertsSelector(fingerprintHashType, hash) } + if *flKeyEnciphermentSelector { + caCertsSelector = scep.EnciphermentCertsSelector() + } dir := filepath.Dir(*flPKeyPath) csrPath := dir + "/csr.pem" From 197f8b7cb785407b71413f675466eef4d9e76a56 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 1 Dec 2023 23:44:25 +0100 Subject: [PATCH 2/2] Move flags back from global to within `main` again --- cmd/scepclient/scepclient.go | 64 +++++++++++++++++------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/cmd/scepclient/scepclient.go b/cmd/scepclient/scepclient.go index 96762be..d8ac7ba 100644 --- a/cmd/scepclient/scepclient.go +++ b/cmd/scepclient/scepclient.go @@ -253,36 +253,7 @@ func validateFingerprint(fingerprint string) (hash []byte, err error) { return } -var ( - flVersion = flag.Bool("version", false, "prints version information") - flServerURL = flag.String("server-url", "", "SCEP server url") - flChallengePassword = flag.String("challenge", "", "enforce a challenge password") - flPKeyPath = flag.String("private-key", "", "private key path, if there is no key, scepclient will create one") - flCertPath = flag.String("certificate", "", "certificate path, if there is no key, scepclient will create one") - flKeySize = flag.Int("keySize", 2048, "rsa key size") - flOrg = flag.String("organization", "scep-client", "organization for cert") - flCName = flag.String("cn", "scepclient", "common name for certificate") - flOU = flag.String("ou", "MDM", "organizational unit for certificate") - flLoc = flag.String("locality", "", "locality for certificate") - flProvince = flag.String("province", "", "province for certificate") - flCountry = flag.String("country", "US", "country code in certificate") - flCACertMessage = flag.String("cacert-message", "", "message sent with GetCACert operation") - flDNSName = flag.String("dnsname", "", "DNS name to be included in the certificate (SAN)") - - // in case of multiple certificate authorities, we need to figure out who the recipient of the encrypted - // data is. - flCAFingerprint = flag.String("ca-fingerprint", "", "SHA-256 digest of CA certificate for NDES server. Note: Changed from MD5.") - flKeyEnciphermentSelector = flag.Bool("key-encipherment-selector", false, "Filter CA certificates by key encipherment usage") - - flDebugLogging = flag.Bool("debug", false, "enable debug logging") - flLogJSON = flag.Bool("log-json", false, "use JSON for log output") -) - -func validateFlags() error { - keyPath := *flPKeyPath - serverURL := *flServerURL - caFingerprint := *flCAFingerprint - useKeyEnciphermentSelector := *flKeyEnciphermentSelector +func validateFlags(keyPath, serverURL, caFingerprint string, useKeyEnciphermentSelector bool) error { if keyPath == "" { return errors.New("must specify private key path") } @@ -300,6 +271,31 @@ func validateFlags() error { } func main() { + var ( + flVersion = flag.Bool("version", false, "prints version information") + flServerURL = flag.String("server-url", "", "SCEP server url") + flChallengePassword = flag.String("challenge", "", "enforce a challenge password") + flPKeyPath = flag.String("private-key", "", "private key path, if there is no key, scepclient will create one") + flCertPath = flag.String("certificate", "", "certificate path, if there is no key, scepclient will create one") + flKeySize = flag.Int("keySize", 2048, "rsa key size") + flOrg = flag.String("organization", "scep-client", "organization for cert") + flCName = flag.String("cn", "scepclient", "common name for certificate") + flOU = flag.String("ou", "MDM", "organizational unit for certificate") + flLoc = flag.String("locality", "", "locality for certificate") + flProvince = flag.String("province", "", "province for certificate") + flCountry = flag.String("country", "US", "country code in certificate") + flCACertMessage = flag.String("cacert-message", "", "message sent with GetCACert operation") + flDNSName = flag.String("dnsname", "", "DNS name to be included in the certificate (SAN)") + + // in case of multiple certificate authorities, we need to figure out who the recipient of the encrypted + // data is. This can be done using either the CA fingerprint, or based on the key usage encoded in the + // certificates returned by the authority. + flCAFingerprint = flag.String("ca-fingerprint", "", "SHA-256 digest of CA certificate for NDES server. Note: Changed from MD5.") + flKeyEnciphermentSelector = flag.Bool("key-encipherment-selector", false, "Filter CA certificates by key encipherment usage") + + flDebugLogging = flag.Bool("debug", false, "enable debug logging") + flLogJSON = flag.Bool("log-json", false, "use JSON for log output") + ) flag.Parse() @@ -309,21 +305,21 @@ func main() { os.Exit(0) } - if err := validateFlags(); err != nil { + if err := validateFlags(*flPKeyPath, *flServerURL, *flCAFingerprint, *flKeyEnciphermentSelector); err != nil { fmt.Println(err) os.Exit(1) } caCertsSelector := scep.NopCertsSelector() - if *flCAFingerprint != "" { + switch { + case *flCAFingerprint != "": hash, err := validateFingerprint(*flCAFingerprint) if err != nil { fmt.Printf("invalid fingerprint: %s\n", err) os.Exit(1) } caCertsSelector = scep.FingerprintCertsSelector(fingerprintHashType, hash) - } - if *flKeyEnciphermentSelector { + case *flKeyEnciphermentSelector: caCertsSelector = scep.EnciphermentCertsSelector() }