Skip to content

Commit

Permalink
[FAB-3734] Fix default expiration times
Browse files Browse the repository at this point in the history
The default expirations were all 8000 hours which is less than 1 year
for all certificates generated by fabric-ca.
This change set adds documentation to the default config file which
is generated from the code pertaining to how to configure certificate
expiration times for 3 types of certificates.  It also changes the
default values for these types.
The 3 types with their default values are as follows:
1) CA root certificate: default of 15 years
2) CA intermediate certificate:  default of 5 years
3) Issued enrollment certificates: default of 1 year
Although there are no standard values, these values are in line with
what others are doing.

This change set also adds appropriate test cases for these 3
types of certificates.

Change-Id: I4b88517b4550233bfbd5a40ae5b203c584e51a17
Signed-off-by: Keith Smith <[email protected]>
  • Loading branch information
Keith Smith committed May 25, 2017
1 parent 406df3c commit 1be793d
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 46 deletions.
27 changes: 19 additions & 8 deletions cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,23 +207,33 @@ affiliations:
#############################################################################
# Signing section
#
# The "default" subsection is used to sign enrollment certificates;
# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
#
# The "ca" profile subsection is used to sign intermediate CA certificates;
# the default expiration ("expiry" field) is "43800h" which is 5 years in hours.
# Note that "isca" is true, meaning that it issues a CA certificate.
#############################################################################
signing:
default:
usage:
- cert sign
expiry: 8760h
profiles:
ca:
usage:
- cert sign
expiry: 8000h
expiry: 43800h
caconstraint:
isca: true
default:
usage:
- cert sign
expiry: 8000h
###########################################################################
# Certificate Signing Request (CSR) section for generating the request
# for an intermediate CA certificate.
# Certificate Signing Request (CSR) section.
# This controls the creation of the root CA certificate.
# The expiration for the root CA certificate is configured with the
# "ca.expiry" field below, whose default value is "131400h" which is
# 15 years in hours.
###########################################################################
csr:
cn: fabric-ca-server
Expand All @@ -235,10 +245,11 @@ csr:
OU: Fabric
hosts:
- <<<MYHOST>>>
- localhost
ca:
pathlen:
pathlenzero:
expiry:
expiry: 131400h
#############################################################################
# BCCSP (BlockChain Crypto Service Provider) section is used to select which
Expand Down
63 changes: 60 additions & 3 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ const (
defaultDatabaseType = "sqlite3"
)

var (
// Default root CA certificate expiration is 15 years (in hours).
defaultRootCACertificateExpiration = "131400h"
// Default intermediate CA certificate expiration is 5 years (in hours).
defaultIntermediateCACertificateExpiration = parseDuration("43800h")
// Default issued certificate expiration is 1 year (in hours).
defaultIssuedCertificateExpiration = parseDuration("8760h")
)

