Skip to content

Commit

Permalink
Enforce validity period in COP for ECerts/TCerts
Browse files Browse the repository at this point in the history
This change set verifies that a certificate has neither
expired nor been revoked.  The main change is a relatively
small change in auth.go which calls cfssl code to verify the
cert.

The test case uses a special signing profile which expires after
1 second.  To support this, I had to make a change so that the
enroll and reenroll commands support using a non-default signing profile.
Therefore, I changed what goes across the network of an enroll
and reenroll request.  Prior to this change, it only sends
a CSR.  After this change, it sends JSON with a CSR and an
optional signing profile name (along with other optional fields).

I have notified the SDK folks of this change.

https://jira.hyperledger.org/browse/FAB-145

Change-Id: Idceeedadf9e5a42d995feee5bdba1353b685e9f1
Signed-off-by: Keith Smith <bksmith@us.ibm.com>
  • Loading branch information
Keith Smith committed Jan 5, 2017
1 parent e27d3ef commit 72a87e3
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 156 deletions.
27 changes: 13 additions & 14 deletions cli/server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ package server

import (
"bytes"
"encoding/hex"
"errors"
"io/ioutil"
"net/http"

"github.com/cloudflare/cfssl/api"
cerr "github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/revoke"
"github.com/hyperledger/fabric-cop/util"
)

const (
enrollmentIDHdrName = "__eid__"
)

