Skip to content

Commit

Permalink
support dynamic challenge passwords
Browse files Browse the repository at this point in the history
  • Loading branch information
groob committed Jul 16, 2017
1 parent e6079f0 commit 57b9a8f
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 16 deletions.
98 changes: 82 additions & 16 deletions server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package scepserver

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -39,25 +41,61 @@ type Service interface {
}

type service struct {
depot depot.Depot
ca []*x509.Certificate // CA cert or chain
caKey *rsa.PrivateKey
caKeyPassword []byte
csrTemplate *x509.Certificate
challengePassword string
allowRenewal int // days before expiry, 0 to disable
clientValidity int // client cert validity in days
depot depot.Depot
ca []*x509.Certificate // CA cert or chain
caKey *rsa.PrivateKey
caKeyPassword []byte
csrTemplate *x509.Certificate
challengePassword string
supportDynamciChallenge bool
challengeFunc chan func()
dynamicChallenges []string
allowRenewal int // days before expiry, 0 to disable
clientValidity int // client cert validity in days

/// info logging is implemented in the service middleware layer.
debugLogger log.Logger
}

func (svc service) GetCACaps(ctx context.Context) ([]byte, error) {
// SCEPChallenge returns a brand new, random dynamic challenge.
func (svc *service) SCEPChallenge() (string, error) {
if !svc.supportDynamciChallenge {
return svc.challengePassword, nil
}

challenges := make(chan string)
errs := make(chan error)
svc.challengeFunc <- func() {
r := make([]byte, 24)
_, err := rand.Read(r)
if err != nil {
errs <- err
challenges <- ""
return
}
challenge := base64.StdEncoding.EncodeToString(r)
svc.dynamicChallenges = append(svc.dynamicChallenges, challenge)
challenges <- challenge
errs <- nil
}
return <-challenges, <-errs
}

func (svc *service) dynamicChallengeLoop() {
for {
select {
case f := <-svc.challengeFunc:
f()
}
}
}

func (svc *service) GetCACaps(ctx context.Context) ([]byte, error) {
defaultCaps := []byte("SHA-1\nPOSTPKIOperation")
return defaultCaps, nil
}

func (svc service) GetCACert(ctx context.Context) ([]byte, int, error) {
func (svc *service) GetCACert(ctx context.Context) ([]byte, int, error) {
if len(svc.ca) == 0 {
return nil, 0, errors.New("missing CA Cert")
}
Expand All @@ -68,7 +106,7 @@ func (svc service) GetCACert(ctx context.Context) ([]byte, int, error) {
return data, len(svc.ca), err
}

func (svc service) PKIOperation(ctx context.Context, data []byte) ([]byte, error) {
func (svc *service) PKIOperation(ctx context.Context, data []byte) ([]byte, error) {
msg, err := scep.ParsePKIMessage(data)
if err != nil {
return nil, err
Expand Down Expand Up @@ -135,7 +173,6 @@ func (svc service) PKIOperation(ctx context.Context, data []byte) ([]byte, error
}

return certRep.Raw, nil

}

func certName(crt *x509.Certificate) string {
Expand All @@ -145,18 +182,35 @@ func certName(crt *x509.Certificate) string {
return string(crt.Signature)
}

func (svc service) GetNextCACert(ctx context.Context) ([]byte, error) {
func (svc *service) GetNextCACert(ctx context.Context) ([]byte, error) {
panic("not implemented")
}

func (svc service) challengePasswordMatch(pw string) bool {
if svc.challengePassword == "" {
func (svc *service) challengePasswordMatch(pw string) bool {
if svc.challengePassword == "" && !svc.supportDynamciChallenge {
// empty password, don't validate
return true
}
if svc.challengePassword == pw {
if !svc.supportDynamciChallenge && svc.challengePassword == pw {
return true
}

if svc.supportDynamciChallenge {
valid := make(chan bool)
svc.challengeFunc <- func() {
for i, v := range svc.dynamicChallenges {
if v == pw {
svc.dynamicChallenges = append(svc.dynamicChallenges[:i], svc.dynamicChallenges[i+1:]...)
valid <- true
return
}
}
valid <- false
return
}
return <-valid
}

return false
}

Expand Down Expand Up @@ -206,6 +260,14 @@ func WithLogger(logger log.Logger) ServiceOption {
}
}

func WithDynamicChallenges() ServiceOption {
return func(s *service) error {
s.challengeFunc = make(chan func())
s.supportDynamciChallenge = true
return nil
}
}

// NewService creates a new scep service
func NewService(depot depot.Depot, opts ...ServiceOption) (Service, error) {
s := &service{
Expand All @@ -218,6 +280,10 @@ func NewService(depot depot.Depot, opts ...ServiceOption) (Service, error) {
}
}

if s.supportDynamciChallenge {
go s.dynamicChallengeLoop()
}

var err error
s.ca, s.caKey, err = depot.CA(s.caKeyPassword)
if err != nil {
Expand Down
38 changes: 38 additions & 0 deletions server/service_bolt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,44 @@ import (
"github.com/micromdm/scep/scep"
)

func TestDynamicChallenge(t *testing.T) {
depot := createDB(0666, nil)
key, err := depot.CreateOrLoadKey(2048)
if err != nil {
t.Fatal(err)
}

_, err = depot.CreateOrLoadCA(key, 5, "MicroMDM", "US")
if err != nil {
t.Fatal(err)
}
opts := []ServiceOption{
ClientValidity(365),
WithDynamicChallenges(),
}
svc, err := NewService(depot, opts...)
if err != nil {
t.Fatal(err)
}

challenger := svc.(interface {
SCEPChallenge() (string, error)
})
challenge, err := challenger.SCEPChallenge()
if err != nil {
t.Fatal(err)
}

impl := svc.(*service)
if !impl.challengePasswordMatch(challenge) {
t.Errorf("challenge password does not match")
}
if impl.challengePasswordMatch(challenge) {
t.Errorf("challenge password matched but should only be used once")
}

}

func TestCaCert(t *testing.T) {
depot := createDB(0666, nil)
key, err := depot.CreateOrLoadKey(2048)
Expand Down

0 comments on commit 57b9a8f

Please sign in to comment.