diff --git a/cmd/cryptogen/main.go b/cmd/cryptogen/main.go index 6fdf067eb2e..6392986b0bf 100644 --- a/cmd/cryptogen/main.go +++ b/cmd/cryptogen/main.go @@ -53,21 +53,23 @@ type NodeTemplate struct { type NodeSpec struct { isAdmin bool - Hostname string `yaml:"Hostname"` - CommonName string `yaml:"CommonName"` - Country string `yaml:"Country"` - Province string `yaml:"Province"` - Locality string `yaml:"Locality"` - OrganizationalUnit string `yaml:"OrganizationalUnit"` - StreetAddress string `yaml:"StreetAddress"` - PostalCode string `yaml:"PostalCode"` - SANS []string `yaml:"SANS"` - PublicKeyAlgorithm string `yaml:"PublicKeyAlgorithm"` + Hostname string `yaml:"Hostname"` + CommonName string `yaml:"CommonName"` + Country string `yaml:"Country"` + Province string `yaml:"Province"` + Locality string `yaml:"Locality"` + OrganizationalUnit string `yaml:"OrganizationalUnit"` + StreetAddress string `yaml:"StreetAddress"` + PostalCode string `yaml:"PostalCode"` + SANS []string `yaml:"SANS"` + PublicKeyAlgorithm string `yaml:"PublicKeyAlgorithm"` + Attrs map[string]string `yaml:"Attrs"` } type UsersSpec struct { - Count int `yaml:"Count"` - PublicKeyAlgorithm string `yaml:"PublicKeyAlgorithm"` + Count int `yaml:"Count"` + PublicKeyAlgorithm string `yaml:"PublicKeyAlgorithm"` + Attrs map[string]string `yaml:"Attrs"` } type OrgSpec struct { @@ -198,6 +200,8 @@ PeerOrgs: Users: Count: 1 PublicKeyAlgorithm: "ecdsa" + Attrs: + abac.creator: "true" # --------------------------------------------------------------------------- # Org2: See "Org1" for full specification @@ -348,6 +352,7 @@ func extendPeerOrg(orgSpec OrgSpec) { user := NodeSpec{ CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName), PublicKeyAlgorithm: publicKeyAlg, + Attrs: orgSpec.Users.Attrs, } users = append(users, user) @@ -567,6 +572,7 @@ func generatePeerOrg(baseDir string, orgSpec OrgSpec) { user := NodeSpec{ CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName), PublicKeyAlgorithm: publicKeyAlg, + Attrs: orgSpec.Users.Attrs, } users = append(users, user) @@ -638,7 +644,7 @@ func generateNodes(baseDir string, nodes []NodeSpec, signCA *ca.CA, tlsCA *ca.CA if node.isAdmin && nodeOUs { currentNodeType = msp.ADMIN } - err := msp.GenerateLocalMSP(nodeDir, node.CommonName, node.SANS, signCA, tlsCA, currentNodeType, nodeOUs, node.PublicKeyAlgorithm) + err := msp.GenerateLocalMSP(nodeDir, node.CommonName, node.SANS, signCA, tlsCA, currentNodeType, nodeOUs, node.PublicKeyAlgorithm, node.Attrs) if err != nil { fmt.Printf("Error generating local MSP for %v:\n%v\n", node, err) os.Exit(1) diff --git a/internal/cryptogen/ca/ca.go b/internal/cryptogen/ca/ca.go index 29b03b23ae0..6aa71812a3c 100644 --- a/internal/cryptogen/ca/ca.go +++ b/internal/cryptogen/ca/ca.go @@ -13,6 +13,8 @@ import ( "crypto/sha256" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" + "encoding/json" "encoding/pem" "fmt" "math/big" @@ -26,6 +28,8 @@ import ( "github.com/pkg/errors" ) +var attrOID = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 7, 8, 1} + type CA struct { Name string Country string @@ -112,13 +116,37 @@ func NewCA( return ca, err } +// attrExtension mirrors the fabric-ca Attributes struct for JSON marshalling. +type attrExtension struct { + Attrs map[string]string `json:"attrs"` +} + +func (ca *CA) addAttributesToCert(attrs map[string]string, cert *x509.Certificate) error { + if len(attrs) == 0 { + return nil + } + buf, err := json.Marshal(attrExtension{Attrs: attrs}) + if err != nil { + return errors.Wrap(err, "Failed to marshal attributes") + } + ext := pkix.Extension{ + Id: attrOID, + Critical: false, + Value: buf, + } + cert.ExtraExtensions = append(cert.ExtraExtensions, ext) + return nil +} + // SignCertificate creates a signed certificate based on a built-in template -// and saves it in baseDir/name +// and saves it in baseDir/name. attrs, if non-nil, are embedded as a custom +// X.509 extension (OID 1.2.3.4.5.6.7.8.1) using the same format as fabric-ca. func (ca *CA) SignCertificate( baseDir, name string, orgUnits, alternateNames []string, + attrs map[string]string, pub crypto.PublicKey, ku x509.KeyUsage, eku []x509.ExtKeyUsage, @@ -151,6 +179,11 @@ func (ca *CA) SignCertificate( } } + err := ca.addAttributesToCert(attrs, &template) + if err != nil { + return nil, err + } + cert, err := genCertificate( baseDir, name, diff --git a/internal/cryptogen/ca/ca_test.go b/internal/cryptogen/ca/ca_test.go index b4ff09c51df..d65a011ece4 100644 --- a/internal/cryptogen/ca/ca_test.go +++ b/internal/cryptogen/ca/ca_test.go @@ -8,6 +8,9 @@ package ca_test import ( "crypto/ecdsa" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/json" "net" "os" "path/filepath" @@ -68,6 +71,7 @@ func TestLoadCertificateECDSA(t *testing.T) { testName3, nil, nil, + nil, &priv.(*ecdsa.PrivateKey).PublicKey, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, @@ -186,6 +190,7 @@ func TestGenerateSignCertificate(t *testing.T) { testName, nil, nil, + nil, &priv.PublicKey, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, @@ -201,6 +206,7 @@ func TestGenerateSignCertificate(t *testing.T) { testName, nil, nil, + nil, &priv.PublicKey, x509.KeyUsageDigitalSignature, []x509.ExtKeyUsage{}, @@ -210,7 +216,7 @@ func TestGenerateSignCertificate(t *testing.T) { // make sure ous are correctly set ous := []string{"TestOU", "PeerOU"} - cert, err = rootCA.SignCertificate(certDir, testName, ous, nil, &priv.PublicKey, + cert, err = rootCA.SignCertificate(certDir, testName, ous, nil, nil, &priv.PublicKey, x509.KeyUsageDigitalSignature, []x509.ExtKeyUsage{}) require.NoError(t, err) require.Contains(t, cert.Subject.OrganizationalUnit, ous[0]) @@ -218,7 +224,7 @@ func TestGenerateSignCertificate(t *testing.T) { // make sure sans are correctly set sans := []string{testName2, testName3, testIP} - cert, err = rootCA.SignCertificate(certDir, testName, nil, sans, &priv.PublicKey, + cert, err = rootCA.SignCertificate(certDir, testName, nil, sans, nil, &priv.PublicKey, x509.KeyUsageDigitalSignature, []x509.ExtKeyUsage{}) require.NoError(t, err) require.Contains(t, cert.DNSNames, testName2) @@ -231,7 +237,7 @@ func TestGenerateSignCertificate(t *testing.T) { require.Equal(t, true, checkForFile(pemFile), "Expected to find file "+pemFile) - _, err = rootCA.SignCertificate(certDir, "empty/CA", nil, nil, &priv.PublicKey, + _, err = rootCA.SignCertificate(certDir, "empty/CA", nil, nil, nil, &priv.PublicKey, x509.KeyUsageKeyEncipherment, []x509.ExtKeyUsage{x509.ExtKeyUsageAny}) require.Error(t, err, "Bad name should fail") @@ -240,9 +246,29 @@ func TestGenerateSignCertificate(t *testing.T) { Name: "badCA", SignCert: &x509.Certificate{}, } - _, err = badCA.SignCertificate(certDir, testName, nil, nil, &ecdsa.PublicKey{}, + _, err = badCA.SignCertificate(certDir, testName, nil, nil, nil, &ecdsa.PublicKey{}, x509.KeyUsageKeyEncipherment, []x509.ExtKeyUsage{x509.ExtKeyUsageAny}) require.Error(t, err, "Empty CA should not be able to sign") + + // verify attributes are embedded as the fabric-ca extension + attrs := map[string]string{"abac.creator": "true", "org.role": "member"} + cert, err = rootCA.SignCertificate(certDir, testName, nil, nil, attrs, &priv.PublicKey, + x509.KeyUsageDigitalSignature, []x509.ExtKeyUsage{}) + require.NoError(t, err) + attrOID := asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 7, 8, 1} + var attrExt *pkix.Extension + for i := range cert.Extensions { + if cert.Extensions[i].Id.Equal(attrOID) { + attrExt = &cert.Extensions[i] + break + } + } + require.NotNil(t, attrExt, "attribute extension should be present") + var attrData struct { + Attrs map[string]string `json:"attrs"` + } + require.NoError(t, json.Unmarshal(attrExt.Value, &attrData)) + require.Equal(t, attrs, attrData.Attrs) } func checkForFile(file string) bool { diff --git a/internal/cryptogen/msp/msp.go b/internal/cryptogen/msp/msp.go index a25ad71b888..ad798a3478b 100644 --- a/internal/cryptogen/msp/msp.go +++ b/internal/cryptogen/msp/msp.go @@ -51,6 +51,7 @@ func GenerateLocalMSP( nodeType int, nodeOUs bool, keyAlg string, + attrs map[string]string, ) error { // create folder structure mspDir := filepath.Join(baseDir, "msp") @@ -88,6 +89,7 @@ func GenerateLocalMSP( name, ous, nil, + attrs, getPublicKey(priv), x509.KeyUsageDigitalSignature, []x509.ExtKeyUsage{}, @@ -150,6 +152,7 @@ func GenerateLocalMSP( name, nil, sans, + nil, getPublicKey(tlsPrivKey), x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, []x509.ExtKeyUsage{ @@ -242,6 +245,7 @@ func GenerateVerifyingMSP( signCA.Name, nil, nil, + nil, getPublicKey(priv), x509.KeyUsageDigitalSignature, []x509.ExtKeyUsage{}, diff --git a/internal/cryptogen/msp/msp_test.go b/internal/cryptogen/msp/msp_test.go index f01ec232db7..4ba473df21a 100644 --- a/internal/cryptogen/msp/msp_test.go +++ b/internal/cryptogen/msp/msp_test.go @@ -36,7 +36,7 @@ var testDir = filepath.Join(os.TempDir(), "msp-test") func testGenerateLocalMSP(t *testing.T, nodeOUs bool) { cleanup(testDir) - err := msp.GenerateLocalMSP(testDir, testName, nil, &ca.CA{}, &ca.CA{}, msp.PEER, nodeOUs, ECDSA) + err := msp.GenerateLocalMSP(testDir, testName, nil, &ca.CA{}, &ca.CA{}, msp.PEER, nodeOUs, ECDSA, nil) require.Error(t, err, "Empty CA should have failed") caDir := filepath.Join(testDir, "ca") @@ -65,7 +65,7 @@ func testGenerateLocalMSP(t *testing.T, nodeOUs bool) { require.Equal(t, testPostalCode, signCA.SignCert.Subject.PostalCode[0], "Failed to match postalCode") // generate local MSP for nodeType=PEER - err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.PEER, nodeOUs, ECDSA) + err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.PEER, nodeOUs, ECDSA, nil) require.NoError(t, err, "Failed to generate local MSP") // check to see that the right files were generated/saved @@ -97,7 +97,7 @@ func testGenerateLocalMSP(t *testing.T, nodeOUs bool) { } // generate local MSP for nodeType=CLIENT - err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.CLIENT, nodeOUs, ECDSA) + err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.CLIENT, nodeOUs, ECDSA, nil) require.NoError(t, err, "Failed to generate local MSP") // check all for _, file := range mspFiles { @@ -111,10 +111,10 @@ func testGenerateLocalMSP(t *testing.T, nodeOUs bool) { } tlsCA.Name = "test/fail" - err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.CLIENT, nodeOUs, ECDSA) + err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.CLIENT, nodeOUs, ECDSA, nil) require.Error(t, err, "Should have failed with CA name 'test/fail'") signCA.Name = "test/fail" - err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.ORDERER, nodeOUs, ECDSA) + err = msp.GenerateLocalMSP(testDir, testName, nil, signCA, tlsCA, msp.ORDERER, nodeOUs, ECDSA, nil) require.Error(t, err, "Should have failed with CA name 'test/fail'") t.Log(err) cleanup(testDir) diff --git a/pr.md b/pr.md new file mode 100644 index 00000000000..e69de29bb2d