Skip to content
This repository was archived by the owner on Apr 22, 2025. It is now read-only.

Commit 270a75e

Browse files
author
Saad Karim
committed
[FAB-7127] Revoke missing gencrl
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>
1 parent 213edec commit 270a75e

File tree

5 files changed

+232
-30
lines changed

5 files changed

+232
-30
lines changed

src/main/java/org/hyperledger/fabric_ca/sdk/HFCAClient.java

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,26 @@ public Enrollment reenroll(User user, EnrollmentRequest req) throws EnrollmentEx
516516
* @throws InvalidArgumentException
517517
*/
518518

519-
public void revoke(User revoker, Enrollment enrollment, String reason) throws RevocationException, InvalidArgumentException {
519+
public void revoke(User revoker, Enrollment enrollment, String reason) throws RevocationException, InvalidArgumentException {
520+
revokeInternal(revoker, enrollment, reason, false);
521+
}
522+
523+
/**
524+
* revoke one enrollment of user
525+
*
526+
* @param revoker admin user who has revoker attribute configured in CA-server
527+
* @param enrollment the user enrollment to be revoked
528+
* @param reason revoke reason, see RFC 5280
529+
* @param genCRL generate CRL list
530+
* @throws RevocationException
531+
* @throws InvalidArgumentException
532+
*/
533+
534+
public String revoke(User revoker, Enrollment enrollment, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {
535+
return revokeInternal(revoker, enrollment, reason, genCRL);
536+
}
537+
538+
private String revokeInternal(User revoker, Enrollment enrollment, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {
520539

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

551570
// build request body
552-
RevocationRequest req = new RevocationRequest(caName, null, serial, aki, reason);
571+
RevocationRequest req = new RevocationRequest(caName, null, serial, aki, reason, genCRL);
553572
String body = req.toJson();
554573

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

557576
// send revoke request
558-
httpPost(url + HFCA_REVOKE, body, authHdr);
577+
JsonObject resp = httpPost(url + HFCA_REVOKE, body, authHdr);
559578
logger.debug("revoke done");
579+
580+
if (genCRL) {
581+
if (resp.isEmpty()) {
582+
throw new RevocationException("Failed to return CRL, revoke response is empty");
583+
}
584+
if (resp.isNull("CRL")) {
585+
throw new RevocationException("Failed to return CRL");
586+
}
587+
return resp.getString("CRL");
588+
}
589+
return null;
560590
} catch (CertificateException e) {
561591
logger.error("Cannot validate certificate. Error is: " + e.getMessage());
562592
throw new RevocationException("Error while revoking cert. " + e.getMessage(), e);
@@ -578,6 +608,25 @@ public void revoke(User revoker, Enrollment enrollment, String reason) throws Re
578608
*/
579609

580610
public void revoke(User revoker, String revokee, String reason) throws RevocationException, InvalidArgumentException {
611+
revokeInternal(revoker, revokee, reason, false);
612+
}
613+
614+
/**
615+
* revoke one user (including his all enrollments)
616+
*
617+
* @param revoker admin user who has revoker attribute configured in CA-server
618+
* @param revokee user who is to be revoked
619+
* @param reason revoke reason, see RFC 5280
620+
* @param genCRL generate CRL
621+
* @throws RevocationException
622+
* @throws InvalidArgumentException
623+
*/
624+
625+
public String revoke(User revoker, String revokee, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {
626+
return revokeInternal(revoker, revokee, reason, genCRL);
627+
}
628+
629+
private String revokeInternal(User revoker, String revokee, String reason, boolean genCRL) throws RevocationException, InvalidArgumentException {
581630

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

598647
// build request body
599-
RevocationRequest req = new RevocationRequest(caName, revokee, null, null, reason);
648+
RevocationRequest req = new RevocationRequest(caName, revokee, null, null, reason, genCRL);
600649
String body = req.toJson();
601650

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

605654
// send revoke request
606-
httpPost(url + HFCA_REVOKE, body, authHdr);
655+
JsonObject resp = httpPost(url + HFCA_REVOKE, body, authHdr);
656+
607657
logger.debug(format("revoke revokee: %s done.", revokee));
658+
659+
if (genCRL) {
660+
if (resp.isEmpty()) {
661+
throw new RevocationException("Failed to return CRL, revoke response is empty");
662+
}
663+
if (resp.isNull("CRL")) {
664+
throw new RevocationException("Failed to return CRL");
665+
}
666+
return resp.getString("CRL");
667+
}
668+
return null;
608669
} catch (Exception e) {
609670
logger.error(e.getMessage(), e);
610671
throw new RevocationException("Error while revoking the user. " + e.getMessage(), e);

src/main/java/org/hyperledger/fabric_ca/sdk/RevocationRequest.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ class RevocationRequest {
3838
private String aki;
3939
// Reason for revocation
4040
private String reason;
41+
// Get CRL list back as part of revoke request
42+
private Boolean genCRL;
4143

4244
// Constructor
43-
RevocationRequest(String caNmae, String id, String serial, String aki, String reason) throws Exception {
45+
RevocationRequest(String caName, String id, String serial, String aki, String reason) throws Exception {
4446
if (isNullOrEmpty(id)) {
4547
if (isNullOrEmpty(serial) || isNullOrEmpty(aki)) {
4648
throw new Exception("Enrollment ID is empty, thus both aki and serial must have non-empty values");
@@ -50,7 +52,13 @@ class RevocationRequest {
5052
this.serial = serial;
5153
this.aki = aki;
5254
this.reason = reason;
53-
this.caName = caNmae;
55+
this.caName = caName;
56+
}
57+
58+
// Constructor with genCRL parameter
59+
RevocationRequest(String caName, String id, String serial, String aki, String reason, Boolean genCRL) throws Exception {
60+
this(caName, id, serial, aki, reason);
61+
this.genCRL = genCRL;
5462
}
5563

5664
String getUser() {
@@ -85,6 +93,14 @@ void setReason(String reason) {
8593
this.reason = reason;
8694
}
8795

96+
Boolean getGenCRL() {
97+
return genCRL;
98+
}
99+
100+
void setGenCRL(Boolean genCRL) {
101+
this.genCRL = genCRL;
102+
}
103+
88104
// Convert the revocation request to a JSON string
89105
String toJson() {
90106
StringWriter stringWriter = new StringWriter();
@@ -113,7 +129,10 @@ private JsonObject toJsonObject() {
113129
if (caName != null) {
114130
factory.add(HFCAClient.FABRIC_CA_REQPROP, caName);
115131
}
116-
factory.add("reason", reason);
132+
133+
if (genCRL != null) {
134+
factory.add("gencrl", genCRL);
135+
}
117136
return factory.build();
118137
}
119138
}

src/main/java/org/hyperledger/fabric_ca/sdk/exception/RevocationException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@ public RevocationException(String message, Exception parent) {
2020
super(message, parent);
2121
}
2222

23+
public RevocationException(String message) {
24+
super(message);
25+
}
26+
2327
}

src/test/java/org/hyperledger/fabric_ca/sdk/RevocationRequestTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@ public class RevocationRequestTest {
2323
private static final String revSerialNmbr = "987654321";
2424
private static final String revAKI = "123456789";
2525
private static final String revReason = "compromised";
26+
private static final Boolean revGenCRL = true;
2627

2728
@Test
2829
public void testNewInstance() {
2930

3031
try {
3132
RevocationRequest testRevocationReq = new RevocationRequest(revCAName, revEnrollmentID, revSerialNmbr,
32-
revAKI, revReason);
33+
revAKI, revReason, revGenCRL);
3334
Assert.assertEquals(testRevocationReq.getUser(), revEnrollmentID);
3435
Assert.assertEquals(testRevocationReq.getSerial(), revSerialNmbr);
3536
Assert.assertEquals(testRevocationReq.getAki(), revAKI);
3637
Assert.assertEquals(testRevocationReq.getReason(), revReason);
38+
Assert.assertEquals(testRevocationReq.getGenCRL(), revGenCRL);
3739

3840
} catch (Exception e) {
3941
Assert.fail("Unexpected Exception " + e.getMessage());

src/test/java/org/hyperledger/fabric_ca/sdkintegration/HFCAClientIT.java

Lines changed: 137 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import static java.nio.charset.StandardCharsets.UTF_8;
5656
import static org.junit.Assert.assertEquals;
5757
import static org.junit.Assert.assertFalse;
58+
import static org.junit.Assert.assertNotNull;
5859
import static org.junit.Assert.assertTrue;
5960
import static org.junit.Assert.fail;
6061

@@ -354,10 +355,146 @@ public void testUserRevoke() throws Exception {
354355
client.reenroll(user);
355356
}
356357

358+
// Tests attempting to revoke a user with Null reason
359+
@Test
360+
public void testUserRevokeNullReason() throws Exception {
361+
362+
thrown.expect(EnrollmentException.class);
363+
thrown.expectMessage("Failed to re-enroll user");
364+
365+
Calendar calendar = Calendar.getInstance(); // gets a calendar using the default time zone and locale.
366+
calendar.add(Calendar.SECOND, -1);
367+
Date revokedTinyBitAgoTime = calendar.getTime(); //avoid any clock skewing.
368+
369+
SampleUser user = getTestUser(TEST_USER1_ORG);
370+
371+
if (!user.isRegistered()) {
372+
RegistrationRequest rr = new RegistrationRequest(user.getName(), TEST_USER1_AFFILIATION);
373+
String password = "testUserRevoke";
374+
rr.setSecret(password);
375+
rr.addAttribute(new Attribute("user.role", "department lead"));
376+
rr.addAttribute(new Attribute("hf.revoker", "true"));
377+
user.setEnrollmentSecret(client.register(rr, admin)); // Admin can register other users.
378+
if (!user.getEnrollmentSecret().equals(password)) {
379+
fail("Secret returned from RegistrationRequest not match : " + user.getEnrollmentSecret());
380+
}
381+
}
382+
383+
sleepALittle();
384+
385+
if (!user.isEnrolled()) {
386+
EnrollmentRequest req = new EnrollmentRequest("profile 2", "label 2", null);
387+
req.addHost("example3.ibm.com");
388+
user.setEnrollment(client.enroll(user.getName(), user.getEnrollmentSecret(), req));
389+
390+
// verify
391+
String cert = user.getEnrollment().getCert();
392+
verifyOptions(cert, req);
393+
}
394+
395+
sleepALittle();
396+
397+
int startedWithRevokes = -1;
398+
399+
if (!testConfig.isRunningAgainstFabric10()) {
400+
401+
startedWithRevokes = getRevokes(null).length; //one more after we do this revoke.
402+
}
403+
404+
// revoke all enrollment of this user
405+
client.revoke(admin, user.getName(), null);
406+
if (!testConfig.isRunningAgainstFabric10()) {
407+
final int newRevokes = getRevokes(null).length;
408+
409+
assertEquals(format("Expected one more revocation %d, but got %d", startedWithRevokes + 1, newRevokes), startedWithRevokes + 1, newRevokes);
410+
}
411+
412+
// trying to reenroll the revoked user should fail with an EnrollmentException
413+
client.reenroll(user);
414+
}
415+
416+
// Tests revoking a user with genCRL using the revoke API
417+
@Test
418+
public void testUserRevokeGenCRL() throws Exception {
419+
420+
if (testConfig.isRunningAgainstFabric10()) {
421+
return; // needs v1.1
422+
}
423+
424+
thrown.expect(EnrollmentException.class);
425+
thrown.expectMessage("Failed to re-enroll user");
426+
427+
Calendar calendar = Calendar.getInstance(); // gets a calendar using the default time zone and locale.
428+
calendar.add(Calendar.SECOND, -1);
429+
Date revokedTinyBitAgoTime = calendar.getTime(); //avoid any clock skewing.
430+
431+
SampleUser user1 = getTestUser(TEST_USER1_ORG);
432+
SampleUser user2 = getTestUser(TEST_USER1_ORG);
433+
434+
SampleUser[] users = new SampleUser[]{user1, user2};
435+
436+
for (SampleUser user : users) {
437+
if (!user.isRegistered()) {
438+
RegistrationRequest rr = new RegistrationRequest(user.getName(), TEST_USER1_AFFILIATION);
439+
String password = "testUserRevoke";
440+
rr.setSecret(password);
441+
rr.addAttribute(new Attribute("user.role", "department lead"));
442+
rr.addAttribute(new Attribute("hf.revoker", "true"));
443+
user.setEnrollmentSecret(client.register(rr, admin)); // Admin can register other users.
444+
if (!user.getEnrollmentSecret().equals(password)) {
445+
fail("Secret returned from RegistrationRequest not match : " + user.getEnrollmentSecret());
446+
}
447+
}
448+
449+
sleepALittle();
450+
451+
if (!user.isEnrolled()) {
452+
EnrollmentRequest req = new EnrollmentRequest("profile 2", "label 2", null);
453+
req.addHost("example3.ibm.com");
454+
user.setEnrollment(client.enroll(user.getName(), user.getEnrollmentSecret(), req));
455+
456+
// verify
457+
String cert = user.getEnrollment().getCert();
458+
verifyOptions(cert, req);
459+
}
460+
}
461+
462+
sleepALittle();
463+
464+
int startedWithRevokes = -1;
465+
466+
startedWithRevokes = getRevokes(null).length; //one more after we do this revoke.
467+
468+
// revoke all enrollment of this user and request back a CRL
469+
String crl = client.revoke(admin, user1.getName(), null, true);
470+
assertNotNull("Failed to get CRL using the Revoke API", crl);
471+
472+
final int newRevokes = getRevokes(null).length;
473+
474+
assertEquals(format("Expected one more revocation %d, but got %d", startedWithRevokes + 1, newRevokes), startedWithRevokes + 1, newRevokes);
475+
476+
final int crlLength = parseCRL(crl).length;
477+
478+
assertEquals(format("The number of revokes %d does not equal the number of revoked certificates (%d) in crl", newRevokes, crlLength), newRevokes, crlLength);
479+
480+
// trying to reenroll the revoked user should fail with an EnrollmentException
481+
client.reenroll(user1);
482+
483+
String crl2 = client.revoke(admin, user2.getName(), null, false);
484+
assertEquals("CRL not requested, CRL should be empty", "", crl2);
485+
486+
}
487+
488+
357489
TBSCertList.CRLEntry[] getRevokes(Date r) throws Exception {
358490

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

493+
return parseCRL(crl);
494+
}
495+
496+
TBSCertList.CRLEntry[] parseCRL(String crl) throws Exception {
497+
361498
Base64.Decoder b64dec = Base64.getDecoder();
362499
final byte[] decode = b64dec.decode(crl.getBytes(UTF_8));
363500

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

436-
// revoke1: revoke(User revoker, Enrollment enrollment, String reason)
437-
@Test
438-
public void testRevoke1NullReason() throws Exception {
439-
440-
thrown.expect(RevocationException.class);
441-
thrown.expectMessage("cannot be null");
442-
443-
SampleUser user = getEnrolledUser(TEST_ADMIN_ORG);
444-
client.revoke(admin, user.getEnrollment(), null);
445-
}
446-
447573
// revoke2: revoke(User revoker, String revokee, String reason)
448574
@Test
449575
public void testRevoke2UnknownUser() throws Exception {
@@ -454,16 +580,6 @@ public void testRevoke2UnknownUser() throws Exception {
454580
client.revoke(admin, "unknownUser", "remove user2");
455581
}
456582

457-
@Test
458-
public void testRevoke2NullReason() throws Exception {
459-
460-
thrown.expect(RevocationException.class);
461-
thrown.expectMessage("cannot be null");
462-
463-
SampleUser user = getEnrolledUser(TEST_ADMIN_ORG);
464-
client.revoke(admin, user.getName(), null);
465-
}
466-
467583
@Test
468584
public void testMockEnrollSuccessFalse() throws Exception {
469585

0 commit comments

Comments
 (0)