// AuthHandler
type copAuthHandler struct {
basic bool
Expand Down Expand Up @@ -106,8 +110,8 @@ func (ah *copAuthHandler) serveHTTP(w http.ResponseWriter, r *http.Request) erro
if err != nil {
return err
}

log.Debug("User/Pass was correct")
r.Header.Set(enrollmentIDHdrName, user)
// TODO: Do the following
// 2) Update state of 'user' in DB as enrolled and return true.
return nil
Expand All @@ -125,22 +129,17 @@ func (ah *copAuthHandler) serveHTTP(w http.ResponseWriter, r *http.Request) erro
if err2 != nil {
return authError
}
// check status of certificate
serial := cert.SerialNumber.String()
aki := hex.EncodeToString(cert.AuthorityKeyId)
certs, err := certDBAccessor.GetCertificate(serial, aki)
if err != nil {
log.Debugf("GetCertificate failed: %s", err)
return authError
}
if len(certs) != 1 {
log.Debugf("Expecting 1 certificate but found %d; serial=%s, aki=%s", len(certs), serial, aki)
// Check for certificate revocation and expiration
revokedOrExpired, checked := revoke.VerifyCertificate(cert)
if revokedOrExpired {
log.Debug("Certificate was either revoked or has expired")
return authError
}
if certs[0].Status != "good" {
log.Debugf("Auth failure - certificate status is %s", certs[0].Status)
if !checked {
log.Debug("A failure occurred while checking for revocation and expiration")
return authError
}
r.Header.Set(enrollmentIDHdrName, util.GetEnrollmentIDFromX509Certificate(cert))
}
return nil
}
Expand Down
2 changes: 0 additions & 2 deletions cli/server/certdbaccessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ func (d *CertDBAccessor) InsertCertificate(cr certdb.CertificateRecord) error {
return err
}
id, err := util.GetEnrollmentIDFromPEM([]byte(cr.PEM))

err = d.checkDB()
if err != nil {
return err
}
Expand Down
98 changes: 35 additions & 63 deletions cli/server/enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,96 +17,68 @@ limitations under the License.
package server

import (
"errors"
"fmt"
"io/ioutil"
"net/http"

"github.com/cloudflare/cfssl/api"
cerr "github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
cop "github.com/hyperledger/fabric-cop/api"
"github.com/hyperledger/fabric-cop/util"
)

// enrollHandler for register requests
type enrollHandler struct {
// NewEnrollHandler is the constructor for the enroll handler
func NewEnrollHandler() (h http.Handler, err error) {
return newSignHandler("enroll")
}

// NewEnrollHandler is constructor for register handler
func NewEnrollHandler() (h http.Handler, err error) {
// NewReenrollHandler is the constructor for the reenroll handler
func NewReenrollHandler() (h http.Handler, err error) {
return newSignHandler("reenroll")
}

// signHandler for enroll or reenroll requests
type signHandler struct {
// "enroll" or "reenroll"
endpoint string
}

// newEnrollHandler is the constructor for an enroll or reenroll handler
func newSignHandler(endpoint string) (h http.Handler, err error) {
// NewHandler is constructor for register handler
return &api.HTTPHandler{
Handler: &enrollHandler{},
Handler: &signHandler{endpoint: endpoint},
Methods: []string{"POST"},
}, nil
}

// Handle a enroll request
func (h *enrollHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Debug("enroll request received")
// Handle an enroll or reenroll request.
// Authentication has already occurred for both enroll and reenroll prior
// to calling this function in auth.go.
func (sh *signHandler) Handle(w http.ResponseWriter, r *http.Request) error {

log.Debugf("Received request for endpoint %s", sh.endpoint)

// Read the request's body
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Errorf("failed to read body: %s", err)
return cerr.NewBadRequest(errors.New("failed to read request body"))
return err
}
r.Body.Close()

user, token, ok := r.BasicAuth()
if !ok {
log.Error("No authorization header set")
return cerr.NewBadRequest(errors.New("missing authorization header"))
}

enroll := NewEnrollUser()
cert, err := enroll.Enroll(user, []byte(token), body)
// Unmarshall the request body
var req signer.SignRequest
err = util.Unmarshal(body, &req, sh.endpoint)
if err != nil {
return cerr.NewBadRequest(err)
return err
}

return api.SendResponse(w, cert)
}

// Enroll is for enrolling a user
type Enroll struct {
cfg *Config
}

// NewEnrollUser returns an pointer to an allocated enroll struct, populated
// with the DB information (that set in the config) or nil on error.
func NewEnrollUser() *Enroll {
e := new(Enroll)
e.cfg = CFG
return e
}

// Enroll will enroll an already registered user and provided an enrollment cert
func (e *Enroll) Enroll(id string, token []byte, csrPEM []byte) ([]byte, cop.Error) {
log.Debugf("Received request to enroll user with id: %s\n", id)

cert, signErr := e.signKey(csrPEM)
if signErr != nil {
log.Error("Failed to sign CSR - Enroll Failed")
return nil, signErr
}

return cert, nil
}

func (e *Enroll) signKey(csrPEM []byte) ([]byte, cop.Error) {
log.Debugf("signKey")
req := signer.SignRequest{
// Hosts: signer.SplitHosts(c.Hostname),
Request: string(csrPEM),
// Profile: c.Profile,
// Label: c.Label,
}
cert, err := enrollSigner.Sign(req)
if err != nil {
log.Errorf("Sign error: %s", err)
return nil, cop.WrapError(err, cop.CFSSL, "Failed in Sign")
err = fmt.Errorf("Failed signing for endpoint %s: %s", sh.endpoint, err)
log.Error(err.Error())
return err
}
log.Debug("Sign success")
return cert, nil

return api.SendResponse(w, cert)
}
2 changes: 1 addition & 1 deletion cli/server/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var initFlags = []string{"remote", "u"}

// initMain creates the private key and self-signed certificate needed to start COP Server
func initMain(args []string, c cli.Config) (err error) {
csrFile, args, err := cli.PopFirstArgument(args)
csrFile, _, err := cli.PopFirstArgument(args)
if err != nil {
return errors.New(err.Error())
}
Expand Down
60 changes: 0 additions & 60 deletions cli/server/reenroll.go

This file was deleted.

4 changes: 4 additions & 0 deletions cli/server/revoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error {
return authErr(w, err)
}

// Make sure that the user has the "hf.Revoker" attribute in order to be authorized
// to revoke a certificate. This attribute comes from the user registry, which
// is either in the DB if LDAP is not configured, or comes from LDAP if LDAP is
// configured.
err = userHasAttribute(cert.Subject.CommonName, "hf.Revoker")
if err != nil {
return authErr(w, err)
Expand Down
47 changes: 35 additions & 12 deletions cli/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func startServer() {
}

if !serverStarted {
os.RemoveAll(dir)
serverStarted = true
fmt.Println("starting COP server ...")
os.Setenv("COP_DEBUG", "true")
Expand Down Expand Up @@ -216,10 +217,6 @@ func TestRevoke(t *testing.T) {
return
}

err = id.RevokeSelf()
if err == nil {
t.Error("RevokeSelf twice should have failed but did not")
}
}

func TestGetTCerts(t *testing.T) {
Expand Down Expand Up @@ -301,14 +298,12 @@ func TestMaxEnrollment(t *testing.T) {
}

func TestEnroll(t *testing.T) {
e := NewEnrollUser()

testUnregisteredUser(e, t)
testIncorrectToken(e, t)
testEnrollingUser(e, t)
testUnregisteredUser(t)
testIncorrectToken(t)
testEnrollingUser(t)
}

func testUnregisteredUser(e *Enroll, t *testing.T) {
func testUnregisteredUser(t *testing.T) {
copServer := `{"serverURL":"https://localhost:8888"}`
c, _ := lib.NewClient(copServer)

Expand All @@ -324,7 +319,7 @@ func testUnregisteredUser(e *Enroll, t *testing.T) {
}
}

func testIncorrectToken(e *Enroll, t *testing.T) {
func testIncorrectToken(t *testing.T) {
copServer := `{"serverURL":"https://localhost:8888"}`
c, _ := lib.NewClient(copServer)

Expand All @@ -340,7 +335,7 @@ func testIncorrectToken(e *Enroll, t *testing.T) {
}
}

func testEnrollingUser(e *Enroll, t *testing.T) {
func testEnrollingUser(t *testing.T) {
copServer := `{"serverURL":"https://localhost:8888"}`
c, _ := lib.NewClient(copServer)

Expand Down Expand Up @@ -389,6 +384,34 @@ func TestUpdateField(t *testing.T) {
}
}

func TestExpiration(t *testing.T) {

copServer := `{"serverURL":"https://localhost:8888"}`
c, _ := lib.NewClient(copServer)

// Enroll this user using the "expiry" profile which is configured
// to expire after 1 second
regReq := &idp.EnrollmentRequest{
Name: "expiryUser",
Secret: "expirypw",
Profile: "expiry",
}

id, err := c.Enroll(regReq)
if err != nil {
t.Error("enroll of user 'admin' with password 'adminpw' failed")
return
}

t.Log("Sleeping 5 seconds waiting for certificate to expire")
time.Sleep(5 * time.Second)
t.Log("Done sleeping")
err = id.RevokeSelf()
if err == nil {
t.Error("certificate should have expired but did not")
}
}

func TestUserRegistry(t *testing.T) {

err := InitUserRegistry(&Config{DBdriver: "postgres", DataSource: "dbname=cop sslmode=disable"})
Expand Down
Loading

0 comments on commit 72a87e3

Please sign in to comment.