Skip to content

Commit

Permalink
[FAB-7127] Revoke missing gencrl
Browse files Browse the repository at this point in the history
Fabric CA supports generating a CRL on the revoke,
this is now also supported rom the JSDK api.

Also fixes the NPE if reason is null when making
the revoke request.

Change-Id: I1fb39089db9e3dd828a7f6a8da1d65eea6cf2f6d
Signed-off-by: Saad Karim <skarim@us.ibm.com>
  • Loading branch information
Saad Karim committed Dec 12, 2017
1 parent 213edec commit 270a75e
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 30 deletions.
71 changes: 66 additions & 5 deletions src/main/java/org/hyperledger/fabric_ca/sdk/HFCAClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,26 @@ public Enrollment reenroll(User user, EnrollmentRequest req) throws EnrollmentEx
* @throws InvalidArgumentException
*/

public void revoke(User revoker, Enrollment enrollment, String reason) throws RevocationException, InvalidArgumentException {
public void revoke(User revoker, Enrollment enrollment, String reason) throws RevocationException, InvalidArgumentException {
revokeInternal(revoker, enrollment, reason, false);
}

/**
* revoke one enrollment of user
*
* @param revoker admin user who has revoker attribute configured in CA-server
* @param enrollment the user enrollment to be revoked
* @param reason revoke reason, see RFC 5280
* @param genCRL generate CRL list
* @throws RevocationException
* @throws InvalidArgumentException
*/

public String revoke(User revoker, Enrollment enrollment, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {
return revokeInternal(revoker, enrollment, reason, genCRL);
}

private String revokeInternal(User revoker, Enrollment enrollment, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {

if (cryptoSuite == null) {
throw new InvalidArgumentException("Crypto primitives not set.");
Expand Down Expand Up @@ -549,14 +568,25 @@ public void revoke(User revoker, Enrollment enrollment, String reason) throws Re
String aki = DatatypeConverter.printHexBinary(AuthorityKeyIdentifier.getInstance(akiOc.getOctets()).getKeyIdentifier());

// build request body
RevocationRequest req = new RevocationRequest(caName, null, serial, aki, reason);
RevocationRequest req = new RevocationRequest(caName, null, serial, aki, reason, genCRL);
String body = req.toJson();

String authHdr = getHTTPAuthCertificate(revoker.getEnrollment(), body);

// send revoke request
httpPost(url + HFCA_REVOKE, body, authHdr);
JsonObject resp = httpPost(url + HFCA_REVOKE, body, authHdr);
logger.debug("revoke done");

if (genCRL) {
if (resp.isEmpty()) {
throw new RevocationException("Failed to return CRL, revoke response is empty");
}
if (resp.isNull("CRL")) {
throw new RevocationException("Failed to return CRL");
}
return resp.getString("CRL");
}
return null;
} catch (CertificateException e) {
logger.error("Cannot validate certificate. Error is: " + e.getMessage());
throw new RevocationException("Error while revoking cert. " + e.getMessage(), e);
Expand All @@ -578,6 +608,25 @@ public void revoke(User revoker, Enrollment enrollment, String reason) throws Re
*/

public void revoke(User revoker, String revokee, String reason) throws RevocationException, InvalidArgumentException {
revokeInternal(revoker, revokee, reason, false);
}

/**
* revoke one user (including his all enrollments)
*
* @param revoker admin user who has revoker attribute configured in CA-server
* @param revokee user who is to be revoked
* @param reason revoke reason, see RFC 5280
* @param genCRL generate CRL
* @throws RevocationException
* @throws InvalidArgumentException
*/

public String revoke(User revoker, String revokee, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {
return revokeInternal(revoker, revokee, reason, genCRL);
}

private String revokeInternal(User revoker, String revokee, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {

if (cryptoSuite == null) {
throw new InvalidArgumentException("Crypto primitives not set.");
Expand All @@ -596,15 +645,27 @@ public void revoke(User revoker, String revokee, String reason) throws Revocatio
setUpSSL();

// build request body
RevocationRequest req = new RevocationRequest(caName, revokee, null, null, reason);
RevocationRequest req = new RevocationRequest(caName, revokee, null, null, reason, genCRL);
String body = req.toJson();

// build auth header
String authHdr = getHTTPAuthCertificate(revoker.getEnrollment(), body);

// send revoke request
httpPost(url + HFCA_REVOKE, body, authHdr);
JsonObject resp = httpPost(url + HFCA_REVOKE, body, authHdr);

logger.debug(format("revoke revokee: %s done.", revokee));

if (genCRL) {
if (resp.isEmpty()) {
throw new RevocationException("Failed to return CRL, revoke response is empty");
}
if (resp.isNull("CRL")) {
throw new RevocationException("Failed to return CRL");
}
return resp.getString("CRL");
}
return null;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RevocationException("Error while revoking the user. " + e.getMessage(), e);
Expand Down
25 changes: 22 additions & 3 deletions src/main/java/org/hyperledger/fabric_ca/sdk/RevocationRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ class RevocationRequest {
private String aki;
// Reason for revocation
private String reason;
// Get CRL list back as part of revoke request
private Boolean genCRL;

// Constructor
RevocationRequest(String caNmae, String id, String serial, String aki, String reason) throws Exception {
RevocationRequest(String caName, String id, String serial, String aki, String reason) throws Exception {
if (isNullOrEmpty(id)) {
if (isNullOrEmpty(serial) || isNullOrEmpty(aki)) {
throw new Exception("Enrollment ID is empty, thus both aki and serial must have non-empty values");
Expand All @@ -50,7 +52,13 @@ class RevocationRequest {
this.serial = serial;
this.aki = aki;
this.reason = reason;
this.caName = caNmae;
this.caName = caName;
}

// Constructor with genCRL parameter
RevocationRequest(String caName, String id, String serial, String aki, String reason, Boolean genCRL) throws Exception {
this(caName, id, serial, aki, reason);
this.genCRL = genCRL;
}

String getUser() {
Expand Down Expand Up @@ -85,6 +93,14 @@ void setReason(String reason) {
this.reason = reason;
}

Boolean getGenCRL() {
return genCRL;
}

void setGenCRL(Boolean genCRL) {
this.genCRL = genCRL;
}

// Convert the revocation request to a JSON string
String toJson() {
StringWriter stringWriter = new StringWriter();
Expand Down Expand Up @@ -113,7 +129,10 @@ private JsonObject toJsonObject() {
if (caName != null) {
factory.add(HFCAClient.FABRIC_CA_REQPROP, caName);
}
factory.add("reason", reason);

if (genCRL != null) {
factory.add("gencrl", genCRL);
}
return factory.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ public RevocationException(String message, Exception parent) {
super(message, parent);
}

public RevocationException(String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@ public class RevocationRequestTest {
private static final String revSerialNmbr = "987654321";
private static final String revAKI = "123456789";
private static final String revReason = "compromised";
private static final Boolean revGenCRL = true;

@Test
public void testNewInstance() {

try {
RevocationRequest testRevocationReq = new RevocationRequest(revCAName, revEnrollmentID, revSerialNmbr,
revAKI, revReason);
revAKI, revReason, revGenCRL);
Assert.assertEquals(testRevocationReq.getUser(), revEnrollmentID);
Assert.assertEquals(testRevocationReq.getSerial(), revSerialNmbr);
Assert.assertEquals(testRevocationReq.getAki(), revAKI);
Assert.assertEquals(testRevocationReq.getReason(), revReason);
Assert.assertEquals(testRevocationReq.getGenCRL(), revGenCRL);

} catch (Exception e) {
Assert.fail("Unexpected Exception " + e.getMessage());
Expand Down
158 changes: 137 additions & 21 deletions src/test/java/org/hyperledger/fabric_ca/sdkintegration/HFCAClientIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
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.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -354,10 +355,146 @@ public void testUserRevoke() throws Exception {
client.reenroll(user);
}

// Tests attempting to revoke a user with Null reason
@Test
public void testUserRevokeNullReason() 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()) {
RegistrationRequest rr = new RegistrationRequest(user.getName(), TEST_USER1_AFFILIATION);
String password = "testUserRevoke";
rr.setSecret(password);
rr.addAttribute(new Attribute("user.role", "department lead"));
rr.addAttribute(new Attribute("hf.revoker", "true"));
user.setEnrollmentSecret(client.register(rr, admin)); // Admin can register other users.
if (!user.getEnrollmentSecret().equals(password)) {
fail("Secret returned from RegistrationRequest not match : " + user.getEnrollmentSecret());
}
}

sleepALittle();

if (!user.isEnrolled()) {
EnrollmentRequest req = new EnrollmentRequest("profile 2", "label 2", null);
req.addHost("example3.ibm.com");
user.setEnrollment(client.enroll(user.getName(), user.getEnrollmentSecret(), req));

// verify
String cert = user.getEnrollment().getCert();
verifyOptions(cert, req);
}

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(), null);
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);
}

// trying to reenroll the revoked user should fail with an EnrollmentException
client.reenroll(user);
}

// Tests revoking a user with genCRL using the revoke API
@Test
public void testUserRevokeGenCRL() throws Exception {

if (testConfig.isRunningAgainstFabric10()) {
return; // needs v1.1
}

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 user1 = getTestUser(TEST_USER1_ORG);
SampleUser user2 = getTestUser(TEST_USER1_ORG);

SampleUser[] users = new SampleUser[]{user1, user2};

for (SampleUser user : users) {
if (!user.isRegistered()) {
RegistrationRequest rr = new RegistrationRequest(user.getName(), TEST_USER1_AFFILIATION);
String password = "testUserRevoke";
rr.setSecret(password);
rr.addAttribute(new Attribute("user.role", "department lead"));
rr.addAttribute(new Attribute("hf.revoker", "true"));
user.setEnrollmentSecret(client.register(rr, admin)); // Admin can register other users.
if (!user.getEnrollmentSecret().equals(password)) {
fail("Secret returned from RegistrationRequest not match : " + user.getEnrollmentSecret());
}
}

sleepALittle();

if (!user.isEnrolled()) {
EnrollmentRequest req = new EnrollmentRequest("profile 2", "label 2", null);
req.addHost("example3.ibm.com");
user.setEnrollment(client.enroll(user.getName(), user.getEnrollmentSecret(), req));

// verify
String cert = user.getEnrollment().getCert();
verifyOptions(cert, req);
}
}

sleepALittle();

int startedWithRevokes = -1;

startedWithRevokes = getRevokes(null).length; //one more after we do this revoke.

// revoke all enrollment of this user and request back a CRL
String crl = client.revoke(admin, user1.getName(), null, true);
assertNotNull("Failed to get CRL using the Revoke API", crl);

final int newRevokes = getRevokes(null).length;

assertEquals(format("Expected one more revocation %d, but got %d", startedWithRevokes + 1, newRevokes), startedWithRevokes + 1, newRevokes);

final int crlLength = parseCRL(crl).length;

assertEquals(format("The number of revokes %d does not equal the number of revoked certificates (%d) in crl", newRevokes, crlLength), newRevokes, crlLength);

// trying to reenroll the revoked user should fail with an EnrollmentException
client.reenroll(user1);

String crl2 = client.revoke(admin, user2.getName(), null, false);
assertEquals("CRL not requested, CRL should be empty", "", crl2);

}


TBSCertList.CRLEntry[] getRevokes(Date r) throws Exception {

String crl = client.generateCRL(admin, r, null, null, null);

return parseCRL(crl);
}

TBSCertList.CRLEntry[] parseCRL(String crl) throws Exception {

Base64.Decoder b64dec = Base64.getDecoder();
final byte[] decode = b64dec.decode(crl.getBytes(UTF_8));

Expand Down Expand Up @@ -433,17 +570,6 @@ public void testEnrollUnknownClient() throws Exception {
clientWithName.enroll(admin.getName(), TEST_ADMIN_PW);
}

// revoke1: revoke(User revoker, Enrollment enrollment, String reason)
@Test
public void testRevoke1NullReason() throws Exception {

thrown.expect(RevocationException.class);
thrown.expectMessage("cannot be null");

SampleUser user = getEnrolledUser(TEST_ADMIN_ORG);
client.revoke(admin, user.getEnrollment(), null);
}

// revoke2: revoke(User revoker, String revokee, String reason)
@Test
public void testRevoke2UnknownUser() throws Exception {
Expand All @@ -454,16 +580,6 @@ public void testRevoke2UnknownUser() throws Exception {
client.revoke(admin, "unknownUser", "remove user2");
}

@Test
public void testRevoke2NullReason() throws Exception {

thrown.expect(RevocationException.class);
thrown.expectMessage("cannot be null");

SampleUser user = getEnrolledUser(TEST_ADMIN_ORG);
client.revoke(admin, user.getName(), null);
}

@Test
public void testMockEnrollSuccessFalse() throws Exception {

Expand Down

0 comments on commit 270a75e

Please sign in to comment.