Skip to content

Commit

Permalink
Merge "[FAB-5674] Adds tool to generate idemix crypto material"
Browse files Browse the repository at this point in the history
  • Loading branch information
hacera-jonathan authored and Gerrit Code Review committed Oct 31, 2017
2 parents 2c57c64 + d230be7 commit 16d7f50
Show file tree
Hide file tree
Showing 29 changed files with 437 additions and 164 deletions.
88 changes: 88 additions & 0 deletions common/tools/idemixgen/idemixca/idemixca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package idemixca

import (
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/idemix"
"github.com/hyperledger/fabric/msp"
m "github.com/hyperledger/fabric/protos/msp"
amcl "github.com/manudrijvers/amcl/go"
"github.com/pkg/errors"
)

// GenerateIssuerKey invokes Idemix library to generate an issuer (CA) signing key pair
// currently two attributes are supported by the issuer:
// AttributeNameOU is the organization unit name
// AttributeNameRole is the role (member or admin) name
// Generated keys are serialized to bytes
func GenerateIssuerKey() ([]byte, []byte, error) {
rng, err := idemix.GetRand()
if err != nil {
return nil, nil, err
}
AttributeNames := []string{msp.AttributeNameOU, msp.AttributeNameRole}
key, err := idemix.NewIssuerKey(AttributeNames, rng)
if err != nil {
return nil, nil, errors.WithMessage(err, "cannot generate CA key")
}
ipkSerialized, err := proto.Marshal(key.IPk)

return key.ISk, ipkSerialized, err
}

// GenerateMSPConfig creates a new MSP config
// If the new MSP config contains a signer then
// it generates a fresh user secret and issues a credential
// with two attributes (described above)
// using the CA's key pair from the file
// If the new MSP config does not contain a signer
// (meaning it is used only for verification)
// then only a public key of the CA (issuer) is added to the MSP config (besides the name)
func GenerateSignerConfig(isAdmin bool, ouString string, key *idemix.IssuerKey) ([]byte, error) {
attrs := make([]*amcl.BIG, 2)

if ouString == "" {
return nil, errors.Errorf("the OU attribute value is empty")
}

role := m.MSPRole_MEMBER

if isAdmin {
role = m.MSPRole_ADMIN
}

attrs[0] = idemix.HashModOrder([]byte(ouString))
attrs[1] = amcl.NewBIGint(int(role))

rng, err := idemix.GetRand()
if err != nil {
return nil, errors.WithMessage(err, "Error getting PRNG")
}
sk := idemix.RandModOrder(rng)
randCred := idemix.RandModOrder(rng)
ni := idemix.RandModOrder(rng)
msg := idemix.NewCredRequest(sk, randCred, ni, key.IPk, rng)
cred, err := idemix.NewCredential(key, msg, attrs, rng)
if err != nil {
return nil, errors.WithMessage(err, "failed to generate a credential")
}
cred.Complete(randCred)

credBytes, err := proto.Marshal(cred)
if err != nil {
return nil, errors.WithMessage(err, "failed to marshal credential")
}

signer := &m.IdemixMSPSignerConfig{
credBytes,
idemix.BigToBytes(sk),
ouString,
isAdmin,
}
return proto.Marshal(signer)
}
103 changes: 103 additions & 0 deletions common/tools/idemixgen/idemixca/idemixca_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package idemixca

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/idemix"
m "github.com/hyperledger/fabric/msp"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

var testDir = filepath.Join(os.TempDir(), "idemixca-test")
var configName = "testconfig"

func TestIdemixCa(t *testing.T) {
cleanup()

isk, ipkBytes, err := GenerateIssuerKey()
assert.NoError(t, err)

ipk := &idemix.IssuerPublicKey{}
err = proto.Unmarshal(ipkBytes, ipk)
assert.NoError(t, err)

writeVerifierToFile(ipkBytes)

key := &idemix.IssuerKey{isk, ipk}

conf, err := GenerateSignerConfig(false, "OU1", key)
assert.NoError(t, err)
cleanupSigner()
assert.NoError(t, writeSignerToFile(conf))
assert.NoError(t, setupMSP())

conf, err = GenerateSignerConfig(true, "OU1", key)
assert.NoError(t, err)
cleanupSigner()
assert.NoError(t, writeSignerToFile(conf))
assert.NoError(t, setupMSP())

// Without the verifier dir present, setup should give an error
cleanupVerifier()
assert.Error(t, setupMSP())

_, err = GenerateSignerConfig(true, "", key)
assert.EqualError(t, err, "the OU attribute value is empty")
}

