Skip to content

Commit 44d76f4

Browse files
committed
[FAB-15895] Added client identity to context
- Created a test utility file for storing certificate strings - Moved tests from ContractSimplePath to ContractRouterTest - Tidied up some imports - Fixed some typos and warnings Signed-off-by: heatherlp <[email protected]> Change-Id: Iee2512ca4078a6487f6711eb5eae0be5b82acd30
1 parent eb41043 commit 44d76f4

File tree

24 files changed

+742
-194
lines changed

24 files changed

+742
-194
lines changed

fabric-chaincode-integration-test/src/test/java/org/hyperleder/fabric/shim/integration/FirstNetworkIntegrationTest.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,8 @@
55
*/
66
package org.hyperleder.fabric.shim.integration;
77

8-
import com.github.dockerjava.api.exception.ConflictException;
9-
import com.google.protobuf.ByteString;
10-
import org.hamcrest.Matchers;
11-
import org.hyperledger.fabric.sdk.*;
12-
import org.hyperledger.fabric.sdk.exception.*;
13-
import org.hyperledger.fabric.sdk.security.CryptoSuite;
14-
import org.hyperledger.fabric.shim.Chaincode;
15-
import org.junit.*;
16-
import org.testcontainers.containers.DockerComposeContainer;
8+
import static org.hamcrest.Matchers.containsString;
9+
import static org.junit.Assert.assertThat;
1710

1811
import java.io.File;
1912
import java.io.IOException;
@@ -25,8 +18,31 @@
2518
import java.util.List;
2619
import java.util.stream.Collectors;
2720

28-
import static org.hamcrest.Matchers.containsString;
29-
import static org.junit.Assert.assertThat;
21+
import com.github.dockerjava.api.exception.ConflictException;
22+
import com.google.protobuf.ByteString;
23+
24+
import org.hamcrest.Matchers;
25+
import org.hyperledger.fabric.sdk.Channel;
26+
import org.hyperledger.fabric.sdk.HFClient;
27+
import org.hyperledger.fabric.sdk.InstallProposalRequest;
28+
import org.hyperledger.fabric.sdk.InstantiateProposalRequest;
29+
import org.hyperledger.fabric.sdk.Peer;
30+
import org.hyperledger.fabric.sdk.ProposalResponse;
31+
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
32+
import org.hyperledger.fabric.sdk.exception.ChaincodeCollectionConfigurationException;
33+
import org.hyperledger.fabric.sdk.exception.ChaincodeEndorsementPolicyParseException;
34+
import org.hyperledger.fabric.sdk.exception.CryptoException;
35+
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
36+
import org.hyperledger.fabric.sdk.exception.ProposalException;
37+
import org.hyperledger.fabric.sdk.exception.TransactionException;
38+
import org.hyperledger.fabric.sdk.security.CryptoSuite;
39+
import org.hyperledger.fabric.shim.Chaincode;
40+
import org.junit.AfterClass;
41+
import org.junit.BeforeClass;
42+
import org.junit.ClassRule;
43+
import org.junit.Ignore;
44+
import org.junit.Test;
45+
import org.testcontainers.containers.DockerComposeContainer;
3046

3147
public class FirstNetworkIntegrationTest {
3248

@@ -274,7 +290,7 @@ void RunSBE(HFClient client, Channel channel, String mode) throws NoSuchAlgorit
274290
}
275291

