Skip to content

Commit

Permalink
More tcert library APIs prior to COP integration
Browse files Browse the repository at this point in the history
This contains more tcert library APIs to enable code
reuse and also to minimize the change set size.
In particular, there are some more utility functions and
one new object in keytree.go.  This will be used to generate
a tree of derived keys for affiliation groups.

The next change set which will shortly follow will
integrate the tcert library into the COP server and client.

The changes to cli/cop_test.go were necessary to fix transient
test failures.  There is no need to start a server when testing
cop.go to get test coverage, so I simplified test cases.
The server and client commands are tested under server and client
packages respectively, where an actual server is started.

See https://jira.hyperledger.org/browse/FAB-876

Change-Id: I025722ef8acf6d524fde2a5ea4d471c9ab532fd3
Signed-off-by: Keith Smith <[email protected]>
  • Loading branch information
Keith Smith committed Jan 3, 2017
1 parent 107fdff commit c11e7f4
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 245 deletions.
144 changes: 13 additions & 131 deletions cli/cop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,97 +16,27 @@ limitations under the License.

package main

import (
"fmt"
"os"
"testing"
"time"
import "testing"

cop "github.com/hyperledger/fabric-cop/api"
server "github.com/hyperledger/fabric-cop/cli/server"
"github.com/hyperledger/fabric-cop/idp"
)

type Admin struct {
User string
Pass []byte
Type string
Group string
Attributes []idp.Attribute
}

const (
CERT string = "../testdata/ec.pem"
KEY string = "../testdata/ec-key.pem"
CFG string = "../testdata/testconfig.json"
CSR string = "../testdata/csr.json"
REG string = "../testdata/registerrequest.json"
DBCONFIG string = "../testdata/enrolltest.json"
)

var (
Registrar = Admin{User: "admin", Pass: []byte("adminpw"), Type: "User", Group: "bank_a", Attributes: []idp.Attribute{idp.Attribute{Name: "hf.Registrar.DelegateRoles", Value: "client,validator,auditor"}}}
testEnroll = cop.RegisterRequest{User: "testEnroll", Type: "client", Group: "bank_a", Attributes: []idp.Attribute{idp.Attribute{Name: "role", Value: "client"}}}
)

var serverStarted bool
var serverExitCode = 0

const (
enrollPath = "/tmp/enrolltest"
)

// Test the server start command
func TestStartServer(t *testing.T) {
fmt.Println("running TestStartServer ...")
os.RemoveAll("/tmp/enrollTest")
rtn := startServer()
if rtn != 0 {
t.Errorf("Failed to start server with return code: %d", rtn)
t.FailNow()
}
fmt.Println("passed TestStartServer")
}

func TestRegister(t *testing.T) {
fmt.Println("running TestRegister ...")
r := server.NewRegisterUser()
_, err := r.RegisterUser(testEnroll.User, testEnroll.Type, testEnroll.Group, testEnroll.Attributes, Registrar.User)
if err != nil {
fmt.Printf("RegisterUser failed: %s\n", err)
t.Errorf("Failed to register user: %s, err: %s", testEnroll.User, err)
}
fmt.Println("passed TestRegister")
}

