Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Csr validator #68

Merged
merged 6 commits into from
Oct 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cmd/scepserver/scepserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (

"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/micromdm/scep/csrverifier"
"github.com/micromdm/scep/csrverifier/executable"
"github.com/micromdm/scep/depot"
"github.com/micromdm/scep/depot/file"
"github.com/micromdm/scep/server"
Expand Down Expand Up @@ -54,6 +56,7 @@ func main() {
flClDuration = flag.String("crtvalid", envString("SCEP_CERT_VALID", "365"), "validity for new client certificates in days")
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")
flDebug = flag.Bool("debug", envBool("SCEP_LOG_DEBUG"), "enable debug logging")
flLogJSON = flag.Bool("log-json", envBool("SCEP_LOG_JSON"), "output JSON logs")
)
Expand Down Expand Up @@ -109,10 +112,21 @@ func main() {
lginfo.Log("No valid number for client cert validity : ", err)
os.Exit(1)
}
var csrVerifier csrverifier.CSRVerifier
if *flCSRVerifierExec > "" {
executableCSRVerifier, err := executablecsrverifier.New(*flCSRVerifierExec, lginfo)
if err != nil {
lginfo.Log("Could not instantiate CSR verifier : ", err)
os.Exit(1)
}
csrVerifier = executableCSRVerifier
}

var svc scepserver.Service // scep service
{
svcOptions := []scepserver.ServiceOption{
scepserver.ChallengePassword(*flChallengePassword),
scepserver.WithCSRVerifier(csrVerifier),
scepserver.CAKeyPassword([]byte(*flCAPass)),
scepserver.ClientValidity(clientValidity),
scepserver.AllowRenewal(allowRenewal),
Expand Down
7 changes: 7 additions & 0 deletions csrverifier/csrverifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Package csrverifier defines an interface for CSR verification.
package csrverifier

// Verify the raw decrypted CSR.
type CSRVerifier interface {
Verify(data []byte) (bool, error)
}
67 changes: 67 additions & 0 deletions csrverifier/executable/csrverifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Package executablecsrverifier defines the ExecutableCSRVerifier csrverifier.CSRVerifier.
package executablecsrverifier
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs doc


import (
"errors"
"os"
"os/exec"

"github.com/go-kit/kit/log"
)

const (
userExecute os.FileMode = 1 << (6 - 3*iota)
groupExecute
otherExecute
)

// New creates a executablecsrverifier.ExecutableCSRVerifier.
func New(path string, logger log.Logger) (*ExecutableCSRVerifier, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return nil, err
}

fileMode := fileInfo.Mode()
if fileMode.IsDir() {
return nil, errors.New("CSR Verifier executable is a directory")
}

filePerm := fileMode.Perm()
if filePerm&(userExecute|groupExecute|otherExecute) == 0 {
return nil, errors.New("CSR Verifier executable is not executable")
}

return &ExecutableCSRVerifier{executable: path, logger: logger}, nil
}

// ExecutableCSRVerifier implements a csrverifier.CSRVerifier.
// It executes a command, and passes it the raw decrypted CSR.
// If the command exit code is 0, the CSR is considered valid.
// In any other cases, the CSR is considered invalid.
type ExecutableCSRVerifier struct {
executable string
logger log.Logger
}

func (v *ExecutableCSRVerifier) Verify(data []byte) (bool, error) {
cmd := exec.Command(v.executable)

stdin, err := cmd.StdinPipe()
if err != nil {
return false, err
}

go func() {
defer stdin.Close()
stdin.Write(data)
}()

err = cmd.Run()
if err != nil {
v.logger.Log("err", err)
// mask the executable error
return false, nil
}
return true, err
}
3 changes: 3 additions & 0 deletions scep/scep.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ type CertRepMessage struct {
// The content of this message is protected
// by the recipient public key(example CA)
type CSRReqMessage struct {
RawDecrypted []byte

// PKCS#10 Certificate request inside the envelope
CSR *x509.CertificateRequest

Expand Down Expand Up @@ -345,6 +347,7 @@ func (msg *PKIMessage) DecryptPKIEnvelope(cert *x509.Certificate, key *rsa.Priva
return errors.Wrap(err, "scep: parse challenge password in pkiEnvelope")
}
msg.CSRReqMessage = &CSRReqMessage{
RawDecrypted: msg.pkiEnvelope,
CSR: csr,
ChallengePassword: cp,
}
Expand Down
32 changes: 30 additions & 2 deletions server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/go-kit/kit/log"
"github.com/micromdm/scep/challenge"
"github.com/micromdm/scep/csrverifier"
"github.com/micromdm/scep/depot"
"github.com/micromdm/scep/scep"
)
Expand Down Expand Up @@ -47,6 +48,7 @@ type service struct {
challengePassword string
supportDynamciChallenge bool
dynamicChallengeStore challenge.Store
csrVerifier csrverifier.CSRVerifier
allowRenewal int // days before expiry, 0 to disable
clientValidity int // client cert validity in days

Expand Down Expand Up @@ -91,8 +93,25 @@ func (svc *service) PKIOperation(ctx context.Context, data []byte) ([]byte, erro

// validate challenge passwords
if msg.MessageType == scep.PKCSReq {
if !svc.challengePasswordMatch(msg.CSRReqMessage.ChallengePassword) {
svc.debugLogger.Log("err", "scep challenge password does not match")
CSRIsValid := false

if svc.csrVerifier != nil {
result, err := svc.csrVerifier.Verify(msg.CSRReqMessage.RawDecrypted)
if err != nil {
return nil, err
}
CSRIsValid = result
if !CSRIsValid {
svc.debugLogger.Log("err", "CSR is not valid")
}
} else {
CSRIsValid = svc.challengePasswordMatch(msg.CSRReqMessage.ChallengePassword)
if !CSRIsValid {
svc.debugLogger.Log("err", "scep challenge password does not match")
}
}

if !CSRIsValid {
certRep, err := msg.Fail(ca, svc.caKey, scep.BadRequest)
if err != nil {
return nil, err
Expand Down Expand Up @@ -186,6 +205,15 @@ func (svc *service) challengePasswordMatch(pw string) bool {
// ServiceOption is a server configuration option
type ServiceOption func(*service) error

// WithCSRVerifier is an option argument to NewService
// which allows setting a CSR verifier.
func WithCSRVerifier(csrVerifier csrverifier.CSRVerifier) ServiceOption {
return func(s *service) error {
s.csrVerifier = csrVerifier
return nil
}
}

// ChallengePassword is an optional argument to NewService
// which allows setting a preshared key for SCEP.
func ChallengePassword(pw string) ServiceOption {
Expand Down