func cleanup() error {
// clean up any previous files
err := os.RemoveAll(testDir)
if err != nil {
return nil
}
return os.Mkdir(testDir, os.ModePerm)
}

func cleanupSigner() {
os.RemoveAll(filepath.Join(testDir, m.IdemixConfigDirUser))
}

func cleanupVerifier() {
os.RemoveAll(filepath.Join(testDir, m.IdemixConfigDirMsp))
}

func writeVerifierToFile(ipkBytes []byte) error {
err := os.Mkdir(filepath.Join(testDir, m.IdemixConfigDirMsp), os.ModePerm)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(testDir, m.IdemixConfigDirMsp, m.IdemixConfigFileIssuerPublicKey), ipkBytes, 0644)
}

func writeSignerToFile(signerBytes []byte) error {
err := os.Mkdir(filepath.Join(testDir, m.IdemixConfigDirUser), os.ModePerm)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(testDir, m.IdemixConfigDirUser, m.IdemixConfigFileSigner), signerBytes, 0644)
}

// setupMSP tests whether we can successfully setup an idemix msp
// with the generated config bytes
func setupMSP() error {
// setup an idemix msp from the test directory
msp, err := m.New(&m.IdemixNewOpts{NewBaseOpts: m.NewBaseOpts{Version: m.MSPv1_0}})
if err != nil {
return errors.Wrap(err, "Getting MSP failed")
}
mspConfig, err := m.GetIdemixMspConfig(testDir, "TestName")

return msp.Setup(mspConfig)
}
126 changes: 126 additions & 0 deletions common/tools/idemixgen/idemixgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package main

// idemixgen is a command line tool that generates the CA's keys and
// generates MSP configs for siging and for verification
// This tool can be used to setup the peers and CA to support
// the Identity Mixer MSP

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/tools/idemixgen/idemixca"
"github.com/hyperledger/fabric/idemix"
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/orderer/common/metadata"
"github.com/pkg/errors"
"gopkg.in/alecthomas/kingpin.v2"
)

const (
IdemixDirIssuer = "ca"
IdemixConfigIssuerSecretKey = "IssuerSecretKey"
)

// command line flags
var (
app = kingpin.New("idemixgen", "Utility for generating key material to be used with the Identity Mixer MSP in Hyperledger Fabric")

genIssuerKey = app.Command("ca-keygen", "Generate CA key material")
genSignerConfig = app.Command("signerconfig", "Generate a default signer for this Idemix MSP")
genCredOU = genSignerConfig.Flag("org-unit", "The Organizational Unit of the default signer").Short('u').String()
genCredIsAdmin = genSignerConfig.Flag("admin", "Make the default signer admin").Short('a').Bool()

version = app.Command("version", "Show version information")
)

