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

create and use CSRSignerContext in the server and middleware #220

Merged
merged 1 commit into from
Aug 24, 2023
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
7 changes: 4 additions & 3 deletions challenge/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package challenge

import (
"context"
"crypto/x509"
"errors"

Expand All @@ -16,8 +17,8 @@ type Store interface {
}

// Middleware wraps next in a CSRSigner that verifies and invalidates the challenge
func Middleware(store Store, next scepserver.CSRSigner) scepserver.CSRSignerFunc {
return func(m *scep.CSRReqMessage) (*x509.Certificate, error) {
func Middleware(store Store, next scepserver.CSRSignerContext) scepserver.CSRSignerContextFunc {
return func(ctx context.Context, m *scep.CSRReqMessage) (*x509.Certificate, error) {
// TODO: compare challenge only for PKCSReq?
valid, err := store.HasChallenge(m.ChallengePassword)
if err != nil {
Expand All @@ -26,6 +27,6 @@ func Middleware(store Store, next scepserver.CSRSigner) scepserver.CSRSignerFunc
if !valid {
return nil, errors.New("invalid challenge")
}
return next.SignCSR(m)
return next.SignCSRContext(ctx, m)
}
}
7 changes: 5 additions & 2 deletions challenge/challenge_bolt_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package challenge

import (
"context"
"io/ioutil"
"os"
"testing"
Expand Down Expand Up @@ -69,12 +70,14 @@ func TestDynamicChallenge(t *testing.T) {
ChallengePassword: challengePassword,
}

_, err = signer.SignCSR(csrReq)
ctx := context.Background()

_, err = signer.SignCSRContext(ctx, csrReq)
if err != nil {
t.Error(err)
}

_, err = signer.SignCSR(csrReq)
_, err = signer.SignCSRContext(ctx, csrReq)
if err == nil {
t.Error("challenge should not be valid twice")
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/scepserver/scepserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ func main() {
if *flSignServerAttrs {
signerOpts = append(signerOpts, scepdepot.WithSeverAttrs())
}
var signer scepserver.CSRSigner = scepdepot.NewSigner(depot, signerOpts...)
var signer scepserver.CSRSignerContext = scepserver.SignCSRAdapter(scepdepot.NewSigner(depot, signerOpts...))
if *flChallengePassword != "" {
signer = scepserver.ChallengeMiddleware(*flChallengePassword, signer)
signer = scepserver.StaticChallengeMiddleware(*flChallengePassword, signer)
}
if csrVerifier != nil {
signer = csrverifier.Middleware(csrVerifier, signer)
Expand Down
7 changes: 6 additions & 1 deletion cryptoutil/cryptoutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"math/big"
"testing"
)

func TestGenerateSubjectKeyID(t *testing.T) {
ecKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
testName string
pub crypto.PublicKey
}{
{"RSA", &rsa.PublicKey{N: big.NewInt(123), E: 65537}},
{"ECDSA", &ecdsa.PublicKey{X: big.NewInt(123), Y: big.NewInt(123), Curve: elliptic.P224()}},
{"ECDSA", ecKey.Public()},
} {
test := test
t.Run(test.testName, func(t *testing.T) {
Expand Down
7 changes: 4 additions & 3 deletions csrverifier/csrverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package csrverifier

import (
"context"
"crypto/x509"
"errors"

Expand All @@ -15,15 +16,15 @@ type CSRVerifier interface {
}

// Middleware wraps next in a CSRSigner that runs verifier
func Middleware(verifier CSRVerifier, next scepserver.CSRSigner) scepserver.CSRSignerFunc {
return func(m *scep.CSRReqMessage) (*x509.Certificate, error) {
func Middleware(verifier CSRVerifier, next scepserver.CSRSignerContext) scepserver.CSRSignerContextFunc {
return func(ctx context.Context, m *scep.CSRReqMessage) (*x509.Certificate, error) {
ok, err := verifier.Verify(m.RawDecrypted)
if err != nil {
return nil, err
}
if !ok {
return nil, errors.New("CSR verify failed")
}
return next.SignCSR(m)
return next.SignCSRContext(ctx, m)
}
}
40 changes: 32 additions & 8 deletions server/csrsigner.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
package scepserver

import (
"context"
"crypto/subtle"
"crypto/x509"
"errors"

"github.com/micromdm/scep/v2/scep"
)

// CSRSignerContext is a handler for signing CSRs by a CA/RA.
//
// SignCSRContext should take the CSR in the CSRReqMessage and return a
// Certificate signed by the CA.
type CSRSignerContext interface {
SignCSRContext(context.Context, *scep.CSRReqMessage) (*x509.Certificate, error)
}

// CSRSignerContextFunc is an adapter for CSR signing by the CA/RA.
type CSRSignerContextFunc func(context.Context, *scep.CSRReqMessage) (*x509.Certificate, error)

// SignCSR calls f(ctx, m).
func (f CSRSignerContextFunc) SignCSRContext(ctx context.Context, m *scep.CSRReqMessage) (*x509.Certificate, error) {
return f(ctx, m)
}

// CSRSigner is a handler for CSR signing by the CA/RA
//
// SignCSR should take the CSR in the CSRReqMessage and return a
Expand All @@ -16,29 +33,36 @@ type CSRSigner interface {
SignCSR(*scep.CSRReqMessage) (*x509.Certificate, error)
}

// CSRSignerFunc is an adapter for CSR signing by the CA/RA
// CSRSignerFunc is an adapter for CSR signing by the CA/RA.
type CSRSignerFunc func(*scep.CSRReqMessage) (*x509.Certificate, error)

// SignCSR calls f(m)
// SignCSR calls f(m).
func (f CSRSignerFunc) SignCSR(m *scep.CSRReqMessage) (*x509.Certificate, error) {
return f(m)
}

// NopCSRSigner does nothing
func NopCSRSigner() CSRSignerFunc {
return func(m *scep.CSRReqMessage) (*x509.Certificate, error) {
// NopCSRSigner does nothing.
func NopCSRSigner() CSRSignerContextFunc {
return func(_ context.Context, _ *scep.CSRReqMessage) (*x509.Certificate, error) {
return nil, nil
}
}

// ChallengeMiddleware wraps next in a CSRSigner that validates the challenge from the CSR
func ChallengeMiddleware(challenge string, next CSRSigner) CSRSignerFunc {
// StaticChallengeMiddleware wraps next and validates the challenge from the CSR.
func StaticChallengeMiddleware(challenge string, next CSRSignerContext) CSRSignerContextFunc {
challengeBytes := []byte(challenge)
return func(m *scep.CSRReqMessage) (*x509.Certificate, error) {
return func(ctx context.Context, m *scep.CSRReqMessage) (*x509.Certificate, error) {
// TODO: compare challenge only for PKCSReq?
if subtle.ConstantTimeCompare(challengeBytes, []byte(m.ChallengePassword)) != 1 {
return nil, errors.New("invalid challenge")
}
return next.SignCSRContext(ctx, m)
}
}

// SignCSRAdapter adapts a next (i.e. no context) to a context signer.
func SignCSRAdapter(next CSRSigner) CSRSignerContextFunc {
return func(_ context.Context, m *scep.CSRReqMessage) (*x509.Certificate, error) {
return next.SignCSR(m)
}
}
9 changes: 6 additions & 3 deletions server/csrsigner_test.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package scepserver

import (
"context"
"testing"

"github.com/micromdm/scep/v2/scep"
)

func TestChallengeMiddleware(t *testing.T) {
testPW := "RIGHT"
signer := ChallengeMiddleware(testPW, NopCSRSigner())
signer := StaticChallengeMiddleware(testPW, NopCSRSigner())

csrReq := &scep.CSRReqMessage{ChallengePassword: testPW}

_, err := signer.SignCSR(csrReq)
ctx := context.Background()

_, err := signer.SignCSRContext(ctx, csrReq)
if err != nil {
t.Error(err)
}

csrReq.ChallengePassword = "WRONG"

_, err = signer.SignCSR(csrReq)
_, err = signer.SignCSRContext(ctx, csrReq)
if err == nil {
t.Error("invalid challenge should generate an error")
}
Expand Down
6 changes: 3 additions & 3 deletions server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type service struct {
// The (chainable) CSR signing function. Intended to handle all
// SCEP request functionality such as CSR & challenge checking, CA
// issuance, RA proxying, etc.
signer CSRSigner
signer CSRSignerContext

/// info logging is implemented in the service middleware layer.
debugLogger log.Logger
Expand Down Expand Up @@ -80,7 +80,7 @@ func (svc *service) PKIOperation(ctx context.Context, data []byte) ([]byte, erro
return nil, err
}

crt, err := svc.signer.SignCSR(msg.CSRReqMessage)
crt, err := svc.signer.SignCSRContext(ctx, msg.CSRReqMessage)
if err == nil && crt == nil {
err = errors.New("no signed certificate")
}
Expand Down Expand Up @@ -119,7 +119,7 @@ func WithAddlCA(ca *x509.Certificate) ServiceOption {
}

// NewService creates a new scep service
func NewService(crt *x509.Certificate, key *rsa.PrivateKey, signer CSRSigner, opts ...ServiceOption) (Service, error) {
func NewService(crt *x509.Certificate, key *rsa.PrivateKey, signer CSRSignerContext, opts ...ServiceOption) (Service, error) {
s := &service{
crt: crt,
key: key,
Expand Down
2 changes: 1 addition & 1 deletion server/service_bolt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestCaCert(t *testing.T) {
caCert := certs[0]

// SCEP service
svc, err := scepserver.NewService(caCert, key, scepdepot.NewSigner(depot))
svc, err := scepserver.NewService(caCert, key, scepserver.SignCSRAdapter(scepdepot.NewSigner(depot)))
if err != nil {
t.Fatal(err)
}
Expand Down
Loading