// CA represents a certificate authority which signs, issues and revokes certificates
type CA struct {
// The home directory for the CA
Expand Down Expand Up @@ -253,9 +262,6 @@ func (ca *CA) getCACert() (cert []byte, err error) {
if clientCfg.Enrollment.CSR == nil {
clientCfg.Enrollment.CSR = &api.CSRInfo{}
}
if clientCfg.Enrollment.CSR.CA == nil {
clientCfg.Enrollment.CSR.CA = &cfcsr.CAConfig{PathLength: 0, PathLenZero: true}
}
log.Debugf("Intermediate enrollment request: %v", clientCfg.Enrollment)
var resp *EnrollmentResponse
resp, err = clientCfg.Enroll(ca.Config.Intermediate.ParentServer.URL, ca.HomeDir)
Expand Down Expand Up @@ -293,6 +299,12 @@ func (ca *CA) getCACert() (cert []byte, err error) {
ca.Config.CSR.CN = "fabric-ca-server"
}
csr := &ca.Config.CSR
if csr.CA == nil {
csr.CA = &cfcsr.CAConfig{}
}
if csr.CA.Expiry == "" {
csr.CA.Expiry = defaultRootCACertificateExpiration
}
req := cfcsr.CertificateRequest{
CN: csr.CN,
Names: csr.Names,
Expand All @@ -302,6 +314,7 @@ func (ca *CA) getCACert() (cert []byte, err error) {
CA: csr.CA,
SerialNumber: csr.SerialNumber,
}
log.Debugf("Root CA certificate request: %+v", req)
// Generate the key/signer
_, cspSigner, err := util.BCCSPKeyRequestGenerate(&req, ca.csp)
if err != nil {
Expand Down Expand Up @@ -365,6 +378,28 @@ func (ca *CA) initConfig() (err error) {
if cfg.CA.Keyfile == "" {
cfg.CA.Keyfile = "ca-key.pem"
}
if cfg.CSR.CA == nil {
cfg.CSR.CA = &cfcsr.CAConfig{}
}
if cfg.CSR.CA.Expiry == "" {
cfg.CSR.CA.Expiry = defaultRootCACertificateExpiration
}
if cfg.Signing == nil {
cfg.Signing = &config.Signing{}
}
cs := cfg.Signing
if cs.Profiles == nil {
cs.Profiles = make(map[string]*config.SigningProfile)
}
caProfile := cs.Profiles["ca"]
initSigningProfile(&caProfile,
defaultIntermediateCACertificateExpiration,
true)
cs.Profiles["ca"] = caProfile
initSigningProfile(
&cs.Default,
defaultIssuedCertificateExpiration,
false)
// Set log level if debug is true
if ca.server.Config.Debug {
log.Level = log.LevelDebug
Expand Down Expand Up @@ -895,3 +930,25 @@ func affiliationPath(name, parent string) string {
}
return fmt.Sprintf("%s.%s", parent, name)
}

func parseDuration(str string) time.Duration {
d, err := time.ParseDuration(str)
if err != nil {
panic(err)
}
return d
}

func initSigningProfile(spp **config.SigningProfile, expiry time.Duration, isCA bool) {
sp := *spp
if sp == nil {
sp = &config.SigningProfile{CAConstraint: config.CAConstraint{IsCA: isCA}}
*spp = sp
}
if sp.Usage == nil {
sp.Usage = []string{"cert sign"}
}
if sp.Expiry == 0 {
sp.Expiry = expiry
}
}
5 changes: 5 additions & 0 deletions lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ func (c *Client) LoadCSRInfo(path string) (*api.CSRInfo, error) {
return &csrInfo, nil
}

// GetCertFilePath returns the path to the certificate file for this client
func (c *Client) GetCertFilePath() string {
return c.certFile
}

// NewGet create a new GET request
func (c *Client) newGet(endpoint string) (*http.Request, error) {
curl, err := c.getURL(endpoint)
Expand Down
7 changes: 7 additions & 0 deletions lib/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (

"github.com/hyperledger/fabric-ca/api"
. "github.com/hyperledger/fabric-ca/lib"
"github.com/hyperledger/fabric-ca/util"
"github.com/stretchr/testify/assert"
)

var (
Expand Down Expand Up @@ -106,6 +108,11 @@ func testRegister(c *Client, t *testing.T) {
t.Fatalf("testRegister failed to store admin identity: %s", err)
}

// Verify that the duration of the newly created enrollment certificate is 1 year
d, err := util.GetCertificateDurationFromFile(c.GetCertFilePath())
assert.NoError(t, err)
assert.True(t, d.Hours() == 8760, fmt.Sprintf("Expecting 8760 but found %f", d.Hours()))

err = c.CheckEnrollment()
if err != nil {
t.Fatalf("testRegister failed to check enrollment: %s", err)
Expand Down
36 changes: 15 additions & 21 deletions lib/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ import (
"testing"
"time"

"github.com/cloudflare/cfssl/config"
"github.com/hyperledger/fabric-ca/api"
. "github.com/hyperledger/fabric-ca/lib"
"github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric/bccsp/factory"
"github.com/stretchr/testify/assert"
)

const (
Expand Down Expand Up @@ -63,6 +63,10 @@ func TestServerInit(t *testing.T) {
if err != nil {
t.Errorf("Third Server init renew failed: %s", err)
}
// Verify that the duration of the newly created certificate is 15 years
d, err := util.GetCertificateDurationFromFile(path.Join(rootDir, "ca-cert.pem"))
assert.NoError(t, err)
assert.True(t, d.Hours() == 131400, fmt.Sprintf("Expecting 131400 but found %f", d.Hours()))
}

func TestRootServer(t *testing.T) {
Expand Down Expand Up @@ -312,10 +316,10 @@ func TestIntermediateServerWithTLS(t *testing.T) {
t.Errorf("Root server stop failed: %s", err)
}

// Check that CSR fields are correctly getting inserted into certificate
// Make sure that the hostname was not inserted into the CA certificate
err = util.CheckHostsInCert(filepath.Join(intermediateDir, "ca-cert.pem"), "testhost")
if err != nil {
t.Error(err)
if err == nil {
t.Error("A CA certificate should not have any hostnames")
}
}

Expand Down Expand Up @@ -601,10 +605,10 @@ func TestMultiCAWithIntermediate(t *testing.T) {
t.Error("Failed to stop server: ", err)
}

// Check that CSR fields are correctly getting inserted into certificate
// Make sure there is no host name in the intermediate CA cert
err = util.CheckHostsInCert(filepath.Join("../testdata/ca/intermediateca/ca1", "ca-cert.pem"), "testhost1")
if err != nil {
t.Error(err)
if err == nil {
t.Error("Intermediate CA should not contain a hostname, but does")
}
}

Expand Down Expand Up @@ -760,6 +764,10 @@ func testIntermediateServer(idx int, t *testing.T) {
if err != nil {
t.Fatalf("Intermediate server init failed: %s", err)
}
// Verify that the duration of the newly created intermediate certificate is 5 years
d, err := util.GetCertificateDurationFromFile(path.Join(intermediateServer.HomeDir, "ca-cert.pem"))
assert.NoError(t, err)
assert.True(t, d.Hours() == 43800, fmt.Sprintf("Expecting 43800 but found %f", d.Hours()))
// Start it
err = intermediateServer.Start()
if err != nil {
Expand Down Expand Up @@ -882,16 +890,6 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S
"org2": nil,
}

defaultSigningProfile := &config.SigningProfile{
Usage: []string{"cert sign"},
ExpiryString: "8000h",
Expiry: time.Hour * 8000,
}

profiles := map[string]*config.SigningProfile{
"ca": defaultSigningProfile,
}

srv := &Server{
Config: &ServerConfig{
Port: port,
Expand All @@ -908,10 +906,6 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S
Registry: CAConfigRegistry{
MaxEnrollments: maxEnroll,
},
Signing: &config.Signing{
Default: defaultSigningProfile,
Profiles: profiles,
},
},
},
HomeDir: home,
Expand Down
47 changes: 33 additions & 14 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,9 @@ func Unmarshal(from []byte, to interface{}, what string) error {
// @param key The pem-encoded key
// @param body The body of an HTTP request
func CreateToken(csp bccsp.BCCSP, cert []byte, key bccsp.Key, body []byte) (string, error) {

block, _ := pem.Decode(cert)
if block == nil {
return "", errors.New("Failed to PEM decode certificate")
}
x509Cert, err := x509.ParseCertificate(block.Bytes)
x509Cert, err := GetX509CertificateFromPEM(cert)
if err != nil {
return "", fmt.Errorf("Error from x509.ParseCertificate: %s", err)
return "", err
}
publicKey := x509Cert.PublicKey

Expand Down Expand Up @@ -307,11 +302,7 @@ func DecodeToken(token string) (*x509.Certificate, string, string, error) {
if err != nil {
return nil, "", "", fmt.Errorf("Failed to decode base64 encoded x509 cert: %s", err)
}
block, _ := pem.Decode(certDecoded)
if block == nil {
return nil, "", "", errors.New("Failed to PEM decode the certificate")
}
x509Cert, err := x509.ParseCertificate(block.Bytes)
x509Cert, err := GetX509CertificateFromPEM(certDecoded)
if err != nil {
return nil, "", "", fmt.Errorf("Error in parsing x509 cert given Block Bytes: %s", err)
}
Expand Down Expand Up @@ -450,19 +441,47 @@ func GetDefaultConfigFile(cmdName string) string {
return path.Join(os.Getenv("HOME"), ".fabric-ca-client", fname)
}

// GetX509CertificateFromPEM converts a PEM buffer to an X509 Certificate
// GetX509CertificateFromPEMFile gets an X509 certificate from a file
func GetX509CertificateFromPEMFile(file string) (*x509.Certificate, error) {
pemBytes, err := ReadFile(file)
if err != nil {
return nil, err
}
x509Cert, err := GetX509CertificateFromPEM(pemBytes)
if err != nil {
return nil, fmt.Errorf("Invalid certificate in %s: %s", file, err)
}
return x509Cert, nil
}

// GetX509CertificateFromPEM get an X509 certificate from bytes in PEM format
func GetX509CertificateFromPEM(cert []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(cert)
if block == nil {
return nil, errors.New("Failed to PEM decode certificate")
}
x509Cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("Error from x509.ParseCertificate: %s", err)
return nil, fmt.Errorf("Error parsing certificate: %s", err)
}
return x509Cert, nil
}

// GetCertificateDurationFromFile returns the validity duration for a certificate
// in a file.
func GetCertificateDurationFromFile(file string) (time.Duration, error) {
cert, err := GetX509CertificateFromPEMFile(file)
if err != nil {
return 0, err
}
return GetCertificateDuration(cert), nil
}

// GetCertificateDuration returns the validity duration for a certificate
func GetCertificateDuration(cert *x509.Certificate) time.Duration {
return cert.NotAfter.Sub(cert.NotBefore)
}

// GetEnrollmentIDFromPEM returns the EnrollmentID from a PEM buffer
func GetEnrollmentIDFromPEM(cert []byte) (string, error) {
x509Cert, err := GetX509CertificateFromPEM(cert)
Expand Down
8 changes: 8 additions & 0 deletions util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,14 @@ func TestCheckHostsInCert(t *testing.T) {
assert.Error(t, err, "Certificate contained no host, should have failed")
}

func TestCertDuration(t *testing.T) {
d, err := GetCertificateDurationFromFile("../testdata/ec.pem")
assert.NoError(t, err)
assert.True(t, d.Hours() == 43800, "Expected certificate duration of 43800h in ec.pem")
_, err = GetCertificateDurationFromFile("bogus.pem")
assert.Error(t, err)
}

func getPEM(file string, t *testing.T) []byte {
buf, err := ioutil.ReadFile(file)
assert.NoError(t, err)
Expand Down

0 comments on commit 1be793d

Please sign in to comment.