Skip to content

Commit 91445c8

Browse files
jt-ntiGerrit Code Review
authored andcommitted
Merge "[FAB-15895] Added client identity to context" into release-1.4
2 parents b54e0d4 + 55c29f9 commit 91445c8

File tree

23 files changed

+702
-173
lines changed

23 files changed

+702
-173
lines changed

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

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

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

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

4280
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
implementation 'com.github.everit-org.json-schema:org.everit.json.schema:1.11.1'
2626
compile 'io.swagger.core.v3:swagger-annotations:2.0.0'
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
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ TxFunction getRouting(InvocationRequest request) {
116116
} else {
117117
logger.debug(() -> "Namespace is " + request);
118118
ContractDefinition contract = registry.getContract(request.getNamespace());
119-
return contract.getUnkownRoute();
119+
return contract.getUnknownRoute();
120120
}
121121
}
122122

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import org.hyperledger.fabric.shim.ChaincodeStub;
1313

1414
/**
15-
* Service that executes {@link InvocationRequest} (wrapped INit/Invoke + extra data) using routing information {@link Routing}
15+
* Service that executes {@link InvocationRequest} (wrapped Init/Invoke + extra data) using routing information {@link Routing}
1616
*/
1717
public interface ExecutionService {
1818

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.everit.json.schema.loader.SchemaLoader;
2323
import org.hyperledger.fabric.Logger;
2424
import org.hyperledger.fabric.contract.annotation.Contract;
25+
import org.hyperledger.fabric.contract.annotation.Info;
2526
import org.hyperledger.fabric.contract.routing.ContractDefinition;
2627
import org.hyperledger.fabric.contract.routing.DataTypeDefinition;
2728
import org.hyperledger.fabric.contract.routing.RoutingRegistry;
@@ -31,8 +32,6 @@
3132
import org.json.JSONObject;
3233
import org.json.JSONTokener;
3334

34-
import org.hyperledger.fabric.contract.annotation.Info;
35-
3635
/**
3736
* Builder to assist in production of the metadata
3837
* <p>
@@ -99,7 +98,7 @@ public static void initialize(RoutingRegistry registry, TypeRegistry typeRegistr
9998
// need to validate that the metadata that has been created is really valid
10099
// it should be as it's been created by code, but this is a valuable double
101100
// check
102-
logger.info("Validating scehma created");
101+
logger.info("Validating schema created");
103102
MetadataBuilder.validate();
104103

105104
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* when invoked TxFunctions - the transaction functions defined in this contract
2525
*
2626
* Will embedded the ContgractInterface instance, as well as the annotation
27-
* itself, and the routing for any tx function that is unkown
27+
* itself, and the routing for any tx function that is unknown
2828
*
2929
*/
3030
public interface ContractDefinition {
@@ -59,8 +59,8 @@ public interface ContractDefinition {
5959

6060
/**
6161
*
62-
* @param method name to returned
63-
* @return TxFunction that represent this requested method
62+
* @param method name to be returned
63+
* @return TxFunction that represents this requested method
6464
*/
6565
TxFunction getTxFunction(String method);
6666

@@ -75,7 +75,7 @@ public interface ContractDefinition {
7575
* @return The TxFunction to be used for this contract in case of unknown
7676
* request
7777
*/
78-
TxFunction getUnkownRoute();
78+
TxFunction getUnknownRoute();
7979

8080
/**
8181
* @return Underlying raw annotation

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ContractDefinitionImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public boolean hasTxFunction(String method) {
112112
}
113113

114114
@Override
115-
public TxFunction getUnkownRoute() {
115+
public TxFunction getUnknownRoute() {
116116
return unknownTx;
117117
}
118118

0 commit comments

Comments
 (0)