From eecc9975f01979610cbf8bb84aff6f88ba96ae59 Mon Sep 17 00:00:00 2001 From: rickr Date: Mon, 9 Oct 2017 15:52:39 -0400 Subject: [PATCH] FAB-6411 generate CRL Change-Id: I3110f26d653a47da443f0baad86eb60960745513 Signed-off-by: rickr --- .../hyperledger/fabric_ca/sdk/HFCAClient.java | 83 +++++++++++++++++++ .../sdk/exception/GenerateCRLException.java | 27 ++++++ src/main/proto/common/configuration.proto | 48 +++++++---- src/main/proto/ledger/rwset/rwset.proto | 8 -- src/main/proto/msp/identities.proto | 36 +++++--- src/main/proto/msp/msp_config.proto | 42 +++++++--- src/main/proto/peer/events.proto | 19 +++++ .../sdkintegration/HFCAClientIT.java | 40 ++++++++- 8 files changed, 256 insertions(+), 47 deletions(-) create mode 100644 src/main/java/org/hyperledger/fabric_ca/sdk/exception/GenerateCRLException.java diff --git a/src/main/java/org/hyperledger/fabric_ca/sdk/HFCAClient.java b/src/main/java/org/hyperledger/fabric_ca/sdk/HFCAClient.java index d34a4fbe..5159517a 100644 --- a/src/main/java/org/hyperledger/fabric_ca/sdk/HFCAClient.java +++ b/src/main/java/org/hyperledger/fabric_ca/sdk/HFCAClient.java @@ -18,7 +18,9 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.PrintWriter; import java.io.StringReader; +import java.io.StringWriter; import java.net.MalformedURLException; import java.net.Socket; import java.net.URL; @@ -33,14 +35,18 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; import java.util.Base64; +import java.util.Date; import java.util.Properties; +import java.util.TimeZone; import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import javax.json.JsonReader; +import javax.json.JsonWriter; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -81,6 +87,7 @@ import org.hyperledger.fabric.sdk.security.CryptoPrimitives; import org.hyperledger.fabric.sdk.security.CryptoSuite; import org.hyperledger.fabric_ca.sdk.exception.EnrollmentException; +import org.hyperledger.fabric_ca.sdk.exception.GenerateCRLException; import org.hyperledger.fabric_ca.sdk.exception.InfoException; import org.hyperledger.fabric_ca.sdk.exception.InvalidArgumentException; import org.hyperledger.fabric_ca.sdk.exception.RegistrationException; @@ -102,6 +109,7 @@ public class HFCAClient { private static final String HFCA_REENROLL = HFCA_CONTEXT_ROOT + "reenroll"; private static final String HFCA_REVOKE = HFCA_CONTEXT_ROOT + "revoke"; private static final String HFCA_INFO = HFCA_CONTEXT_ROOT + "cainfo"; + private static final String HFCA_GENCRL = HFCA_CONTEXT_ROOT + "gencrl"; static final String FABRIC_CA_REQPROP = "caname"; @@ -603,6 +611,81 @@ public void revoke(User revoker, String revokee, String reason) throws Revocatio } } + /** + * Generate certificate revocation list. + * + * @param registrar admin user configured in CA-server + * @param revokedBefore Restrict certificates returned to revoked before this date if not null. + * @param revokedAfter Restrict certificates returned to revoked after this date if not null. + * @param expireBefore Restrict certificates returned to expired before this date if not null. + * @param expireAfter Restrict certificates returned to expired after this date if not null. + * @throws InvalidArgumentException + */ + + public String generateCRL(User registrar, Date revokedBefore, Date revokedAfter, Date expireBefore, Date expireAfter) + throws InvalidArgumentException, GenerateCRLException { + + if (cryptoSuite == null) { + throw new InvalidArgumentException("Crypto primitives not set."); + } + + if (registrar == null) { + throw new InvalidArgumentException("registrar is not set"); + } + + try { + setUpSSL(); + + //--------------------------------------- + JsonObjectBuilder factory = Json.createObjectBuilder(); + if (revokedBefore != null) { + factory.add("revokedBefore", toJson(revokedBefore)); + } + if (revokedAfter != null) { + factory.add("revokedAfter", toJson(revokedAfter)); + } + if (expireBefore != null) { + factory.add("expireBefore", toJson(expireBefore)); + } + if (expireAfter != null) { + factory.add("expireAfter", toJson(expireAfter)); + } + if (caName != null) { + factory.add(HFCAClient.FABRIC_CA_REQPROP, caName); + } + + JsonObject jsonObject = factory.build(); + + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = Json.createWriter(new PrintWriter(stringWriter)); + jsonWriter.writeObject(jsonObject); + jsonWriter.close(); + String body = stringWriter.toString(); + + //--------------------------------------- + + // build auth header + String authHdr = getHTTPAuthCertificate(registrar.getEnrollment(), body); + + // send revoke request + JsonObject ret = httpPost(url + HFCA_GENCRL, body, authHdr); + + return ret.getString("CRL"); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new GenerateCRLException(e.getMessage(), e); + } + } + + private String toJson(Date date) { + final TimeZone utc = TimeZone.getTimeZone("UTC"); + + SimpleDateFormat tformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + tformat.setTimeZone(utc); + return tformat.format(date); + } + /** * Http Post Request. * diff --git a/src/main/java/org/hyperledger/fabric_ca/sdk/exception/GenerateCRLException.java b/src/main/java/org/hyperledger/fabric_ca/sdk/exception/GenerateCRLException.java new file mode 100644 index 00000000..c4172113 --- /dev/null +++ b/src/main/java/org/hyperledger/fabric_ca/sdk/exception/GenerateCRLException.java @@ -0,0 +1,27 @@ +/* + * + * Copyright 2016,2017 DTCC, Fujitsu Australia Software Technology, IBM - 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_ca.sdk.exception; + +public class GenerateCRLException extends BaseException { + + private static final long serialVersionUID = 1L; + + public GenerateCRLException(String message, Exception parent) { + super(message, parent); + } + +} diff --git a/src/main/proto/common/configuration.proto b/src/main/proto/common/configuration.proto index 534b308c..83bb44c1 100644 --- a/src/main/proto/common/configuration.proto +++ b/src/main/proto/common/configuration.proto @@ -47,23 +47,43 @@ message Consortium { string name = 1; } -// Capabilities message contains all the capabilities that a channel requires -// participant entities to comply with. The entity should drop off the channel -// if it can't fulfill any of the required capabilities. -// Capabilities is encoded into the configuaration as Values of each type -// Orderer, Channel, or Application. -// The key string should represent the capability name, and it must be unique -// within each type. For readability, it may be advisable to prefix the key with -// its type (eg app-acl) +// Capabilities message defines the capabilities a particular binary must implement +// for that binary to be able to safely participate in the channel. The capabilities +// message is defined at the /Channel level, the /Channel/Application level, and the +// /Channel/Orderer level. +// +// The /Channel level capabilties define capabilities which both the orderer and peer +// binaries must satisfy. These capabilties might be things like a new MSP type, +// or a new policy type. +// +// The /Channel/Orderer level capabilties define capabilities which must be supported +// by the orderer, but which have no bearing on the behavior of the peer. For instance +// if the orderer changes the logic for how it constructs new channels, only all orderers +// must agree on the new logic. The peers do not need to be aware of this change as +// they only interact with the channel after it has been constructed. +// +// Finally, the /Channel/Application level capabilities define capabilities which the peer +// binary must satisfy, but which have no bearing on the orderer. For instance, if the +// peer adds a new UTXO transaction type, or changes the chaincode lifecycle requirements, +// all peers must agree on the new logic. However, orderers never inspect transactions +// this deeply, and therefore have no need to be aware of the change. +// +// The capabilities strings defined in these messages typically correspond to release +// binary versions (e.g. "V1.1"), and are used primarilly as a mechanism for a fully +// upgraded network to switch from one set of logic to a new one. +// +// Although for V1.1, the orderers must be upgraded to V1.1 prior to the rest of the +// network, going forward, because of the split between the /Channel, /Channel/Orderer +// and /Channel/Application capabilities. It should be possible for the orderer and +// application networks to upgrade themselves independently (with the exception of any +// new capabilities defined at the /Channel level). message Capabilities { map capabilities = 1; } -// Capability holds a set of options. We can add more as needed in the -// future. For now, whether it is required or not. If a configured capability -// is not required, it must be completely compatible with previous releases. -// Compatible features are not required to be encoded as capabilities; they -// only provide flexibility for the admins to control the features. +// Capability is an empty message for the time being. It is defined as a protobuf +// message rather than a constant, so that we may extend capabilities with other fields +// if the need arises in the future. For the time being, a capability being in the +// capabilities map requires that that capability be supported. message Capability { - bool required = 1; } diff --git a/src/main/proto/ledger/rwset/rwset.proto b/src/main/proto/ledger/rwset/rwset.proto index 2ccfd09e..e1a49f0c 100644 --- a/src/main/proto/ledger/rwset/rwset.proto +++ b/src/main/proto/ledger/rwset/rwset.proto @@ -63,11 +63,3 @@ message CollectionPvtReadWriteSet { string collection_name = 1; bytes rwset = 2; // Data model specific serialized proto message (e.g., kvrwset.KVRWSet for KV and Document data models) } - -// CollectionProperty defines an element of a private data that corresponds -// to a certain transaction and collection -message CollectionCriteria { - string channel = 1; - string tx_id = 2; - string collection = 3; -} \ No newline at end of file diff --git a/src/main/proto/msp/identities.proto b/src/main/proto/msp/identities.proto index 7046248c..857ae7a4 100644 --- a/src/main/proto/msp/identities.proto +++ b/src/main/proto/msp/identities.proto @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +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. +SPDX-License-Identifier: Apache-2.0 */ syntax = "proto3"; @@ -31,3 +21,25 @@ message SerializedIdentity { // the Identity, serialized according to the rules of its MPS bytes id_bytes = 2; } + +// This struct represents an Idemix Identity +// to be used to serialize it and deserialize it. +// The IdemixMSP will first serialize an idemix identity to bytes using +// this proto, and then uses these bytes as id_bytes in SerializedIdentity +message SerializedIdemixIdentity { + // NymX is the X-component of the pseudonym elliptic curve point. + // It is a []byte representation of an amcl.BIG + // The pseudonym can be seen as a public key of the identity, it is used to verify signatures. + bytes NymX = 1; + + // NymX is the Y-component of the pseudonym elliptic curve point. + // It is a []byte representation of an amcl.BIG + // The pseudonym can be seen as a public key of the identity, it is used to verify signatures. + bytes NymY = 2; + + // OU contains the organizational unit of the idemix identity + bytes OU = 3; + + // Role contains the role of this identity (e.g., ADMIN or MEMBER) + bytes Role = 4; +} \ 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 1dccb44e..67ba6de4 100644 --- a/src/main/proto/msp/msp_config.proto +++ b/src/main/proto/msp/msp_config.proto @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +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. +SPDX-License-Identifier: Apache-2.0 */ syntax = "proto3"; @@ -110,6 +100,34 @@ message FabricCryptoConfig { } +// IdemixMSPConfig collects all the configuration information for +// an Idemix MSP. +message IdemixMSPConfig { + // Name holds the identifier of the MSP + string name = 1; + + // IPk represents the (serialized) issuer public key + bytes IPk = 2; + + // signer may contain crypto material to configure a default signer + IdemixMSPSignerConfig signer = 3; +} + +// IdemixMSPSIgnerConfig contains the crypto material to set up an idemix signing identity +message IdemixMSPSignerConfig { + // Cred represents the serialized idemix credential of the default signer + bytes Cred = 1; + + // Sk is the secret key of the default signer, corresponding to credential Cred + bytes Sk = 2; + + // organizational_unit_identifier defines the organizational unit the default signer is in + string organizational_unit_identifier = 3; + + // is_admin defines whether the default signer is admin or not + bool is_admin = 4; +} + // SigningIdentityInfo represents the configuration information // related to the signing identity the peer is to use for generating // endorsements diff --git a/src/main/proto/peer/events.proto b/src/main/proto/peer/events.proto index c5337f42..959f6a58 100644 --- a/src/main/proto/peer/events.proto +++ b/src/main/proto/peer/events.proto @@ -34,6 +34,7 @@ enum EventType { BLOCK = 1; CHAINCODE = 2; REJECTION = 3; + FILTEREDBLOCK = 4; } //ChaincodeReg is used for registering chaincode Interests @@ -74,6 +75,22 @@ message Unregister { repeated Interest events = 1; } +//FilteredBlock is sent by producers and contains minimal information +//about the block. +message FilteredBlock { + string channel_id = 1; + uint64 number = 2; // The position in the blockchain + repeated FilteredTransaction filtered_tx = 3; +} + +//FilteredTransaction is a minimal set of information about a transaction +//within a block. +message FilteredTransaction { + string txid = 1; + TxValidationCode tx_validation_code = 2; + ChaincodeEvent ccEvent = 3; +} + // SignedEvent is used for any communication between consumer and producer message SignedEvent { // Signature over the event bytes @@ -99,6 +116,8 @@ message Event { //Unregister consumer sent events Unregister unregister = 5; + + FilteredBlock filtered_block = 7; } // Creator of the event, specified as a certificate chain bytes creator = 6; diff --git a/src/test/java/org/hyperledger/fabric_ca/sdkintegration/HFCAClientIT.java b/src/test/java/org/hyperledger/fabric_ca/sdkintegration/HFCAClientIT.java index aa6e2a4e..b1f4092f 100644 --- a/src/test/java/org/hyperledger/fabric_ca/sdkintegration/HFCAClientIT.java +++ b/src/test/java/org/hyperledger/fabric_ca/sdkintegration/HFCAClientIT.java @@ -21,11 +21,16 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; +import java.util.Calendar; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.x509.CertificateList; +import org.bouncycastle.asn1.x509.TBSCertList; import org.hyperledger.fabric.sdk.Enrollment; import org.hyperledger.fabric.sdk.security.CryptoSuite; import org.hyperledger.fabric.sdk.testutils.TestConfig; @@ -48,6 +53,7 @@ import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -196,7 +202,6 @@ public void testRegisterAttributesDefault() throws Exception { */ @Test public void testRegisterAttributesNONE() throws Exception { - SampleUser user = new SampleUser("mrAttributesNone", TEST_ADMIN_ORG, sampleStore); RegistrationRequest rr = new RegistrationRequest(user.getName(), TEST_USER1_AFFILIATION); @@ -294,6 +299,10 @@ public void testUserRevoke() throws Exception { thrown.expect(EnrollmentException.class); thrown.expectMessage("Failed to re-enroll user"); + Calendar calendar = Calendar.getInstance(); // gets a calendar using the default time zone and locale. + calendar.add(Calendar.SECOND, -1); + Date revokedTinyBitAgoTime = calendar.getTime(); //avoid any clock skewing. + SampleUser user = getTestUser(TEST_USER1_ORG); if (!user.isRegistered()) { @@ -322,13 +331,42 @@ public void testUserRevoke() throws Exception { sleepALittle(); + int startedWithRevokes = -1; + + if (!testConfig.isRunningAgainstFabric10()) { + + startedWithRevokes = getRevokes(null).length; //one more after we do this revoke. + } + // revoke all enrollment of this user client.revoke(admin, user.getName(), "revoke user 3"); + if (!testConfig.isRunningAgainstFabric10()) { + + final int newRevokes = getRevokes(null).length; + + assertEquals(format("Expected one more revocation %d, but got %d", startedWithRevokes + 1, newRevokes), startedWithRevokes + 1, newRevokes); + + final int revokestinybitago = getRevokes(revokedTinyBitAgoTime).length; //Should be same number when test case was started. + assertEquals(format("Expected same revocations %d, but got %d", startedWithRevokes, revokestinybitago), startedWithRevokes, revokestinybitago); + } // trying to reenroll the revoked user should fail with an EnrollmentException client.reenroll(user); } + TBSCertList.CRLEntry[] getRevokes(Date r) throws Exception { + + String crl = client.generateCRL(admin, r, null, null, null); + + Base64.Decoder b64dec = Base64.getDecoder(); + final byte[] decode = b64dec.decode(crl.getBytes(UTF_8)); + + ByteArrayInputStream inStream = new ByteArrayInputStream(decode); + ASN1InputStream asnInputStream = new ASN1InputStream(inStream); + + return CertificateList.getInstance(asnInputStream.readObject()).getRevokedCertificates(); + } + @Test public void testEnrollNoKeyPair() throws Exception {