diff --git a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixCredential.java b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixCredential.java index 3dd87162..f19828f9 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixCredential.java +++ b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixCredential.java @@ -87,7 +87,7 @@ public class IdemixCredential { * * @param proto a protobuf representation of a credential */ - IdemixCredential(Idemix.Credential proto) { + public IdemixCredential(Idemix.Credential proto) { if (proto == null) { throw new IllegalArgumentException("Cannot create idemix credential from null input"); } @@ -118,7 +118,7 @@ BIG getS() { return S; } - byte[][] getAttrs() { + public byte[][] getAttrs() { return Attrs; } @@ -129,7 +129,7 @@ byte[][] getAttrs() { * @param ipk the public key of the issuer * @return true iff valid */ - boolean verify(BIG sk, IdemixIssuerPublicKey ipk) { + public boolean verify(BIG sk, IdemixIssuerPublicKey ipk) { if (ipk == null || Attrs.length != ipk.getAttributeNames().length) { return false; } diff --git a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixIssuerPublicKey.java b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixIssuerPublicKey.java index 080be5b0..a5e33599 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixIssuerPublicKey.java +++ b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixIssuerPublicKey.java @@ -122,7 +122,7 @@ public class IdemixIssuerPublicKey { * * @param proto a protobuf representation of an issuer public key */ - IdemixIssuerPublicKey(Idemix.IssuerPublicKey proto) { + public IdemixIssuerPublicKey(Idemix.IssuerPublicKey proto) { // check for bad input if (proto == null) { throw new IllegalArgumentException("Cannot create IdemixIssuerPublicKey from null input"); @@ -160,7 +160,7 @@ public class IdemixIssuerPublicKey { * * @return true iff valid */ - boolean check() { + public boolean check() { // check formalities of IdemixIssuerPublicKey if (AttributeNames == null || Hsk == null || HRand == null || HAttrs == null || BarG1 == null || BarG1.is_infinity() || BarG2 == null @@ -221,7 +221,7 @@ Idemix.IssuerPublicKey toProto() { /** * @return The names of the attributes certified with this issuer public key */ - String[] getAttributeNames() { + public String[] getAttributeNames() { return AttributeNames; } @@ -244,7 +244,7 @@ protected ECP2 getW() { /** * @return A digest of this issuer public key */ - byte[] getHash() { + public byte[] getHash() { return Hash; } } diff --git a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonym.java b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonym.java index 5fb6771b..f38210d5 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonym.java +++ b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonym.java @@ -35,7 +35,7 @@ public class IdemixPseudonym { * @param sk the secret key of the user * @param ipk the public key of the issuer */ - IdemixPseudonym(BIG sk, IdemixIssuerPublicKey ipk) { + public IdemixPseudonym(BIG sk, IdemixIssuerPublicKey ipk) { if (sk == null || ipk == null) { throw new IllegalArgumentException("Cannot construct idemix pseudonym from null input"); } @@ -47,7 +47,7 @@ public class IdemixPseudonym { /** * @return the value of the pseudonym as an ECP */ - ECP getNym() { + public ECP getNym() { return Nym; } diff --git a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonymSignature.java b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonymSignature.java index 56aa66e7..69c6e421 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonymSignature.java +++ b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixPseudonymSignature.java @@ -43,7 +43,7 @@ public class IdemixPseudonymSignature { * @param ipk the issuer public key * @param msg the message to be signed */ - IdemixPseudonymSignature(BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, byte[] msg) { + public IdemixPseudonymSignature(BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, byte[] msg) { if (sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || msg == null) { throw new IllegalArgumentException("Cannot create IdemixPseudonymSignature from null input"); } @@ -84,7 +84,7 @@ public class IdemixPseudonymSignature { * * @param proto a protobuf object representing an IdemixPseudonymSignature */ - IdemixPseudonymSignature(Idemix.NymSignature proto) { + public IdemixPseudonymSignature(Idemix.NymSignature proto) { if (proto == null) { throw new IllegalArgumentException("Cannot create idemix nym signature from null input"); } @@ -102,7 +102,7 @@ public class IdemixPseudonymSignature { * @param msg the message that should be signed in this signature * @return true iff valid */ - boolean verify(ECP nym, IdemixIssuerPublicKey ipk, byte[] msg) { + public boolean verify(ECP nym, IdemixIssuerPublicKey ipk, byte[] msg) { if (nym == null || ipk == null || msg == null) { return false; } @@ -131,7 +131,7 @@ boolean verify(ECP nym, IdemixIssuerPublicKey ipk, byte[] msg) { /** * @return A proto object representing this IdemixPseudonymSignature */ - Idemix.NymSignature toProto() { + public Idemix.NymSignature toProto() { return Idemix.NymSignature.newBuilder() .setProofC(ByteString.copyFrom(IdemixUtils.bigToBytes(proofC))) .setProofSSk(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSSk))) diff --git a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixSignature.java b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixSignature.java index faf26ea3..c1e3a8a4 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixSignature.java +++ b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixSignature.java @@ -69,7 +69,7 @@ public class IdemixSignature { * @param rhIndex the index of the attribute that represents the revocation handle * @param cri the credential revocation information that allows the signer to prove non-revocation */ - IdemixSignature(IdemixCredential c, BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, boolean[] disclosure, byte[] msg, int rhIndex, Idemix.CredentialRevocationInformation cri) { + public IdemixSignature(IdemixCredential c, BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, boolean[] disclosure, byte[] msg, int rhIndex, Idemix.CredentialRevocationInformation cri) { if (c == null || sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || disclosure == null || msg == null || cri == null) { throw new IllegalArgumentException("Cannot construct idemix signature from null input"); } @@ -197,7 +197,7 @@ public class IdemixSignature { * * @param proto a protobuf object representing an IdemixSignature */ - IdemixSignature(Idemix.Signature proto) { + public IdemixSignature(Idemix.Signature proto) { if (proto == null) { throw new IllegalArgumentException("Cannot construct idemix signature from null input"); } @@ -236,7 +236,7 @@ public class IdemixSignature { * @param epoch monotonically increasing counter representing a time window * @return true iff valid */ - boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues, int rhIndex, PublicKey revPk, int epoch) throws CryptoException { + public boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues, int rhIndex, PublicKey revPk, int epoch) throws CryptoException { if (disclosure == null || ipk == null || msg == null || attributeValues == null || attributeValues.length != ipk.getAttributeNames().length || disclosure.length != ipk.getAttributeNames().length) { return false; } @@ -349,7 +349,7 @@ boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[ * * @return a protobuf object representing this IdemixSignature */ - Idemix.Signature toProto() { + public Idemix.Signature toProto() { Idemix.Signature.Builder builder = Idemix.Signature.newBuilder() .setAPrime(IdemixUtils.transformToProto(aPrime)) .setABar(IdemixUtils.transformToProto(aBar)) diff --git a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixUtils.java b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixUtils.java index b110f163..9860a925 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixUtils.java +++ b/src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixUtils.java @@ -52,7 +52,6 @@ public final class IdemixUtils { static final FP12 genGT = PAIR.fexp(PAIR.ate(genG2, genG1)); static final BIG GROUP_ORDER = new BIG(ROM.CURVE_Order); static final int FIELD_BYTES = BIG.MODBYTES; - private static final RAND RNG = getRand(); private IdemixUtils() { // private constructor as there shouldn't be instances of this utility class @@ -95,7 +94,7 @@ public static BIG randModOrder(RAND rng) { * @param data the data to be hashed * @return a BIG in 0, ..., GROUP_ORDER-1 that is the hash of the data */ - static BIG hashModOrder(byte[] data) { + public static BIG hashModOrder(byte[] data) { HASH256 hash = new HASH256(); for (byte b : data) { hash.process(b); @@ -115,7 +114,7 @@ static BIG hashModOrder(byte[] data) { * @param big the BIG to turn into bytes * @return a byte array representation of the BIG */ - static byte[] bigToBytes(BIG big) { + public static byte[] bigToBytes(BIG big) { byte[] ret = new byte[IdemixUtils.FIELD_BYTES]; big.toBytes(ret); return ret; @@ -256,7 +255,7 @@ static Idemix.ECP transformToProto(ECP w) { * @param m the modulus * @return Returns a+b (mod m) */ - public static BIG modAdd(BIG a, BIG b, BIG m) { + static BIG modAdd(BIG a, BIG b, BIG m) { BIG c = a.plus(b); c.mod(m); return c; @@ -270,7 +269,7 @@ public static BIG modAdd(BIG a, BIG b, BIG m) { * @param m the modulus * @return returns a-b (mod m) */ - public static BIG modSub(BIG a, BIG b, BIG m) { + static BIG modSub(BIG a, BIG b, BIG m) { return modAdd(a, BIG.modneg(b, m), m); } } diff --git a/src/main/java/org/hyperledger/fabric/sdk/identity/IdemixIdentity.java b/src/main/java/org/hyperledger/fabric/sdk/identity/IdemixIdentity.java new file mode 100644 index 00000000..597547f7 --- /dev/null +++ b/src/main/java/org/hyperledger/fabric/sdk/identity/IdemixIdentity.java @@ -0,0 +1,200 @@ +/* + * + * Copyright IBM Corp. 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 org.hyperledger.fabric.sdk.identity; + +import java.util.Arrays; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.milagro.amcl.FP256BN.BIG; +import org.apache.milagro.amcl.FP256BN.ECP; +import org.hyperledger.fabric.protos.common.MspPrincipal; +import org.hyperledger.fabric.protos.idemix.Idemix; +import org.hyperledger.fabric.protos.msp.Identities; +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.exception.InvalidArgumentException; +import org.hyperledger.fabric.sdk.idemix.IdemixIssuerPublicKey; +import org.hyperledger.fabric.sdk.idemix.IdemixSignature; +import org.hyperledger.fabric.sdk.idemix.IdemixUtils; + +/** + * IdemixIdentity is a public serializable part of the IdemixSigningIdentity. + * It contains an (un)linkable pseudonym, revealed attribute values, and a + * corresponding proof of possession of an Idemix credential + */ +public class IdemixIdentity implements Identity { + + private static final Log logger = LogFactory.getLog(IdemixIdentity.class); + + // MSP identifier + private final String mspId; + + private final byte[] ipkHash; + + // Idemix Pseudonym + private final ECP pseudonym; + + // Organization Unit attribute + private final String ou; + + // Role attribute + private final boolean role; + + // Proof of possession of Idemix credential + // with respect to the pseudonym (nym) + // and the corresponding attributes (ou, role) + private final IdemixSignature associationProof; + + /** + * Create Idemix Identity from a Serialized Identity + * + * @param proto + */ + public IdemixIdentity(Identities.SerializedIdentity proto) throws CryptoException, InvalidArgumentException { + if (proto == null) { + throw new InvalidArgumentException("Input must not be null"); + } + + this.mspId = proto.getMspid(); + + try { + logger.trace("Fetching Idemix Proto"); + Identities.SerializedIdemixIdentity idemixProto = Identities.SerializedIdemixIdentity.parseFrom(proto.getIdBytes()); + + if (idemixProto == null) { + throw new IllegalArgumentException("The identity does not contain a serialized idemix identity"); + } + logger.trace("Deserializing Nym and attribute values"); + this.pseudonym = new ECP(BIG.fromBytes(idemixProto.getNymX().toByteArray()), + BIG.fromBytes(idemixProto.getNymY().toByteArray())); + + MspPrincipal.OrganizationUnit ou = MspPrincipal.OrganizationUnit.parseFrom(idemixProto.getOu()); + MspPrincipal.MSPRole role = MspPrincipal.MSPRole.parseFrom(idemixProto.getRole()); + + this.ou = ou.getOrganizationalUnitIdentifier(); + this.role = role.getRole().getNumber() == 1; + this.ipkHash = ou.getCertifiersIdentifier().toByteArray(); + + logger.trace("Deserializing Proof"); + this.associationProof = new IdemixSignature(Idemix.Signature.parseFrom(idemixProto.getProof().toByteArray())); + + } catch (InvalidProtocolBufferException e) { + throw new CryptoException("Cannot deserialize MSP ID", e); + } + } + + /** + * Create Idemix Identity from the following inputs: + * + * @param mspId is MSP ID sting + * @param nym is Identity Mixer Pseudonym + * @param ou is OU attribute + * @param role is Role attribute + * @param proof is Proof + */ + public IdemixIdentity(String mspId, IdemixIssuerPublicKey ipk, ECP nym, String ou, boolean role, IdemixSignature proof) + throws InvalidArgumentException { + + if (mspId == null) { + throw new InvalidArgumentException("MSP ID must not be null"); + } + + if (mspId.isEmpty()) { + throw new InvalidArgumentException("MSP ID must not be empty"); + } + + if (ipk == null) { + throw new InvalidArgumentException("Issuer Public Key must not be empty"); + } + + if (nym == null) { + throw new InvalidArgumentException("Identity Mixer Pseudonym (nym) must not be null"); + } + + if (ou == null) { + throw new InvalidArgumentException("OU attribute must not be null"); + } + + if (ou.isEmpty()) { + throw new InvalidArgumentException("OU attribute must not be empty"); + } + + if (proof == null) { + throw new InvalidArgumentException("Proof must not be null"); + } + + + this.mspId = mspId; + this.ipkHash = ipk.getHash(); + this.pseudonym = nym; + this.ou = ou; + this.role = role; + this.associationProof = proof; + } + + /** + * Serialize Idemix Identity + */ + @Override + public Identities.SerializedIdentity createSerializedIdentity() { + MspPrincipal.OrganizationUnit ou = MspPrincipal.OrganizationUnit.newBuilder() + .setCertifiersIdentifier(ByteString.copyFrom(this.ipkHash)) + .setMspIdentifier(this.mspId) + .setOrganizationalUnitIdentifier(this.ou) + .build(); + + MspPrincipal.MSPRole role = MspPrincipal.MSPRole.newBuilder() + .setRole(this.role ? MspPrincipal.MSPRole.MSPRoleType.ADMIN : MspPrincipal.MSPRole.MSPRoleType.MEMBER) + .setMspIdentifier(this.mspId) + .build(); + + Identities.SerializedIdemixIdentity serializedIdemixIdentity = Identities.SerializedIdemixIdentity.newBuilder() + .setProof(ByteString.copyFrom(this.associationProof.toProto().toByteArray())) + .setOu(ByteString.copyFrom(ou.toByteArray())) + .setRole(ByteString.copyFrom(role.toByteArray())) + .setNymY(ByteString.copyFrom(IdemixUtils.bigToBytes(this.pseudonym.getY()))) + .setNymX(ByteString.copyFrom(IdemixUtils.bigToBytes(this.pseudonym.getX()))) + .build(); + + return Identities.SerializedIdentity.newBuilder() + .setIdBytes(ByteString.copyFrom(serializedIdemixIdentity.toByteArray())) + .setMspid(this.mspId) + .build(); + } + + public String getOuValue() { + return this.ou; + } + + public boolean getRoleValue() { + return this.role; + } + + @Override + public String toString() { + return "IdemixIdentity" + + " [ MSP ID: " + this.mspId + + " Issuer Public Key Hash: " + Arrays.toString(this.ipkHash) + + " Pseudonym: " + this.pseudonym.toRawString() + + " OU: " + this.ou + + " Role: " + this.role + + " Association Proof: " + this.associationProof.toProto().toString() + + " ]"; + } +} diff --git a/src/main/java/org/hyperledger/fabric/sdk/identity/IdemixSigningIdentity.java b/src/main/java/org/hyperledger/fabric/sdk/identity/IdemixSigningIdentity.java new file mode 100644 index 00000000..288023d2 --- /dev/null +++ b/src/main/java/org/hyperledger/fabric/sdk/identity/IdemixSigningIdentity.java @@ -0,0 +1,258 @@ +/* + * + * Copyright IBM Corp. 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 org.hyperledger.fabric.sdk.identity; + +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.util.Arrays; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.milagro.amcl.FP256BN.BIG; +import org.hyperledger.fabric.protos.idemix.Idemix; +import org.hyperledger.fabric.protos.msp.Identities.SerializedIdentity; +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.exception.InvalidArgumentException; +import org.hyperledger.fabric.sdk.idemix.IdemixCredential; +import org.hyperledger.fabric.sdk.idemix.IdemixIssuerPublicKey; +import org.hyperledger.fabric.sdk.idemix.IdemixPseudonym; +import org.hyperledger.fabric.sdk.idemix.IdemixPseudonymSignature; +import org.hyperledger.fabric.sdk.idemix.IdemixSignature; +import org.hyperledger.fabric.sdk.idemix.IdemixUtils; + +/** + * IdemixSigningIdentity is an Idemix implementation of the SigningIdentity It + * contains IdemixIdentity (a public part) and a corresponding secret part that + * contains the user secret key and the commitment opening (randomness) to the + * pseudonym value (a commitment to the user secret) + *

+ * We note that since the attributes and their disclosure is fixed we are not + * adding them as fields here. + */ +public class IdemixSigningIdentity implements SigningIdentity { + + // public part of the signing identity (passed with the signature) + private final IdemixIdentity idemixIdentity; + + // public key of the Idemix CA (issuer) + private final IdemixIssuerPublicKey ipk; + + // user's secret + private final BIG sk; + + // idemix pseudonym (represents Idemix identity) + private final IdemixPseudonym pseudonym; + + // credental revocation information + private final Idemix.CredentialRevocationInformation cri; + + // proof that the identity is valid (proof of possession of a credential + // with respect to a pseudonym. + private final IdemixSignature proof; + + // discloseFlags will be passed to the idemix signing and verification + // routines. + // It informs idemix to disclose both attributes (OU and Role) when signing. + private static final boolean[] disclosedFlags = new boolean[]{true, true, false, false}; + + // empty message to sign in the validate identity proof + private static final byte[] msgEmpty = {}; + + // the revocation handle is always the third attribute + private static final int rhIndex = 3; + + private static final Log logger = LogFactory.getLog(IdemixSigningIdentity.class); + + /** + * Create new Idemix Signing Identity with a fresh pseudonym + * + * @param ipk issuer public key + * @param revocationPk the issuer's long term revocation public key + * @param mspId MSP identifier + * @param sk user's secret + * @param cred idemix credential + * @param cri the credential revocation information + * @param ou is OU attribute + * @param role is role attribute + * @throws CryptoException + * @throws InvalidArgumentException + */ + public IdemixSigningIdentity(IdemixIssuerPublicKey ipk, PublicKey revocationPk, String mspId, BIG sk, IdemixCredential cred, Idemix.CredentialRevocationInformation cri, String ou, boolean role) + throws CryptoException, InvalidArgumentException { + + // input checks + if (ipk == null) { + throw new InvalidArgumentException("Issuer Public Key (IPK) must not be null"); + } + + if (revocationPk == null) { + throw new InvalidArgumentException("Revocation PK must not be null"); + } + + if (mspId == null) { + throw new InvalidArgumentException("MSP ID must not be null"); + } + + if (mspId.isEmpty()) { + throw new InvalidArgumentException("MSP ID must not be empty"); + } + + if (ou == null) { + throw new InvalidArgumentException("OU must not be null"); + } + + if (ou.isEmpty()) { + throw new InvalidArgumentException("OU must not be empty"); + } + + if (sk == null) { + throw new InvalidArgumentException("SK must not be null"); + } + + if (cred == null) { + throw new InvalidArgumentException("Credential must not be null"); + } + + if (cri == null) { + throw new InvalidArgumentException("Credential revocation information must not be null"); + } + + logger.trace("Verifying public key with hash: " + Arrays.toString(ipk.getHash()) + + " \nAttributes: " + Arrays.toString(ipk.getAttributeNames())); + + if (!ipk.check()) { + CryptoException e = new CryptoException("Issuer public key is not valid"); + logger.error("", e); + throw e; + } + + this.ipk = ipk; + this.sk = sk; + this.cri = cri; + + logger.trace("Verifying the credential"); + + // cryptographically verify credential + // (check if the issuer's signature is valid) + if (!cred.verify(sk, ipk)) { + CryptoException e = new CryptoException("Credential is not cryptographically valid"); + logger.error("", e); + throw e; + } + + logger.trace("Checking attributes"); + + // attribute checks + // 4 attributes are expected: + // - organization unit (disclosed) + // - role: admin or member (disclosed) + // - enrollment id (hidden, for future auditing feature and authorization with CA) + // - revocation handle (hidden, for future revocation support) + if (cred.getAttrs().length != 4) { + throw new CryptoException("Error: There are " + cred.getAttrs().length + " attributes and the expected are 4"); + } + + byte[] ouBytes = cred.getAttrs()[0]; + byte[] roleBytes = cred.getAttrs()[1]; + byte[] eIdBytes = cred.getAttrs()[2]; + byte[] rHBytes = cred.getAttrs()[3]; + + BIG[] attributes = new BIG[4]; + attributes[0] = BIG.fromBytes(ouBytes); + attributes[1] = BIG.fromBytes(roleBytes); + attributes[2] = BIG.fromBytes(eIdBytes); + attributes[3] = BIG.fromBytes(rHBytes); + + // check that the OU string matches the credential's attribute value + if (!Arrays.equals(IdemixUtils.bigToBytes(IdemixUtils.hashModOrder(ou.getBytes(StandardCharsets.UTF_8))), ouBytes)) { + throw new IllegalArgumentException("the OU string does not match the credential"); + } + + // check that the role matches the credential's attribute value + if (!Arrays.equals(IdemixUtils.bigToBytes(new BIG(role ? 1 : 0)), roleBytes)) { + throw new IllegalArgumentException("the role does not match the credential"); + } + + logger.trace("Generating fresh pseudonym and proof"); + // generate a fresh pseudonym + this.pseudonym = new IdemixPseudonym(this.sk, this.ipk); + + // generate a fresh proof of possession of a credential + // with respect to a freshly generated pseudonym + this.proof = new IdemixSignature(cred, this.sk, this.pseudonym, this.ipk, IdemixSigningIdentity.disclosedFlags, IdemixSigningIdentity.msgEmpty, rhIndex, cri); + logger.trace("Verifying the proof"); + // verify the proof + if (!this.proof.verify(IdemixSigningIdentity.disclosedFlags, this.ipk, IdemixSigningIdentity.msgEmpty, attributes, rhIndex, revocationPk, (int) cri.getEpoch())) { + throw new CryptoException("Generated proof of identity is not valid"); + } + + logger.trace("Generating the Identity Object"); + // generate a fresh identity with new pseudonym + this.idemixIdentity = new IdemixIdentity(mspId, this.ipk, this.pseudonym.getNym(), ou, role, this.proof); + logger.trace(this.idemixIdentity.toString()); + } + + @Override + public byte[] sign(byte[] msg) throws CryptoException, InvalidArgumentException { + if (msg == null) { + throw new InvalidArgumentException("Input must not be null"); + } + return new IdemixPseudonymSignature(this.sk, this.pseudonym, this.ipk, msg).toProto().toByteArray(); + } + + @Override + public SerializedIdentity createSerializedIdentity() { + return this.idemixIdentity.createSerializedIdentity(); + } + + @Override + public boolean verifySignature(byte[] msg, byte[] sig) throws CryptoException, InvalidArgumentException { + + if (msg == null) { + throw new InvalidArgumentException("Message must not be null"); + } + + if (sig == null) { + throw new InvalidArgumentException("Signature must not be null"); + } + + Idemix.NymSignature nymSigProto = null; + try { + nymSigProto = Idemix.NymSignature.parseFrom(sig); + } catch (InvalidProtocolBufferException e) { + logger.error("Idemix Nym Signature parsing error, dumping \nSignature: " + Arrays.toString(sig) + " \nMessage: " + Arrays.toString(msg)); + throw new CryptoException("Could not parse Idemix Nym Signature", e); + } + + IdemixPseudonymSignature nymSig = new IdemixPseudonymSignature(nymSigProto); + if (!nymSig.verify(this.pseudonym.getNym(), this.ipk, msg)) { + logger.error("Idemix Nym Signature verification error, dumping \nSignature: " + Arrays.toString(sig) + " \nMessage: " + Arrays.toString(msg)); + return false; + } + + return true; + } + + public IdemixPseudonym getNym() { + return this.pseudonym; + } + + public IdemixSignature getProof() { + return this.proof; + } +} diff --git a/src/main/java/org/hyperledger/fabric/sdk/identity/Identity.java b/src/main/java/org/hyperledger/fabric/sdk/identity/Identity.java new file mode 100644 index 00000000..ff40ec81 --- /dev/null +++ b/src/main/java/org/hyperledger/fabric/sdk/identity/Identity.java @@ -0,0 +1,36 @@ +/* + * + * Copyright IBM Corp. 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 org.hyperledger.fabric.sdk.identity; + +import org.hyperledger.fabric.protos.msp.Identities; + +/** + * Identity corresponds to the Identity in fabric MSP. + * The Identity is attached to the transaction signature and + * can be unique per user or unlinkable (depending on the implementation and requirements) + * This is to be used at the peer side when verifying certificates/credentials that transactions are signed + * with, and verifying signatures that correspond to these certificates. + */ +public interface Identity { + + /** + * Converts an identity to bytes + * + * @return SerializedIdentity + */ + Identities.SerializedIdentity createSerializedIdentity(); +} diff --git a/src/main/java/org/hyperledger/fabric/sdk/identity/SigningIdentity.java b/src/main/java/org/hyperledger/fabric/sdk/identity/SigningIdentity.java new file mode 100644 index 00000000..886a7446 --- /dev/null +++ b/src/main/java/org/hyperledger/fabric/sdk/identity/SigningIdentity.java @@ -0,0 +1,48 @@ +/* + * + * Copyright IBM Corp. 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 org.hyperledger.fabric.sdk.identity; + +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.exception.InvalidArgumentException; + +/** + * SigningIdentity extends Identity with signing capabilities. It is used by a + * client to sign transactions in a linkable or unlinkable fashion. + */ +public interface SigningIdentity extends Identity { + + /** + * Sings a message with the secret key and the corresponding certificate + * + * @param msg + * @return signature + * @throws CryptoException + * @throws InvalidArgumentException + */ + byte[] sign(byte[] msg) throws CryptoException, InvalidArgumentException; + + /** + * Verifies a signature on a message + * + * @param msg + * @param sig + * @return true/false + * @throws CryptoException + * @throws InvalidArgumentException + */ + boolean verifySignature(byte[] msg, byte[] sig) throws CryptoException, InvalidArgumentException; +} \ No newline at end of file diff --git a/src/main/proto/msp/msp_config.proto b/src/main/proto/msp/msp_config.proto index d9c33503..2b3e9a92 100644 --- a/src/main/proto/msp/msp_config.proto +++ b/src/main/proto/msp/msp_config.proto @@ -8,6 +8,7 @@ syntax = "proto3"; option go_package = "github.com/hyperledger/fabric/protos/msp"; option java_package = "org.hyperledger.fabric.protos.msp"; +option java_outer_classname = "MspConfig"; package msp; @@ -196,7 +197,7 @@ message FabricOUIdentifier { // that does not contain any of the specified OU will be considered invalid. message FabricNodeOUs { // If true then an msp identity that does not contain any of the specified OU will be considered invalid. - bool enable = 1; + bool enable = 1; // OU Identifier of the clients FabricOUIdentifier client_ou_identifier = 2; diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/IssuerPublicKey new file mode 100644 index 00000000..4be2ff5b Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/IssuerSecretKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/IssuerSecretKey new file mode 100644 index 00000000..5e07c36b --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/IssuerSecretKey @@ -0,0 +1 @@ +BnPΊ"M{@r%ΩP,.^=:YUPY \ No newline at end of file diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/RevocationKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/RevocationKey new file mode 100644 index 00000000..0526e318 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/ca/RevocationKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/msp/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/msp/IssuerPublicKey new file mode 100644 index 00000000..4be2ff5b Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/msp/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/msp/RevocationPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/msp/RevocationPublicKey new file mode 100644 index 00000000..53ae3662 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/msp/RevocationPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/user/SignerConfig b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/user/SignerConfig new file mode 100644 index 00000000..62a85008 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1Broken/user/SignerConfig @@ -0,0 +1,5 @@ + + +D + -~ӧsz0(֌5 ceK  o(]{ST s9/XEtasdf ʁ" ΃< +/rLaߞBth* asdfasdfasdf& N`WsTZ7$x5i4~j+ p FBvw uN>Q$uaXH asdf" TӈBI)~LJf0d0e2DQ6# zl_z:މk$1Np c$0X)f]Di]^@x)-lsgt \ No newline at end of file diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/IssuerSecretKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/IssuerSecretKey new file mode 100644 index 00000000..567cea74 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/IssuerSecretKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/RevocationKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/RevocationKey new file mode 100644 index 00000000..ea385401 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDDuFW4JG+e6dF7U9EI0nGhk7cpln0rAyBFyLHkN3MGcWzN1UXcrz2Kz +v6iK/MyqK2qgBwYFK4EEACKhZANiAAQAC3ZN2NR24PBTl136I5I65bJcVsHOXwZZ +noD1cjUHIVYCJlprdlRNFWkP3TZVWCp9xAi7p04OaTg8bZbfUz3YgX5HAxqyP8qF +nk7ReVEpiksjkJ98Ygqyu7iDeqe/Udk= +-----END PRIVATE KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/msp/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/msp/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/msp/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/msp/RevocationPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/msp/RevocationPublicKey new file mode 100644 index 00000000..9f3c415a --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAAt2TdjUduDwU5dd+iOSOuWyXFbBzl8G +WZ6A9XI1ByFWAiZaa3ZUTRVpD902VVgqfcQIu6dODmk4PG2W31M92IF+RwMasj/K +hZ5O0XlRKYpLI5CffGIKsru4g3qnv1HZ +-----END PUBLIC KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/user/SignerConfig b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/user/SignerConfig new file mode 100644 index 00000000..63cd64cc Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1/user/SignerConfig differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/IssuerSecretKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/IssuerSecretKey new file mode 100644 index 00000000..567cea74 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/IssuerSecretKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/RevocationKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/RevocationKey new file mode 100644 index 00000000..ea385401 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDDuFW4JG+e6dF7U9EI0nGhk7cpln0rAyBFyLHkN3MGcWzN1UXcrz2Kz +v6iK/MyqK2qgBwYFK4EEACKhZANiAAQAC3ZN2NR24PBTl136I5I65bJcVsHOXwZZ +noD1cjUHIVYCJlprdlRNFWkP3TZVWCp9xAi7p04OaTg8bZbfUz3YgX5HAxqyP8qF +nk7ReVEpiksjkJ98Ygqyu7iDeqe/Udk= +-----END PRIVATE KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/msp/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/msp/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/msp/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/msp/RevocationPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/msp/RevocationPublicKey new file mode 100644 index 00000000..9f3c415a --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAAt2TdjUduDwU5dd+iOSOuWyXFbBzl8G +WZ6A9XI1ByFWAiZaa3ZUTRVpD902VVgqfcQIu6dODmk4PG2W31M92IF+RwMasj/K +hZ5O0XlRKYpLI5CffGIKsru4g3qnv1HZ +-----END PUBLIC KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/user/SignerConfig b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/user/SignerConfig new file mode 100644 index 00000000..cc51eec6 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU1Admin/user/SignerConfig differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/IssuerSecretKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/IssuerSecretKey new file mode 100644 index 00000000..567cea74 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/IssuerSecretKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/RevocationKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/RevocationKey new file mode 100644 index 00000000..ea385401 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDDuFW4JG+e6dF7U9EI0nGhk7cpln0rAyBFyLHkN3MGcWzN1UXcrz2Kz +v6iK/MyqK2qgBwYFK4EEACKhZANiAAQAC3ZN2NR24PBTl136I5I65bJcVsHOXwZZ +noD1cjUHIVYCJlprdlRNFWkP3TZVWCp9xAi7p04OaTg8bZbfUz3YgX5HAxqyP8qF +nk7ReVEpiksjkJ98Ygqyu7iDeqe/Udk= +-----END PRIVATE KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/msp/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/msp/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/msp/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/msp/RevocationPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/msp/RevocationPublicKey new file mode 100644 index 00000000..9f3c415a --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAAt2TdjUduDwU5dd+iOSOuWyXFbBzl8G +WZ6A9XI1ByFWAiZaa3ZUTRVpD902VVgqfcQIu6dODmk4PG2W31M92IF+RwMasj/K +hZ5O0XlRKYpLI5CffGIKsru4g3qnv1HZ +-----END PUBLIC KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/user/SignerConfig b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/user/SignerConfig new file mode 100644 index 00000000..305167d2 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1OU2/user/SignerConfig differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/IssuerSecretKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/IssuerSecretKey new file mode 100644 index 00000000..567cea74 Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/IssuerSecretKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/RevocationKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/RevocationKey new file mode 100644 index 00000000..ea385401 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDDuFW4JG+e6dF7U9EI0nGhk7cpln0rAyBFyLHkN3MGcWzN1UXcrz2Kz +v6iK/MyqK2qgBwYFK4EEACKhZANiAAQAC3ZN2NR24PBTl136I5I65bJcVsHOXwZZ +noD1cjUHIVYCJlprdlRNFWkP3TZVWCp9xAi7p04OaTg8bZbfUz3YgX5HAxqyP8qF +nk7ReVEpiksjkJ98Ygqyu7iDeqe/Udk= +-----END PRIVATE KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/msp/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/msp/IssuerPublicKey new file mode 100644 index 00000000..9517ed3f Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/msp/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/msp/RevocationPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/msp/RevocationPublicKey new file mode 100644 index 00000000..9f3c415a --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP1Verifier/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAAt2TdjUduDwU5dd+iOSOuWyXFbBzl8G +WZ6A9XI1ByFWAiZaa3ZUTRVpD902VVgqfcQIu6dODmk4PG2W31M92IF+RwMasj/K +hZ5O0XlRKYpLI5CffGIKsru4g3qnv1HZ +-----END PUBLIC KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/IssuerPublicKey new file mode 100644 index 00000000..b75c3f3a Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/IssuerSecretKey b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/IssuerSecretKey new file mode 100644 index 00000000..2ff2a235 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/IssuerSecretKey @@ -0,0 +1 @@ +P}j!gI/n{H]GÂ? hL \ No newline at end of file diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/RevocationKey b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/RevocationKey new file mode 100644 index 00000000..f83316c0 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/ca/RevocationKey @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGkAgEBBDBMRsBtjZXWx6DmJuwcK6B8nfcp365eQ3RpLtMASts6e25+bsDO1EpX +4VX2atjLAKqgBwYFK4EEACKhZANiAAQ/E3gU75VkKOf0FDXOFK6udzBdG0ZeOwVM +HiBlhjLlZ2zRQe/cHyGJUy3pB9XfymZzkMADLYgfpu71yDda2IMZtLIvxw/Mybhn +SY5qxdRAehzA73myxc6eZGvWf8sA6mI= +-----END PRIVATE KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/msp/IssuerPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/msp/IssuerPublicKey new file mode 100644 index 00000000..b75c3f3a Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/msp/IssuerPublicKey differ diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/msp/RevocationPublicKey b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/msp/RevocationPublicKey new file mode 100644 index 00000000..919ef018 --- /dev/null +++ b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/msp/RevocationPublicKey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPxN4FO+VZCjn9BQ1zhSurncwXRtGXjsF +TB4gZYYy5Wds0UHv3B8hiVMt6QfV38pmc5DAAy2IH6bu9cg3WtiDGbSyL8cPzMm4 +Z0mOasXUQHocwO95ssXOnmRr1n/LAOpi +-----END PUBLIC KEY----- diff --git a/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/user/SignerConfig b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/user/SignerConfig new file mode 100644 index 00000000..b871770a Binary files /dev/null and b/src/test/fixture/IdemixIdentitiesTest/MSP2OU1/user/SignerConfig differ diff --git a/src/test/java/org/hyperledger/fabric/sdk/identity/IdemixIdentitiesTest.java b/src/test/java/org/hyperledger/fabric/sdk/identity/IdemixIdentitiesTest.java new file mode 100644 index 00000000..6334a5f1 --- /dev/null +++ b/src/test/java/org/hyperledger/fabric/sdk/identity/IdemixIdentitiesTest.java @@ -0,0 +1,479 @@ +/* + * + * Copyright IBM Corp. 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 org.hyperledger.fabric.sdk.identity; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.apache.milagro.amcl.FP256BN.BIG; +import org.apache.milagro.amcl.FP256BN.ECP; +import org.bouncycastle.util.io.pem.PemReader; +import org.hyperledger.fabric.protos.idemix.Idemix; +import org.hyperledger.fabric.protos.msp.Identities; +import org.hyperledger.fabric.protos.msp.MspConfig.IdemixMSPSignerConfig; +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.exception.InvalidArgumentException; +import org.hyperledger.fabric.sdk.idemix.IdemixCredential; +import org.hyperledger.fabric.sdk.idemix.IdemixIssuerPublicKey; +import org.hyperledger.fabric.sdk.idemix.IdemixPseudonym; +import org.hyperledger.fabric.sdk.idemix.IdemixSignature; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +/** + * Tests for IdemixIdentity and IdemixSigningIdentity + */ +public class IdemixIdentitiesTest { + + // Test resources with crypto material generated by the idemixgen tool (in go) + private static final String TEST_PATH = "src/test/fixture/IdemixIdentitiesTest/"; + private static final String USER_PATH = "/user/"; + private static final String VERIFIER_PATH = "/msp/"; + private static final String MSP1Broken = "MSP1Broken"; + private static final String MSP1OU1 = "MSP1OU1"; + private static final String OU1 = "OU1"; + private static final String OU2 = "OU2"; + private static final boolean roleAdmin = true; + private static final boolean roleMember = false; + private static final String MSP1OU1Admin = "MSP1OU1Admin"; + private static final String MSP1OU2 = "MSP1OU2"; + private static final String MSP1Verifier = "MSP1Verifier"; + private static final String MSP2OU1 = "MSP2OU1"; + private static final String SIGNER_CONFIG = "SignerConfig"; + private static final String REVOCATION_PUBLIC_KEY = "RevocationPublicKey"; + private static final String IPK_CONFIG = "IssuerPublicKey"; + + + private static IdemixCredential cred = null; + private static Idemix.CredentialRevocationInformation cri = null; + private static IdemixIssuerPublicKey ipk = null; + private static PublicKey revocationPk = null; + private static BIG sk = null; + private static IdemixPseudonym nym = null; + private static ECP nymPublic = null; + private static IdemixSignature proof = null; + private static IdemixSigningIdentity signingIdentity = null; + + private static byte[] message = {1, 2, 3, 4}; + private static byte[] sigTest = {1, 2, 3, 4}; + + + // Setup using a happy path + @BeforeClass + public static void setup() { + + // Parse crypto material from files + IdemixMSPSignerConfig signerConfig = null; + try { + signerConfig = readIdemixMSPConfig(TEST_PATH + MSP1OU1 + USER_PATH, SIGNER_CONFIG); + } catch (Exception e) { + fail("Unexpected exception while reading signerconfig: " + e.getMessage()); + } + assertNotNull(signerConfig); + + try { + revocationPk = readIdemixRevocationPublicKey(TEST_PATH + MSP1OU1 + VERIFIER_PATH, REVOCATION_PUBLIC_KEY); + } catch (Exception e) { + fail("Unexpected exception while reading revocation public key: " + e.getMessage()); + } + assertNotNull(revocationPk); + + Idemix.IssuerPublicKey ipkProto = null; + try { + ipkProto = readIdemixIssuerPublicKey(TEST_PATH + MSP1OU1 + VERIFIER_PATH, IPK_CONFIG); + } catch (IOException e1) { + fail("Unexpected exception while reading revocation public key" + e1.getMessage()); + } + + ipk = new IdemixIssuerPublicKey(ipkProto); + assertTrue(ipk.check()); + + sk = BIG.fromBytes(signerConfig.getSk().toByteArray()); + + Idemix.Credential credProto = null; + try { + credProto = Idemix.Credential.parseFrom(signerConfig.getCred()); + } catch (InvalidProtocolBufferException e) { + fail("Could not parse a credential"); + } + + assertNotNull(credProto); + + cred = new IdemixCredential(credProto); + + try { + cri = Idemix.CredentialRevocationInformation.parseFrom(signerConfig.getCredentialRevocationInformation()); + } catch (InvalidProtocolBufferException e) { + fail("failed to extract cri from signer config: " + e.getMessage()); + } + assertNotNull(cri); + + try { + signingIdentity = new IdemixSigningIdentity(ipk, revocationPk, MSP1OU1, sk, cred, cri, OU1, roleMember); + } catch (CryptoException | InvalidArgumentException e) { + fail("Could not create Idemix Signing Identity" + e.getMessage()); + } + + assertNotNull(signingIdentity); + + nym = signingIdentity.getNym(); + + nymPublic = nym.getNym(); + + proof = signingIdentity.getProof(); + + } + + // Test creating a signing identity with MSP1Verifier (should fail) + @Test(expected = IOException.class) + public void testIdemixSigningIdentityVerifier() throws IOException { + try { + createIdemixSigningIdentity(MSP1Verifier); + } catch (CryptoException | InvalidArgumentException | InvalidKeySpecException | NoSuchAlgorithmException e) { /* If exception throw test fails */ } + + } + + // Test creating a signing identity with MSP1Broken (should fail) + @Test(expected = IOException.class) + public void testIdemixSigningIdentityBroken() throws IOException { + try { + createIdemixSigningIdentity(MSP1Broken); + } catch (CryptoException | InvalidArgumentException | InvalidKeySpecException | NoSuchAlgorithmException e) { + fail("Unexpected Exception" + e.getMessage()); + } + + } + + // Test creating a signer config + @Test + public void testIdemixMSPSignerConfigSuccess() { + IdemixMSPSignerConfig signerConfig = null; + try { + signerConfig = readIdemixMSPConfig(TEST_PATH + MSP1OU1 + USER_PATH, SIGNER_CONFIG); + } catch (InvalidProtocolBufferException e) { + fail("Unexpected IPBException" + e.getMessage()); + } catch (IOException e) { + fail("Unexpected IOException" + e.getMessage()); + } + assertNotNull(signerConfig); + } + + // Test creating a signing identity from null input + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputNullPk() throws InvalidArgumentException { + try { + new IdemixSigningIdentity(null, revocationPk, MSP1OU1, sk, cred, cri, OU1, roleMember); + } catch (CryptoException e) { + fail("Unexpected Crypto exception"); + } + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputNullRevPk() throws InvalidArgumentException, CryptoException { + new IdemixSigningIdentity(ipk, null, MSP1OU1, sk, cred, cri, OU1, roleMember); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputNullMsp() throws InvalidArgumentException, CryptoException { + new IdemixSigningIdentity(ipk, revocationPk, null, sk, cred, cri, OU1, roleMember); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputEmptymsp() throws InvalidArgumentException, CryptoException { + new IdemixSigningIdentity(ipk, revocationPk, "", sk, cred, cri, OU1, roleMember); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputNullSk() throws InvalidArgumentException, CryptoException { + new IdemixSigningIdentity(ipk, revocationPk, MSP1OU1, null, cred, cri, OU1, roleMember); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputNullCri() throws InvalidArgumentException, CryptoException { + new IdemixSigningIdentity(ipk, revocationPk, MSP1OU1, sk, cred, null, OU1, roleMember); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixSigningIdentityInputNullCred() throws InvalidArgumentException, CryptoException { + new IdemixSigningIdentity(ipk, revocationPk, MSP1OU1, sk, null, cri, OU1, roleMember); + } + + // Test Signing and Verification with Signing Identity + @Test(expected = InvalidArgumentException.class) + public void testSigningNullMsg() throws InvalidArgumentException, CryptoException { + testSigning(signingIdentity, message, null, true); + } + + @Test(expected = InvalidArgumentException.class) + public void testSigningNullSig() throws InvalidArgumentException, CryptoException { + testSigning(signingIdentity, null, sigTest, true); + } + + @Test(expected = InvalidArgumentException.class) + public void testSigningNullMsgSig() throws InvalidArgumentException, CryptoException { + testSigning(signingIdentity, null, null, true); + } + + @Test + public void testSigningSuccess() throws InvalidArgumentException, CryptoException { + assertTrue(testSigning(signingIdentity, message, null, false)); + } + + + @Test + public void testSerializingAndDeserializingIdentity() { + Identities.SerializedIdentity proto = signingIdentity.createSerializedIdentity(); + assertNotNull(proto); + + Identities.SerializedIdemixIdentity idemixProto = null; + try { + idemixProto = Identities.SerializedIdemixIdentity.parseFrom(proto.getIdBytes()); + } catch (InvalidProtocolBufferException e) { + fail("Could not parse Idemix Serialized Identity" + e.getMessage()); + } + if (idemixProto != null) { + new ECP(BIG.fromBytes(idemixProto.getNymX().toByteArray()), BIG.fromBytes(idemixProto.getNymY().toByteArray())); + idemixProto.getOu().toByteArray(); + idemixProto.getRole().toByteArray(); + try { + new IdemixSignature(Idemix.Signature.parseFrom(idemixProto.getProof().toByteArray())); + } catch (InvalidProtocolBufferException e) { + fail("Cannot deserialize proof" + e.getMessage()); + } + } + + try { + new IdemixIdentity(proto); + } catch (CryptoException | InvalidArgumentException e) { + fail("Cannot create Idemix Identity from Proto" + e.getMessage()); + } + + } + + // Test creating IdemixIdentity + + @Test(expected = InvalidArgumentException.class) + public void testIdemixIdentityInputNull() throws InvalidArgumentException { + try { + new IdemixIdentity(null); + } catch (CryptoException e) { + fail("Unexpected Crypto exception " + e.getMessage()); + } + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixIdentityInputNullMsp() throws InvalidArgumentException { + new IdemixIdentity(null, ipk, nymPublic, OU1, roleMember, proof); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixIdentityInputNullNym() throws InvalidArgumentException { + new IdemixIdentity(MSP1OU1, ipk, null, OU1, roleMember, proof); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixIdentityInputNullOu() throws InvalidArgumentException { + new IdemixIdentity(MSP1OU1, ipk, nymPublic, null, roleMember, proof); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixIdentityInputNullProof() throws InvalidArgumentException { + new IdemixIdentity(MSP1OU1, ipk, nymPublic, OU1, roleMember, null); + } + + @Test(expected = InvalidArgumentException.class) + public void testIdemixIdentityInputNullIpk() throws InvalidArgumentException { + new IdemixIdentity(MSP1OU1, null, nymPublic, OU1, roleMember, proof); + } + + @Test + public void testIdemixIdentity() { + try { + new IdemixIdentity(MSP1OU1, ipk, nymPublic, OU1, roleMember, proof); + } catch (InvalidArgumentException e) { + fail("Unexpected Invalid Argument exception" + e.getMessage()); + } + } + + + // Test creating different signing identities + @Test + public void testSigningIdentityMSP1OU1Admin() { + assertTrue(testCreatingSigningIdentityAndSign(MSP1OU1Admin)); + } + + @Test + public void testSigningIdentityMSP1OU2() { + assertTrue(testCreatingSigningIdentityAndSign(MSP1OU2)); + } + + @Test + public void testSigningIdentityMSP2OU1() { + assertTrue(testCreatingSigningIdentityAndSign(MSP2OU1)); + } + + // Helper functions + + /** + * Helper function to create a Signing Identity and sign with it + * + * @param mspId + * @return + */ + public boolean testCreatingSigningIdentityAndSign(String mspId) { + + boolean b = false; + + IdemixSigningIdentity signingIdentityTest = null; + try { + signingIdentityTest = createIdemixSigningIdentity(mspId); + } catch (Exception e) { + fail("Unexpected exception: " + e.getMessage()); + } + + assertNotNull(signingIdentityTest); + + // Test signing using this identity + try { + b = testSigning(signingIdentityTest, message, null, false); + } catch (CryptoException | InvalidArgumentException e) { + fail("Unexpected exception: " + e.getMessage()); + } + + return b; + } + + + /** + * Helper function for testing signing + * + * @param signIdentity + * @return + * @throws InvalidArgumentException + */ + public boolean testSigning(IdemixSigningIdentity signIdentity, byte[] msg, byte[] sigInput, boolean useInputSig) throws CryptoException, InvalidArgumentException { + + byte[] sig = signIdentity.sign(msg); + byte[] otherMsg = {1, 1, 1, 1}; + + if (useInputSig) { + assertFalse(signIdentity.verifySignature(otherMsg, sigInput)); + return signIdentity.verifySignature(msg, sigInput); + } else { + assertFalse(signIdentity.verifySignature(otherMsg, sig)); + return signIdentity.verifySignature(msg, sig); + } + } + + /** + * Helper function to create IdemixSigningIdentity from a file generated by idemixgen go tool + * + * @param mspId + * @return IdemixSigningIdentity object + * @throws IOException + * @throws InvalidProtocolBufferException + */ + private IdemixSigningIdentity createIdemixSigningIdentity(String mspId) throws CryptoException, InvalidArgumentException, IOException, InvalidKeySpecException, NoSuchAlgorithmException { + IdemixMSPSignerConfig signerConfig = null; + signerConfig = readIdemixMSPConfig(TEST_PATH + mspId + USER_PATH, SIGNER_CONFIG); + assertNotNull(signerConfig); + + Idemix.IssuerPublicKey ipkProto = readIdemixIssuerPublicKey(TEST_PATH + mspId + VERIFIER_PATH, IPK_CONFIG); + IdemixIssuerPublicKey ipk = new IdemixIssuerPublicKey(ipkProto); + assertTrue(ipk.check()); + + PublicKey revPk = readIdemixRevocationPublicKey(TEST_PATH + mspId + VERIFIER_PATH, REVOCATION_PUBLIC_KEY); + + BIG sk = BIG.fromBytes(signerConfig.getSk().toByteArray()); + + Idemix.Credential credProto = Idemix.Credential.parseFrom(signerConfig.getCred()); + + assertNotNull(credProto); + + IdemixCredential cred = new IdemixCredential(credProto); + + Idemix.CredentialRevocationInformation cri = Idemix.CredentialRevocationInformation.parseFrom(signerConfig.getCredentialRevocationInformation()); + + return new IdemixSigningIdentity(ipk, revPk, mspId, sk, cred, cri, signerConfig.getOrganizationalUnitIdentifier(), signerConfig.getIsAdmin()); + } + + /** + * Helper function: parse Idemix MSP Signer config (is part of the MSPConfig proto) from path + * + * @param configPath + * @param id + * @return IdemixMSPSignerConfig proto + */ + public static IdemixMSPSignerConfig readIdemixMSPConfig(String configPath, String id) throws IOException { + + Path path = Paths.get(configPath + id); + byte[] data = Files.readAllBytes(path); + IdemixMSPSignerConfig signerConfig = IdemixMSPSignerConfig.parseFrom(data); + return signerConfig; + } + + /** + * Parse Idemix issuer public key from the config file + * + * @param configPath + * @param id + * @return Idemix IssuerPublicKey proto + */ + public static Idemix.IssuerPublicKey readIdemixIssuerPublicKey(String configPath, String id) throws IOException { + + Path path = Paths.get(configPath + id); + byte[] data = Files.readAllBytes(path); + + Idemix.IssuerPublicKey ipk = Idemix.IssuerPublicKey.parseFrom(data); + return ipk; + } + + /** + * Parse Idemix long-term revocation public key from the config file + * + * @param configPath + * @param id + * @return the long-term revocation public key + */ + public static PublicKey readIdemixRevocationPublicKey(String configPath, String id) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + Path path = Paths.get(configPath + id); + byte[] data = Files.readAllBytes(path); + + String pem = new String(data, StandardCharsets.UTF_8); + byte[] der = convertPemToDer(pem); + return KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(der)); + } + + private static byte[] convertPemToDer(String pem) throws IOException { + PemReader pemReader = new PemReader(new StringReader(pem)); + return pemReader.readPemObject().getContent(); + } +}