diff --git a/lib/client.go b/lib/client.go index f5f67936a..b7049d821 100644 --- a/lib/client.go +++ b/lib/client.go @@ -456,13 +456,15 @@ func (c *Client) newIdemixEnrollmentResponse(identity *Identity, result *common. // Create SignerConfig object with credential bytes from the response // and secret key - isAdmin, _ := strconv.ParseBool(result.Attrs["Role"]) + isAdmin, _ := result.Attrs["IsAdmin"].(bool) + ou, _ := result.Attrs["OU"].(string) + enrollmentID, _ := result.Attrs["EnrollmentID"].(string) signerConfig := &idemixcred.SignerConfig{ Cred: credBytes, Sk: idemix.BigToBytes(sk), IsAdmin: isAdmin, - OrganizationalUnitIdentifier: result.Attrs["OU"], - EnrollmentID: result.Attrs["EnrollmentID"], + OrganizationalUnitIdentifier: ou, + EnrollmentID: enrollmentID, CredentialRevocationInformation: criBytes, } diff --git a/lib/common/serverresponses.go b/lib/common/serverresponses.go index 63e150d82..473591e92 100644 --- a/lib/common/serverresponses.go +++ b/lib/common/serverresponses.go @@ -38,7 +38,7 @@ type IdemixEnrollmentResponseNet struct { // Base64 encoding of proto bytes of idemix.Credential Credential string // Attribute name-value pairs - Attrs map[string]string + Attrs map[string]interface{} // Base64 encoding of proto bytes of idemix.CredentialRevocationInformation CRI string // Base64 encoding of the issuer nonce diff --git a/lib/server/idemix/enroll.go b/lib/server/idemix/enroll.go index dd25adc36..2041fc53e 100644 --- a/lib/server/idemix/enroll.go +++ b/lib/server/idemix/enroll.go @@ -26,7 +26,7 @@ type EnrollmentResponse struct { // Base64 encoding of idemix Credential Credential string // Attribute name-value pairs - Attrs map[string]string + Attrs map[string]interface{} // Base64 encoding of Credential Revocation information CRI string // Base64 encoding of the issuer nonce @@ -183,9 +183,9 @@ func (h *EnrollRequestHandler) GenerateNonce() *fp256bn.BIG { // GetAttributeValues returns attribute values of the caller of Idemix enroll request func (h *EnrollRequestHandler) GetAttributeValues(caller spi.User, ipk *idemix.IssuerPublicKey, - rh *fp256bn.BIG) (map[string]string, []*fp256bn.BIG, error) { + rh *fp256bn.BIG) (map[string]interface{}, []*fp256bn.BIG, error) { rc := []*fp256bn.BIG{} - attrMap := make(map[string]string) + attrMap := make(map[string]interface{}) for _, attrName := range ipk.AttributeNames { if attrName == AttrEnrollmentID { idBytes := []byte(caller.GetName()) @@ -203,18 +203,21 @@ func (h *EnrollRequestHandler) GetAttributeValues(caller spi.User, ipk *idemix.I } else if attrName == AttrRevocationHandle { rc = append(rc, rh) attrMap[attrName] = util.B64Encode(idemix.BigToBytes(rh)) - } else if attrName == AttrRole { + } else if attrName == AttrIsAdmin { isAdmin := false attrObj, err := caller.GetAttribute("isAdmin") if err == nil { isAdmin, err = strconv.ParseBool(attrObj.GetValue()) + if err != nil { + log.Debugf("isAdmin attribute of user %s must be a boolean value", caller.GetName()) + } } role := 0 if isAdmin { role = 1 } - rc = append(rc, fp256bn.NewBIGint(int(role))) - attrMap[attrName] = strconv.FormatBool(isAdmin) + rc = append(rc, fp256bn.NewBIGint(role)) + attrMap[attrName] = isAdmin } else { attrObj, err := caller.GetAttribute(attrName) if err != nil { diff --git a/lib/server/idemix/issuercredential.go b/lib/server/idemix/issuercredential.go index 373d6e7bd..4e3ece3c5 100644 --- a/lib/server/idemix/issuercredential.go +++ b/lib/server/idemix/issuercredential.go @@ -19,8 +19,8 @@ import ( const ( // AttrEnrollmentID is the attribute name for enrollment ID AttrEnrollmentID = "EnrollmentID" - // AttrRole is the attribute name for role - AttrRole = "Role" + // AttrIsAdmin is the attribute name for role + AttrIsAdmin = "IsAdmin" // AttrOU is the attribute name for OU AttrOU = "OU" // AttrRevocationHandle is the attribute name for revocation handle @@ -160,5 +160,5 @@ func (ic *caIdemixCredential) NewIssuerKey() (*idemix.IssuerKey, error) { // GetAttributeNames returns attribute names supported by the Fabric CA for Idemix credentials func GetAttributeNames() []string { - return []string{AttrOU, AttrRole, AttrEnrollmentID, AttrRevocationHandle} + return []string{AttrOU, AttrIsAdmin, AttrEnrollmentID, AttrRevocationHandle} } diff --git a/swagger/swagger-fabric-ca.json b/swagger/swagger-fabric-ca.json index 4871da05d..501cbec8a 100644 --- a/swagger/swagger-fabric-ca.json +++ b/swagger/swagger-fabric-ca.json @@ -319,11 +319,11 @@ "Result": { "type": "object", "properties": { - "cert": { + "Cert": { "type": "string", "description": "The enrollment certificate in base 64 encoded format." }, - "cainfo": { + "ServerInfo": { "type": "object", "properties": { "CAName": { @@ -467,15 +467,15 @@ "Result": { "type": "object", "properties": { - "credential": { + "Credential": { "type": "string", "description": "The credential in base64 encoding of the bytes of the idemix.Credential proto buffer" }, - "nonce": { + "Nonce": { "type": "string", "description": "The nonce in base 64 encoded format" }, - "attrs": { + "Attrs": { "type": "object", "properties": { "OU": { @@ -483,8 +483,8 @@ "description": "The Organizational Unit of the identity that requested the credential" }, "IsAdmin": { - "type": "string", - "description": "'true' if the identity that requested the credential is an admin" + "type": "boolean", + "description": "true if the identity that requested the credential is an admin" }, "EnrollmentID": { "type": "string", @@ -497,11 +497,11 @@ "EnrollmentID" ] }, - "cri": { + "CRI": { "type": "string", "description": "The cri base64 encoding of the bytes of the idemix.CredentialRevocationInformation proto buffer" }, - "cainfo": { + "CAInfo": { "type": "object", "properties": { "CAName": { @@ -797,9 +797,41 @@ "Result": { "type": "object", "properties": { - "cert": { + "Cert": { "type": "string", "description": "The enrollment certificate in base 64 encoded format." + }, + "ServerInfo": { + "type": "object", + "properties": { + "CAName": { + "type": "string", + "description": "The name of the CA that issued the credential" + }, + "CAChain": { + "type": "string", + "description": "Base 64 encoded PEM-encoded certificate chain of the CA's signing certificate" + }, + "IssuerPublicKey": { + "type": "string", + "description": "Base 64 encoding of proto bytes of the CA's Idemix issuer public key" + }, + "IssuerRevocationPublicKey": { + "type": "string", + "description": "Base 64 encoding of PEM-encoded bytes of the CA's Idemix issuer revocation public key" + }, + "Version": { + "type": "string", + "description": "Version of the server" + } + }, + "required": [ + "CAName", + "CAChain", + "IssuerPublicKey", + "IssuerRevocationPublicKey", + "Version" + ] } } }, @@ -964,13 +996,13 @@ "Result": { "type": "object", "properties": { - "credentials": { + "secret": { "type": "string", "description": "The base64 encoded enrollment secret of the newly registered identity." } }, "required": [ - "credentials" + "secret" ] }, "Errors": { @@ -1114,25 +1146,25 @@ "Result": { "type": "object", "properties": { - "revokedcerts": { + "RevokedCerts": { "type": "array", "description": "An array of revoked certificates", "items": { "type": "object", "description": "A revoked certificate", "properties": { - "serial": { + "Serial": { "type": "string", "description": "Serial number of the revoked certificate" }, - "aki": { + "AKI": { "type": "string", "description": "Authority Key Identifier (AKI) of the revoked certificate" } } } }, - "crl": { + "CRL": { "type": "string", "description": "base64 encoded PEM-encoded CRL" } @@ -1272,13 +1304,13 @@ "Result": { "type": "object", "properties": { - "crl": { + "CRL": { "type": "string", "description": "base64 encoded PEM-encoded CRL" } }, "required": [ - "crl" + "CRL" ] }, "Errors": { diff --git a/testdata/IdemixPublicKey b/testdata/IdemixPublicKey index bf73aba80..7a0ff74f4 100644 Binary files a/testdata/IdemixPublicKey and b/testdata/IdemixPublicKey differ diff --git a/testdata/IdemixSecretKey b/testdata/IdemixSecretKey index 23fd85374..b04b06153 100644 --- a/testdata/IdemixSecretKey +++ b/testdata/IdemixSecretKey @@ -1 +1 @@ -T�ɟPbA5��$&�^"v,ԉu�}�V��2 +�Zf���G����+��sO��m��g��a˔mǓ \ No newline at end of file diff --git a/testdata/IdemixSignerConfig b/testdata/IdemixSignerConfig index c9e575da5..9ffc79d8f 100644 --- a/testdata/IdemixSignerConfig +++ b/testdata/IdemixSignerConfig @@ -1 +1 @@ -{"Cred":"CkQKIHI9eH0EytkPRRIa0qSeEnIYjf4nnZ4irAtIaE0ZNWPeEiC9z/0kv0cqxUorqJDRlrq+Jt1TrvmGtmhFHo8AfYci6RJECiCQw6rOdjSXdyHXXIb7FsgqtsGmNkNQXnXLXy7fC17KsxIgCkW6P7tofG5etEBKTGgteeMDA1vbaaAFPRAtbr0yIwsaIAUVLSRASlZc7+ql/4SDKNbbrDxiD6pJEeaZzrvtM/d5IiDDQRbdG9mxvArDKRrrMv1SSUrdymfR9SPhifSMS5G0tiog47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUqIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKiCMaXbltUEEFb3pCL1N7hXfsWepyHP8S7ioH28qtEipGCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","Sk":"ZIHrXtZbdr+JDLXGleDeUD2FPZW5v1wURfH9RfO+JWc=","enrollment_id":"admin","credential_revocation_information":"CAESiAEKIP4MM1C0yWwgKFYPV3wokTrOHFOaEr+EPNImFraJwJ77EiBOpmBXc4rAVNta4cY32BO5JN144ofQNYnSae00o35qKxogcCBG58VCo7N2dw11Ek4+Ue/LJHWNYVhI6Qm0gb7cJ/8iIAVU47zTiMKQQu6mSSl+sp+LTL6AghqYs+ASgRFKrQSbGmCR3hzRBkhQN7JFUGDAOPtpOTwn7HN0ZlIKAi3xGm4qjGZ+4NXZKUV/D99Vmp88fTb6Nh7KrFt/sUPy+jl2y0SksfLzpiejrOvVtcsMt7fivZcggfoqtgUjeBgCxCHPN38="} \ No newline at end of file +{"Cred":"CkQKIANA0MxA4nlDiJLazLl2Shze2Kp7c1IgQQU3Cs0yGtxgEiAmK8THjo4BghMg680kAz0hMxDA6c6yNn6WdvkIMUzoOhJECiBDhNbtosc6w8XWohyXyph53vbnPJVfSpVlOOAQg66kVBIgMSojMFJGyAOcbhpfiJ4R4uF7GtaytklgaSNUJ3vA5g8aIEL0K/xFis/htNVeGc7bcpBgkl6t3ITETr0zN23EfhCuIiDPDYVkzsy4hXtr/HckaIHeTC2esPSKGBPPuV6mVjQHKiog47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUqIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKiCMaXbltUEEFb3pCL1N7hXfsWepyHP8S7ioH28qtEipGCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","Sk":"g5ulj4NjVbZ0tmupk2oHcbhS9owj3ktVZ6ZANTvCbVw=","enrollment_id":"admin","credential_revocation_information":"CAESiAEKIP4MM1C0yWwgKFYPV3wokTrOHFOaEr+EPNImFraJwJ77EiBOpmBXc4rAVNta4cY32BO5JN144ofQNYnSae00o35qKxogcCBG58VCo7N2dw11Ek4+Ue/LJHWNYVhI6Qm0gb7cJ/8iIAVU47zTiMKQQu6mSSl+sp+LTL6AghqYs+ASgRFKrQSbGmCg7mxyBleswYrP/l8uv4j4UuEgHFNW1OA5X1i74RCpTmvaijwkzky5D2+YDI2l0GBBC3A3ea9EvQqn1dN4JG/fDMM4MFf7ph0M//sqkeppR6KOl1p+ECZVVeNjn2omXcc="} \ No newline at end of file diff --git a/util/util.go b/util/util.go index 5c80f625b..831c37ef5 100644 --- a/util/util.go +++ b/util/util.go @@ -355,7 +355,7 @@ func GetRSAPrivateKey(raw []byte) (*rsa.PrivateKey, error) { if err == nil { return RSAprivKey, nil } - key, err2 := x509.ParsePKCS8PrivateKey(raw) + key, err2 := x509.ParsePKCS8PrivateKey(decoded.Bytes) if err2 == nil { switch key.(type) { case *ecdsa.PrivateKey: diff --git a/util/util_test.go b/util/util_test.go index 3062ec89c..e0756f1a4 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -18,17 +18,23 @@ package util import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" "fmt" "io" "io/ioutil" + "math/big" "net/http" "os" + "path" "path/filepath" "strings" "testing" - "math/big" - "github.com/hyperledger/fabric/bccsp/factory" _ "github.com/mattn/go-sqlite3" "github.com/spf13/viper" @@ -112,6 +118,24 @@ func TestECCreateToken(t *testing.T) { } } +func TestDecodeToken(t *testing.T) { + token := "x.y.z" + _, _, _, err := DecodeToken(token) + assert.Error(t, err, "Decode should fail if the token has more than two parts") + + token = "x" + _, _, _, err = DecodeToken(token) + assert.Error(t, err, "Decode should fail if the token has less than two parts") + + token = "x.y" + _, _, _, err = DecodeToken(token) + assert.Error(t, err, "Decode should fail if the 1st part of the token is not in base64 encoded format") + + fakecert := B64Encode([]byte("hello")) + token = fakecert + ".y" + _, _, _, err = DecodeToken(token) + assert.Error(t, err, "Decode should fail if the 1st part of the token is not base64 bytes of a X509 cert") +} func TestGetX509CertFromPem(t *testing.T) { certBuffer, error := ioutil.ReadFile(getPath("ec.pem")) @@ -376,12 +400,21 @@ func TestReadFile(t *testing.T) { } func TestWriteFile(t *testing.T) { + testdir, err := ioutil.TempDir(".", "writefiletest") + if err != nil { + t.Fatalf("Failed to create temp directory: %s", err.Error()) + } + defer os.RemoveAll(testdir) testData := []byte("foo") - err := WriteFile("../testdata/test.txt", testData, 0777) + err = WriteFile(path.Join(testdir, "test.txt"), testData, 0777) + assert.NoError(t, err) + readOnlyDir := path.Join(testdir, "readonlydir") + err = os.MkdirAll(readOnlyDir, 4444) if err != nil { - t.Error("Failed to write file, error: ", err) + t.Fatalf("Failed to create directory: %s", err.Error()) } - os.Remove("../testdata/test.txt") + err = WriteFile(path.Join(readOnlyDir, "test/test.txt"), testData, 0777) + assert.Error(t, err, "Should fail to create 'test' directory as the parent directory is read only") } func getPath(file string) string { @@ -600,6 +633,25 @@ func TestGetSerialAsHex(t *testing.T) { func TestECPrivateKey(t *testing.T) { _, err := GetECPrivateKey(getPEM("../testdata/ec-key.pem", t)) assert.NoError(t, err) + + rsaKey, err := rsa.GenerateKey(rand.Reader, 256) + if err != nil { + t.Fatalf("Failed to create rsa key: %s", err.Error()) + } + encodedPK, err := x509.MarshalPKCS8PrivateKey(rsaKey) + if err != nil { + t.Fatalf("Failed to marshal RSA private key: %s", err.Error()) + } + + pemEncodedPK := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encodedPK}) + _, err = GetECPrivateKey(pemEncodedPK) + assert.Error(t, err) + + _, err = GetECPrivateKey([]byte("hello")) + assert.Error(t, err) + + _, err = GetECPrivateKey(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("hello")})) + assert.Error(t, err) } func TestPKCS8WrappedECPrivateKey(t *testing.T) { @@ -608,8 +660,40 @@ func TestPKCS8WrappedECPrivateKey(t *testing.T) { } func TestRSAPrivateKey(t *testing.T) { - _, err := GetRSAPrivateKey(getPEM("../testdata/rsa-key.pem", t)) + _, err := GetRSAPrivateKey([]byte("hello")) + assert.Error(t, err) + + _, err = GetRSAPrivateKey(getPEM("../testdata/rsa-key.pem", t)) + assert.NoError(t, err) + + rsaKey, err := rsa.GenerateKey(rand.Reader, 256) + if err != nil { + t.Fatalf("Failed to create rsa key: %s", err.Error()) + } + encodedPK, err := x509.MarshalPKCS8PrivateKey(rsaKey) + if err != nil { + t.Fatalf("Failed to marshal RSA private key: %s", err.Error()) + } + + pemEncodedPK := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encodedPK}) + _, err = GetRSAPrivateKey(pemEncodedPK) assert.NoError(t, err) + + _, err = GetRSAPrivateKey(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("hello")})) + assert.Error(t, err) + + ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("Failed to create rsa key: %s", err.Error()) + } + encodedPK, err = x509.MarshalPKCS8PrivateKey(ecdsaKey) + if err != nil { + t.Fatalf("Failed to marshal RSA private key: %s", err.Error()) + } + + pemEncodedPK = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encodedPK}) + _, err = GetRSAPrivateKey(pemEncodedPK) + assert.Error(t, err) } func TestCheckHostsInCert(t *testing.T) {