276292
@Test
277-
public void testSimpelChaincodeFirstNetwork() throws IllegalAccessException, InvocationTargetException, InvalidArgumentException, InstantiationException, NoSuchMethodException, CryptoException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, TransactionException, ProposalException, ChaincodeEndorsementPolicyParseException, ChaincodeCollectionConfigurationException {
293+
public void testSimpleChaincodeFirstNetwork() throws IllegalAccessException, InvocationTargetException, InvalidArgumentException, InstantiationException, NoSuchMethodException, CryptoException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, TransactionException, ProposalException, ChaincodeEndorsementPolicyParseException, ChaincodeCollectionConfigurationException {
278294
final CryptoSuite crypto = CryptoSuite.Factory.getCryptoSuite();
279295

280296
// Create client and set default crypto suite
@@ -330,7 +346,7 @@ private void executeAndValidateQueryOnAccount(HFClient client, Channel channel,
330346

331347
@Test
332348
@Ignore
333-
public void testSimpelChaincodeFirstNetworkNewPM() throws IllegalAccessException, InvocationTargetException, InvalidArgumentException, InstantiationException, NoSuchMethodException, CryptoException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, TransactionException, ProposalException, ChaincodeEndorsementPolicyParseException, ChaincodeCollectionConfigurationException {
349+
public void testSimpleChaincodeFirstNetworkNewPM() throws IllegalAccessException, InvocationTargetException, InvalidArgumentException, InstantiationException, NoSuchMethodException, CryptoException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, TransactionException, ProposalException, ChaincodeEndorsementPolicyParseException, ChaincodeCollectionConfigurationException {
334350
final CryptoSuite crypto = CryptoSuite.Factory.getCryptoSuite();
335351

336352
// Create client and set default crypto suite

fabric-chaincode-integration-test/src/test/java/org/hyperleder/fabric/shim/integration/Utils.java

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,44 @@
55
*/
66
package org.hyperleder.fabric.shim.integration;
77

8+
import static java.nio.charset.StandardCharsets.UTF_8;
9+
import static org.hamcrest.Matchers.hasItem;
10+
import static org.junit.Assert.assertThat;
11+
import static org.junit.Assert.fail;
12+
13+
import java.io.BufferedOutputStream;
14+
import java.io.ByteArrayInputStream;
15+
import java.io.ByteArrayOutputStream;
16+
import java.io.File;
17+
import java.io.FileInputStream;
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.io.Reader;
21+
import java.io.StringReader;
22+
import java.security.NoSuchAlgorithmException;
23+
import java.security.NoSuchProviderException;
24+
import java.security.PrivateKey;
25+
import java.security.Security;
26+
import java.security.spec.InvalidKeySpecException;
27+
import java.util.Collection;
28+
import java.util.HashMap;
29+
import java.util.LinkedList;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.Properties;
33+
import java.util.Set;
34+
import java.util.concurrent.CompletableFuture;
35+
import java.util.concurrent.CountDownLatch;
36+
import java.util.concurrent.ExecutorService;
37+
import java.util.concurrent.Executors;
38+
import java.util.concurrent.TimeUnit;
39+
import java.util.concurrent.TimeoutException;
40+
import java.util.concurrent.atomic.AtomicBoolean;
41+
import java.util.stream.Collectors;
42+
843
import com.github.dockerjava.api.model.Container;
944
import com.github.dockerjava.api.model.Image;
45+
1046
import org.apache.commons.compress.archivers.ArchiveEntry;
1147
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
1248
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
@@ -18,28 +54,28 @@
1854
import org.bouncycastle.openssl.PEMParser;
1955
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
2056
import org.hamcrest.Matcher;
21-
import org.hamcrest.Matchers;
22-
import org.hyperledger.fabric.sdk.*;
23-
import org.hyperledger.fabric.sdk.exception.*;
57+
import org.hyperledger.fabric.sdk.BlockEvent;
58+
import org.hyperledger.fabric.sdk.ChaincodeCollectionConfiguration;
59+
import org.hyperledger.fabric.sdk.ChaincodeEndorsementPolicy;
60+
import org.hyperledger.fabric.sdk.ChaincodeID;
61+
import org.hyperledger.fabric.sdk.Channel;
62+
import org.hyperledger.fabric.sdk.Enrollment;
63+
import org.hyperledger.fabric.sdk.HFClient;
64+
import org.hyperledger.fabric.sdk.InstallProposalRequest;
65+
import org.hyperledger.fabric.sdk.InstantiateProposalRequest;
66+
import org.hyperledger.fabric.sdk.Orderer;
67+
import org.hyperledger.fabric.sdk.Peer;
68+
import org.hyperledger.fabric.sdk.ProposalResponse;
69+
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
70+
import org.hyperledger.fabric.sdk.TransactionRequest;
71+
import org.hyperledger.fabric.sdk.User;
72+
import org.hyperledger.fabric.sdk.exception.ChaincodeCollectionConfigurationException;
73+
import org.hyperledger.fabric.sdk.exception.ChaincodeEndorsementPolicyParseException;
74+
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
75+
import org.hyperledger.fabric.sdk.exception.ProposalException;
76+
import org.hyperledger.fabric.sdk.exception.TransactionException;
2477
import org.testcontainers.DockerClientFactory;
2578

26-
import java.io.*;
27-
import java.security.NoSuchAlgorithmException;
28-
import java.security.NoSuchProviderException;
29-
import java.security.PrivateKey;
30-
import java.security.Security;
31-
import java.security.spec.InvalidKeySpecException;
32-
import java.util.*;
33-
import java.util.concurrent.*;
34-
import java.util.concurrent.atomic.AtomicBoolean;
35-
import java.util.stream.Collectors;
36-
37-
import static java.nio.charset.StandardCharsets.UTF_8;
38-
import static org.hamcrest.Matchers.contains;
39-
import static org.hamcrest.Matchers.hasItem;
40-
import static org.junit.Assert.assertThat;
41-
import static org.junit.Assert.fail;
42-
4379
public class Utils {
4480

4581
static public User getUser(String name, String mspId, File privateKeyFile, File certificateFile)

fabric-chaincode-shim/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ tasks.withType(org.gradle.api.tasks.testing.Test) {
1919
dependencies {
2020
compile project(':fabric-chaincode-protos')
2121
compile 'io.netty:netty-tcnative-boringssl-static:2.0.7.Final'
22-
compile 'org.bouncycastle:bcpkix-jdk15on:1.60'
23-
compile 'org.bouncycastle:bcprov-jdk15on:1.60'
22+
compile 'org.bouncycastle:bcpkix-jdk15on:1.62'
23+
compile 'org.bouncycastle:bcprov-jdk15on:1.62'
2424
compile 'org.reflections:reflections:0.9.11'
2525
compile group: 'cglib', name: 'cglib', version: '3.2.10'
2626
implementation 'com.github.everit-org.json-schema:org.everit.json.schema:1.11.1'
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package org.hyperledger.fabric.contract;
8+
9+
import static java.nio.charset.StandardCharsets.UTF_8;
10+
11+
import java.io.ByteArrayInputStream;
12+
import java.io.IOException;
13+
import java.security.cert.CertificateException;
14+
import java.security.cert.CertificateFactory;
15+
import java.security.cert.X509Certificate;
16+
import java.util.HashMap;
17+
import java.util.Iterator;
18+
import java.util.Map;
19+
20+
import org.bouncycastle.asn1.ASN1InputStream;
21+
import org.bouncycastle.asn1.ASN1Primitive;
22+
import org.bouncycastle.asn1.DEROctetString;
23+
import org.hyperledger.fabric.Logger;
24+
import org.hyperledger.fabric.protos.msp.Identities.SerializedIdentity;
25+
import org.hyperledger.fabric.shim.ChaincodeStub;
26+
import org.json.JSONException;
27+
import org.json.JSONObject;
28+
29+
/**
30+
* ClientIdentity represents information about the identity that submitted a
31+
* transaction. Chaincodes can use this class to obtain information about the submitting
32+
* identity including a unique ID, the MSP (Membership Service Provider) ID, and attributes.
33+
* Such information is useful in enforcing access control by the chaincode.
34+
*
35+
*/
36+
public final class ClientIdentity {
37+
private static Logger logger = Logger.getLogger(ContractRouter.class.getName());
38+
39+
private String mspId;
40+
private X509Certificate cert;
41+
private Map<String, String> attrs;
42+
private String id;
43+
// special OID used by Fabric to save attributes in x.509 certificates
44+
private static final String FABRIC_CERT_ATTR_OID = "1.2.3.4.5.6.7.8.1";
45+
46+
public ClientIdentity(ChaincodeStub stub) throws CertificateException, JSONException, IOException {
47+
final byte[] signingId = stub.getCreator();
48+
49+
// Create a Serialized Identity protobuf
50+
SerializedIdentity si = SerializedIdentity.parseFrom(signingId);
51+
this.mspId = si.getMspid();
52+
53+
final byte[] idBytes = si.getIdBytes().toByteArray();
54+
55+
final X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new ByteArrayInputStream(idBytes));
56+
this.cert = cert;
57+
58+
this.attrs = new HashMap<String, String>();
59+
// Get the extension where the identity attributes are stored
60+
final byte[] extensionValue = cert.getExtensionValue(FABRIC_CERT_ATTR_OID);
61+
if (extensionValue != null) {
62+
this.attrs = parseAttributes(extensionValue);
63+
}
64+
65+
// Populate identity
66+
this.id = "x509::" + cert.getSubjectDN().getName() + "::" + cert.getIssuerDN().getName();
67+
}
68+
/**
69+
* getId returns the ID associated with the invoking identity. This ID
70+
* is guaranteed to be unique within the MSP.
71+
* @return {String} A string in the format: "x509::{subject DN}::{issuer DN}"
72+
*/
73+
public String getId() {
74+
return this.id;
75+
}
76+
77+
/**
78+
* getMSPID returns the MSP ID of the invoking identity.
79+
* @return {String}
80+
*/
81+
public String getMSPID() {
82+
return this.mspId;
83+
}
84+
85+
/**
86+
* parseAttributes returns a map of the attributes associated with an identity
87+
* @param extensionValue DER-encoded Octet string stored in the attributes extension
88+
* of the certificate, as a byte array
89+
* @return attrMap {Map<String, String>} a map of identity attributes as key value pair strings
90+
*/
91+
private Map<String, String> parseAttributes(byte[] extensionValue) throws IOException {
92+
93+
Map<String, String> attrMap = new HashMap<String, String>();
94+
95+
// Create ASN1InputStream from extensionValue
96+
try ( ByteArrayInputStream inStream = new ByteArrayInputStream(extensionValue);
97+
ASN1InputStream asn1InputStream = new ASN1InputStream(inStream)) {
98+
99+
// Read the DER object
100+
ASN1Primitive derObject = asn1InputStream.readObject();
101+
if (derObject instanceof DEROctetString)
102+
{
103+
DEROctetString derOctetString = (DEROctetString) derObject;
104+
105+
// Create attributeString from octets and create JSON object
106+
String attributeString = new String(derOctetString.getOctets(), UTF_8);
107+
final JSONObject extJSON = new JSONObject(attributeString);
108+
final JSONObject attrs = extJSON.getJSONObject("attrs");
109+
110+
Iterator<String> keys = attrs.keys();
111+
while(keys.hasNext()) {
112+
String key = keys.next();
113+
// Populate map with attributes and values
114+
attrMap.put(key, attrs.getString(key));
115+
}
116+
}
117+
} catch (JSONException error) {
118+
// creating a JSON object failed
119+
// decoded extensionValue is not a string containing JSON
120+
logger.error(() -> logger.formatError(error));
121+
// return empty map
122+
}
123+
return attrMap;
124+
}
125+
126+
/**
127+
* getAttributeValue returns the value of the client's attribute named `attrName`.
128+
* If the invoking identity possesses the attribute, returns the value of the attribute.
129+
* If the invoking identity does not possess the attribute, returns null.
130+
* @param attrName Name of the attribute to retrieve the value from the
131+
* identity's credentials (such as x.509 certificate for PKI-based MSPs).
132+
* @return {String | null} Value of the attribute or null if the invoking identity
133+
* does not possess the attribute.
134+
*/
135+
public String getAttributeValue(String attrName) {
136+
if (this.attrs.containsKey(attrName)) {
137+
return this.attrs.get(attrName);
138+
} else {
139+
return null;
140+
}
141+
}
142+
143+
/**
144+
* assertAttributeValue verifies that the invoking identity has the attribute named `attrName`
145+
* with a value of `attrValue`.
146+
* @param attrName Name of the attribute to retrieve the value from the
147+
* identity's credentials (such as x.509 certificate for PKI-based MSPs)
148+
* @param attrValue Expected value of the attribute
149+
* @return {boolean} True if the invoking identity possesses the attribute and the attribute
150+
* value matches the expected value. Otherwise, returns false.
151+
*/
152+
public boolean assertAttributeValue(String attrName, String attrValue) {
153+
if (!this.attrs.containsKey(attrName)) {
154+
return false;
155+
} else {
156+
return attrValue.equals(this.attrs.get(attrName));
157+
}
158+
}
159+
160+
/**
161+
* getX509Certificate returns the X509 certificate associated with the invoking identity,
162+
* or null if it was not identified by an X509 certificate, for instance if the MSP is
163+
* implemented with an alternative to PKI such as [Identity Mixer](https://jira.hyperledger.org/browse/FAB-5673).
164+
* @return {X509Certificate | null}
165+
*/
166+
public X509Certificate getX509Certificate() {
167+
return this.cert;
168+
}
169+
}

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/Context.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
package org.hyperledger.fabric.contract;
88

9+
import java.io.IOException;
10+
import java.security.cert.CertificateException;
11+
912
import org.hyperledger.fabric.shim.ChaincodeStub;
13+
import org.json.JSONException;
1014

1115
/**
1216
*
@@ -32,13 +36,20 @@
3236
*/
3337
public class Context {
3438
protected ChaincodeStub stub;
39+
protected ClientIdentity clientIdentity;
3540

3641
/**
3742
* Constructor
43+
* Creates new client identity and sets it as a property of the stub
3844
* @param stub Instance of the {@link ChaincodeStub} to use
3945
*/
4046
public Context(ChaincodeStub stub) {
4147
this.stub = stub;
48+
try {
49+
this.clientIdentity = new ClientIdentity(stub);
50+
} catch (CertificateException | JSONException | IOException e) {
51+
throw new ContractRuntimeException("Could not create new client identity", e);
52+
}
4253
}
4354

4455
/**
@@ -48,4 +59,12 @@ public Context(ChaincodeStub stub) {
4859
public ChaincodeStub getStub() {
4960
return this.stub;
5061
}
62+
63+
/**
64+
*
65+
* @return ClientIdentity object to use
66+
*/
67+
public ClientIdentity getClientIdentity() {
68+
return this.clientIdentity;
69+
}
5170
}

0 commit comments

Comments
 (0)