func TestEnroll(t *testing.T) {
fmt.Println("running TestEnroll ...")
rtn := enroll("admin", "adminpw")
if rtn != 0 {
fmt.Printf("enroll failed: rtn=%d\n", rtn)
t.Errorf("Failed to enroll with return code: %d", rtn)
func TestClientCommand(t *testing.T) {
rtn := COPMain([]string{"cop", "client"})
if rtn == 0 {
t.Error("TestClientCommand passed but should have failed")
}
fmt.Println("passed TestEnroll")
}

func TestReenroll(t *testing.T) {
fmt.Println("running TestReenroll ...")
rtn := reenroll()
if rtn != 0 {
fmt.Printf("reenroll failed: rtn=%d\n", rtn)
t.Errorf("Failed to reenroll with return code: %d", rtn)
func TestServerCommand(t *testing.T) {
rtn := COPMain([]string{"cop", "server"})
if rtn == 0 {
t.Error("TestServerCommand passed but should have failed")
}
fmt.Println("passed TestReenroll")
}

func TestCFSSL(t *testing.T) {
fmt.Println("running TestCFSSL ...")
rtn := cfssl()
if rtn != 0 {
fmt.Printf("TestCFSSL failed: rtn=%d\n", rtn)
t.Errorf("Failed to test CFSSL with return code: %d", rtn)
func TestCFSSLCommand(t *testing.T) {
rtn := COPMain([]string{"cop", "cfssl"})
if rtn == 0 {
t.Error("TestCFSSLCommand passed but should have failed")
}
fmt.Println("passed TestCFSSL")
}

func TestBogusCommand(t *testing.T) {
Expand All @@ -115,51 +45,3 @@ func TestBogusCommand(t *testing.T) {
t.Error("TestBogusCommand passed but should have failed")
}
}

func startServer() int {
if !serverStarted {
serverStarted = true
fmt.Println("starting COP server ...")
os.Setenv("COP_HOME", enrollPath)
go runServer()
time.Sleep(3 * time.Second)
fmt.Println("COP server started")
} else {
fmt.Println("COP server already started")
}
return serverExitCode
}

func runServer() {
os.Setenv("COP_DEBUG", "true")
os.Setenv("COP_HOME", enrollPath)
serverExitCode = COPMain([]string{"cop", "server", "start", "-ca", CERT, "-ca-key", KEY, "-config", CFG, "-db-config", DBCONFIG})
}

func enroll(user, pass string) int {
fmt.Printf("enrolling user '%s' with password '%s' ...\n", user, pass)
rtn := COPMain([]string{"cop", "client", "enroll", user, pass, "http://localhost:8888", CSR})
fmt.Printf("enroll result is '%d'\n", rtn)
return rtn
}

func reenroll() int {
fmt.Println("reenrolling ...")
rtn := COPMain([]string{"cop", "client", "reenroll", "http://localhost:8888", CSR})
fmt.Printf("reenroll result is '%d'\n", rtn)
return rtn
}

func cfssl() int {
fmt.Println("cfssl ...")
rtn := COPMain([]string{"cop", "cfssl", "version"})
fmt.Printf("cfssl result is '%d'\n", rtn)
return rtn
}

func register(file string) int {
fmt.Printf("register file '%s' ...\n", file)
rtn := COPMain([]string{"cop", "client", "register", file, "http://localhost:8888", "loglevel=0"})
fmt.Printf("register result is '%d'\n", rtn)
return rtn
}
5 changes: 5 additions & 0 deletions cli/server/dbutil/dbutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ func NewUserRegistrySQLLite3(datasource string) (*sqlx.DB, bool, error) {
return nil, false, err
}

log.Debug("Successfully opened sqlite3 DB")

return db, exists, nil
}

Expand All @@ -97,14 +99,17 @@ func createSQLiteDBTables(datasource string) error {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS users (id VARCHAR(64), token bytea, type VARCHAR(64), user_group VARCHAR(64), attributes VARCHAR(256), state INTEGER, max_enrollments INTEGER, serial_number bytea, authority_key_identifier bytea)"); err != nil {
return err
}
log.Debug("Created users table")

if _, err := db.Exec("CREATE TABLE IF NOT EXISTS groups (name VARCHAR(64), parent_id VARCHAR(64), prekey VARCHAR(48))"); err != nil {
return err
}
log.Debug("Created groups table")

if _, err := db.Exec("CREATE TABLE IF NOT EXISTS certificates (id VARCHAR(64), serial_number bytea NOT NULL, authority_key_identifier bytea NOT NULL, ca_label bytea, status bytea NOT NULL, reason int, expiry timestamp, revoked_at timestamp, pem bytea NOT NULL, PRIMARY KEY(serial_number, authority_key_identifier))"); err != nil {
return err
}
log.Debug("Created certificates table")

return nil
}
Expand Down
94 changes: 94 additions & 0 deletions lib/tcert/keytree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tcert

import (
"fmt"
"strings"

"github.com/hyperledger/fabric/core/crypto/bccsp"
)

/*
* A key tree is a hierarchy of derived keys with a single root key.
* Each node in the tree has a key and a name, where the key is secret
* and the name may be public. If the secret associated with a node
* is known, then the secret of each node in it's sub-tree can be derived
* if the name of the nodes are known; however, it is not possible to
* derive the keys associated with other nodes in the tree which are not
* part of this sub-tree.
*
* This data structure is useful to support releasing a secret associated
* with any node to an auditor without giving the auditor access to all
* nodes in the tree.
*/

const (
keyPathSep = "/"
)

// NewKeyTree is the constructor for a key tree
func NewKeyTree(bccspMgr bccsp.BCCSP, rootKey bccsp.Key) *KeyTree {
tree := new(KeyTree)
tree.bccspMgr = bccspMgr
tree.rootKey = rootKey
tree.keys = make(map[string]bccsp.Key)
return tree
}

// KeyTree is a tree of derived keys
type KeyTree struct {
bccspMgr bccsp.BCCSP
rootKey bccsp.Key
keys map[string]bccsp.Key
}

// GetKey returns a key associated with a specific path in the tree.
func (m *KeyTree) GetKey(path []string) (bccsp.Key, error) {
if path == nil || len(path) == 0 {
return m.rootKey, nil
}
pathStr := strings.Join(path, keyPathSep)
key := m.keys[pathStr]
if key != nil {
return key, nil
}
parentKey, err := m.GetKey(path[0 : len(path)-1])
if err != nil {
return nil, err
}
childName := path[len(path)-1]
key, err = m.deriveChildKey(parentKey, childName, pathStr)
if err != nil {
return nil, err
}
m.keys[pathStr] = key
return key, nil
}

// Given a parentKey and a childName, derive the child's key
func (m *KeyTree) deriveChildKey(parentKey bccsp.Key, childName, path string) (bccsp.Key, error) {
opts := &bccsp.HMACDeriveKeyOpts{
Temporary: true,
Arg: []byte(childName),
}
key, err := m.bccspMgr.KeyDeriv(parentKey, opts)
if err != nil {
return nil, fmt.Errorf("Failed to derive key %s: %s", path, err)
}
return key, nil
}
62 changes: 62 additions & 0 deletions lib/tcert/keytree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tcert

import (
"bytes"
"testing"

"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric/core/crypto/bccsp"
"github.com/hyperledger/fabric/core/crypto/bccsp/factory"
)

func TestKeyTree(t *testing.T) {

log.Level = log.LevelDebug

path := []string{"A", "B", "C"}

csp, err := factory.GetDefault()
if err != nil {
t.Fatalf("Failed to get BCCSP factory: %s", err)
}
opts := &bccsp.AES256KeyGenOpts{Temporary: true}
rootKey, err := csp.KeyGen(opts)
if err != nil {
t.Fatalf("Failed to create root key: %s", err)
}

tree1 := NewKeyTree(csp, rootKey)
key1, err := tree1.GetKey(path)
if err != nil {
t.Fatalf("Failed to get key1: %s", err)
}

tree2 := NewKeyTree(csp, rootKey)
key2, err := tree2.GetKey(path)
if err != nil {
t.Fatalf("Failed to get key2: %s", err)
}

ski1 := key1.SKI()
ski2 := key2.SKI()
if !bytes.Equal(ski1, ski2) {
t.Errorf("keys are not equal %s != %s", ski1, ski2)
}

}
27 changes: 21 additions & 6 deletions lib/tcert/tcert.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,39 @@ var (
tcertSubject = pkix.Name{CommonName: "Fabric Transaction Certificate"}
)

// NewMgr is the constructor for a TCert manager
// @parameter caCert is used for extracting CA data to associate with issued certificates
// LoadMgr is the constructor for a TCert manager given key and certificate file names
// @parameter caKeyFile is the file name for the CA's key
// @parameter caCertFile is the file name for the CA's cert
func LoadMgr(caKeyFile, caCertFile string) (*Mgr, error) {
caKey, err := LoadKey(caKeyFile)
if err != nil {
return nil, err
}
caCert, err := LoadCert(caCertFile)
if err != nil {
return nil, err
}
return NewMgr(caKey, caCert)
}

// NewMgr is the constructor for a TCert manager given a key and an x509 certificate
// @parameter caKey is used for signing a certificate request
func NewMgr(caCert *x509.Certificate, caKey interface{}) (*Mgr, error) {
// @parameter caCert is used for extracting CA data to associate with issued certificates
func NewMgr(caKey interface{}, caCert *x509.Certificate) (*Mgr, error) {
mgr := new(Mgr)
mgr.CACert = caCert
mgr.CAKey = caKey
mgr.CACert = caCert
mgr.ValidityPeriod = time.Hour * 24 * 365 // default to 1 year
mgr.MaxAllowedBatchSize = 1000
return mgr, nil
}

// Mgr is the manager for the TCert library
type Mgr struct {
// CACert is used for extracting CA data to associate with issued certificates
CACert *x509.Certificate
// CAKey is used for signing a certificate request
CAKey interface{}
// CACert is used for extracting CA data to associate with issued certificates
CACert *x509.Certificate
// ValidityPeriod is the duration that the issued certificate will be valid
// unless the user requests a shorter validity period.
// The default value is 1 year.
Expand Down
Loading

0 comments on commit c11e7f4

Please sign in to comment.