func main() {
app.HelpFlag.Short('h')

switch kingpin.MustParse(app.Parse(os.Args[1:])) {

case genIssuerKey.FullCommand():
isk, ipk, err := idemixca.GenerateIssuerKey()
handleError(err)

// Prevent overwriting the existing key
path := filepath.Join(IdemixDirIssuer)
checkDirectoryNotExists(path, fmt.Sprintf("Directory %s already exists", path))

path = msp.IdemixConfigDirMsp
checkDirectoryNotExists(path, fmt.Sprintf("Directory %s already exists", path))

// write private and public keys to the file
handleError(os.Mkdir(IdemixDirIssuer, 0770))
handleError(os.Mkdir(msp.IdemixConfigDirMsp, 0770))
writeFile(filepath.Join(IdemixDirIssuer, IdemixConfigIssuerSecretKey), isk)
writeFile(filepath.Join(IdemixDirIssuer, msp.IdemixConfigFileIssuerPublicKey), ipk)
writeFile(filepath.Join(msp.IdemixConfigDirMsp, msp.IdemixConfigFileIssuerPublicKey), ipk)

case genSignerConfig.FullCommand():
config, err := idemixca.GenerateSignerConfig(*genCredIsAdmin, *genCredOU, readIssuerKey())
handleError(err)

path := msp.IdemixConfigDirUser
checkDirectoryNotExists(path, fmt.Sprintf("This MSP config already contains a directory \"%s\"", path))

// Write config to file
handleError(os.Mkdir(msp.IdemixConfigDirUser, 0770))
writeFile(filepath.Join(msp.IdemixConfigDirUser, msp.IdemixConfigFileSigner), config)

case version.FullCommand():
printVersion()
}
}

func printVersion() {
fmt.Println(metadata.GetVersionInfo())
}

// writeFile writes bytes to a file and panics in case of an error
func writeFile(path string, contents []byte) {
handleError(ioutil.WriteFile(path, contents, 0640))
}

// readIssuerKey reads the issuer key from the current directory
func readIssuerKey() *idemix.IssuerKey {
path := filepath.Join(IdemixDirIssuer, IdemixConfigIssuerSecretKey)
isk, err := ioutil.ReadFile(path)
if err != nil {
handleError(errors.Wrapf(err, "failed to open issuer secret key file: %s", path))
}
path = filepath.Join(IdemixDirIssuer, msp.IdemixConfigFileIssuerPublicKey)
ipkBytes, err := ioutil.ReadFile(path)
if err != nil {
handleError(errors.Wrapf(err, "failed to open issuer public key file: %s", path))
}
ipk := &idemix.IssuerPublicKey{}
handleError(proto.Unmarshal(ipkBytes, ipk))
key := &idemix.IssuerKey{isk, ipk}

return key
}

// checkDirectoryNotExists checks whether a directory with the given path already exists and exits if this is the case
func checkDirectoryNotExists(path string, errorMessage string) {
_, err := os.Stat(path)
if err == nil {
handleError(errors.New(errorMessage))
}
}

func handleError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
57 changes: 57 additions & 0 deletions docs/source/idemixgen.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
Identity Mixer MSP configuration generator (idemixgen)
======================================================

This document describes the usage for the idemixgen utility, which can be
used to create configuration files for the identity mixer based MSP.
Two commands are available, one for creating a fresh CA key pair, and one
for creating an MSP config using a previously generated CA key.

Directory Structure
-------------------
The idemixgen tool will create directories with the following structure:

::

- /ca/
IssuerSecretKey
IssuerPublicKey
- /msp/
IssuerPublicKey
- /user/
SignerConfig

The ``ca`` directory contains the issuer secret key and should only be present
for a CA. The ``msp`` directory contains the information required to set up an
MSP verifying idemix signatures. The ``user`` directory specifies a default
signer.

CA Key Generation
-----------------
CA (issuer) keys suitable for identity mixer can be created using command
``idemixgen ca-keygen``. This will create directories ``ca`` and ``msp`` in the
working directory.

Adding a Default Signer
-----------------------
After generating the ``ca`` and ``msp`` directories with
``idemixgen ca-keygen``, a default signer specified in the ``user`` directory
can be added to the config with ``idemixgen signerconfig``.

::

$ idemixgen signerconfig -h
usage: idemixgen signerconfig [<flags>]

Generate a default signer for this Idemix MSP

Flags:
-h, --help Show context-sensitive help (also try --help-long and --help-man).
-u, --org-unit=ORG-UNIT The Organizational Unit of the default signer
-a, --admin Make the default signer admin

For example, we can create a default signer that is a member of organizational
unit "OrgUnit1" and that is an admin with the following command:
::

idemixgen signerconfig -u OrgUnit1 --admin

Loading

0 comments on commit 16d7f50

Please sign in to comment.