From 6494270363b82683136eeda2332207412f29b906 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 29 Sep 2015 11:17:21 -0400 Subject: [PATCH] Lightweight CAs: implement deletion API and CLI Implement lightweight authority deletion including CLI command. To be deleted an authority must be disabled and have no sub-CAs. Fixes: https://fedorahosted.org/pki/ticket/1324 --- base/ca/shared/conf/acl.ldif | 1 + base/ca/shared/conf/acl.properties | 1 + .../src/com/netscape/ca/CertificateAuthority.java | 75 ++++++++++++++++++++++ .../dogtagpki/server/ca/rest/AuthorityService.java | 36 +++++++++-- .../certsrv/authority/AuthorityClient.java | 5 ++ .../certsrv/authority/AuthorityResource.java | 8 +++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 4 ++ .../netscape/certsrv/ca/CAEnabledException.java | 15 +++++ .../netscape/certsrv/ca/CANotLeafException.java | 16 +++++ .../netscape/certsrv/ca/ICertificateAuthority.java | 6 ++ .../netscape/cmstools/authority/AuthorityCLI.java | 1 + .../cmstools/authority/AuthorityRemoveCLI.java | 72 +++++++++++++++++++++ 12 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 base/common/src/com/netscape/certsrv/ca/CAEnabledException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotLeafException.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java diff --git a/base/ca/shared/conf/acl.ldif b/base/ca/shared/conf/acl.ldif index 54c9f1d5c64b6578de83f1b7ffdff922a69975f4..27d89a3131e9220b4c03b17aad14b364fe97ff20 100644 --- a/base/ca/shared/conf/acl.ldif +++ b/base/ca/shared/conf/acl.ldif @@ -59,3 +59,4 @@ resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="A resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities +resourceACLS: certServer.ca.authorities:delete:allow (delete) group="Administrators":Administrators may delete lightweight authorities diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties index f0b5b9f650ad2fc4bde531ade94347a7280d3089..8b3e9d0eea09e5e3ab8271888ab0532d47b69348 100644 --- a/base/ca/shared/conf/acl.properties +++ b/base/ca/shared/conf/acl.properties @@ -25,3 +25,4 @@ authorities.create = certServer.ca.authorities,create authorities.list = certServer.ca.authorities,list authorities.modify = certServer.ca.authorities,modify authorities.read = certServer.ca.authorities,read +authorities.delete = certServer.ca.authorities,delete diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index d5523c14cc0132422c971b840324bd95bfa1fda9..5440a380ab8c5627f6305ad882159a7bcf3ef88e 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -50,9 +50,11 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoStore; import org.mozilla.jss.crypto.CryptoToken; import org.mozilla.jss.crypto.KeyPairAlgorithm; import org.mozilla.jss.crypto.KeyPairGenerator; +import org.mozilla.jss.crypto.NoSuchItemOnTokenException; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -68,7 +70,9 @@ import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CAEnabledException; import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CANotLeafException; import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; @@ -2627,4 +2631,75 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + public void deleteAuthority() throws EBaseException { + if (isHostAuthority()) + throw new CATypeException("Cannot delete the host CA"); + + if (authorityEnabled) + throw new CAEnabledException("Must disable CA before deletion"); + + boolean hasSubCAs = false; + for (ICertificateAuthority ca : getCAs()) { + AuthorityID parentAID = ca.getAuthorityParentID(); + if (parentAID != null && parentAID.equals(this.authorityID)) { + hasSubCAs = true; + break; + } + } + if (hasSubCAs) + throw new CANotLeafException("CA with sub-CAs cannot be deleted (delete sub-CAs first)"); + + caMap.remove(authorityID); + shutdown(); + + // delete ldap entry + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + try { + conn.delete(dn); + } catch (LDAPException e) { + throw new ELdapException("Error deleting authority entry '" + dn + "': " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + CryptoManager cryptoManager; + try { + cryptoManager = CryptoManager.getInstance(); + } catch (CryptoManager.NotInitializedException e) { + // can't happen + throw new ECAException("CryptoManager not initialized"); + } + + // delete cert + CryptoStore cryptoStore = + cryptoManager.getInternalKeyStorageToken().getCryptoStore(); + try { + cryptoStore.deleteCert(mCaX509Cert); + } catch (NoSuchItemOnTokenException e) { + CMS.debug("deleteAuthority: cert is not on token: " + e); + // if the cert isn't there, never mind + } catch (TokenException e) { + CMS.debug("deleteAuthority: TokenExcepetion while deleting cert: " + e); + throw new ECAException("TokenException while deleting cert: " + e); + } + + // delete key + try { + cryptoStore.deletePrivateKey(mSigningUnit.getPrivateKey()); + } catch (NoSuchItemOnTokenException e) { + CMS.debug("deleteAuthority: private key is not on token: " + e); + // if the key isn't there, never mind + } catch (TokenException e) { + CMS.debug("deleteAuthority: TokenExcepetion while deleting private key: " + e); + // TODO don't know what causes this yet, or how to + // prevent it. + //throw new ECAException("TokenException while deleting private key: " + e); + } + } + } diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java index 2aa0e97d966d7f879a9999966fb5942bb54dcf42..8d0cac3ef997d4daab3198be68efcb66b145f991 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -43,7 +43,9 @@ import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CAEnabledException; import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CANotLeafException; import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.ca.IssuerUnavailableException; @@ -100,7 +102,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ca = hostCA.getCA(aid); @@ -117,7 +119,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -144,7 +146,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -199,7 +201,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -233,6 +235,32 @@ public class AuthorityService extends PKIService implements AuthorityResource { new AuthorityData(null, null, null, null, false, null)); } + @Override + public Response deleteCA(String aidString) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad AuthorityID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.deleteAuthority(); + return createNoContentResponse(); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (CAEnabledException | CANotLeafException e) { + throw new ConflictingOperationException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + private static AuthorityData readAuthorityData(ICertificateAuthority ca) throws PKIException { String dn; diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java index 86de3352e2424211125c146edf759481448a2694..5a80877ca4479058ba88005421c46d092b2df6a6 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -59,4 +59,9 @@ public class AuthorityClient extends Client { return client.getEntity(response, AuthorityData.class); } + public void deleteCA(String aidString) { + Response response = proxy.deleteCA(aidString); + client.getEntity(response, Void.class); + } + } diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java index eaef903db444512dbea6c87b11800130d94a944d..c6dc696247122b5f07802696c38c2f3517341106 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -1,5 +1,6 @@ package com.netscape.certsrv.authority; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -93,4 +94,11 @@ public interface AuthorityResource { @ACLMapping("authorities.modify") public Response disableCA(@PathParam("id") String caIDString); + @DELETE + @Path("{id}") + @ClientResponseType(entityType=Void.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.delete") + public Response deleteCA(@PathParam("id") String caIDString); + } diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java index daac587b75843f5faf75e556d4d0135c8ffc8fd7..9816f87ad02289739c7ff90be1ffc71e086a2bd9 100644 --- a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -29,6 +29,10 @@ public class AuthorityID implements Comparable { return uuid.toString(); } + public boolean equals(AuthorityID aid) { + return this.compareTo(aid) == 0; + } + public int compareTo(AuthorityID aid) { return uuid.compareTo(aid.uuid); } diff --git a/base/common/src/com/netscape/certsrv/ca/CAEnabledException.java b/base/common/src/com/netscape/certsrv/ca/CAEnabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..4c85276f3bf71bb640a3468f4ed4be4a6aa0180c --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CAEnabledException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be performed because + * the CA to which the operation pertains is enabled. + */ +public class CAEnabledException extends ECAException { + + private static final long serialVersionUID = 1056602856006912665L; + + public CAEnabledException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotLeafException.java b/base/common/src/com/netscape/certsrv/ca/CANotLeafException.java new file mode 100644 index 0000000000000000000000000000000000000000..eabca73641cd0f646167fb6b436aaa736e7139b2 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotLeafException.java @@ -0,0 +1,16 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be performed because + * the CA to which the operation pertains is not a leaf CA (ie, has + * sub-CAs). + */ +public class CANotLeafException extends ECAException { + + private static final long serialVersionUID = -2729093578678941399L; + + public CANotLeafException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index 31d5c9277a63ca7c916f39651300b0c9a9061c1e..96bc392294f27d57d9795c2b1b793b8cbc001fda 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -583,4 +583,10 @@ public interface ICertificateAuthority extends ISubsystem { */ public void modifyAuthority(Boolean enabled, String desc) throws EBaseException; + + /** + * Delete this lightweight CA. + */ + public void deleteAuthority() + throws EBaseException; } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java index 99d38ad1b989e171079df78ddd8b2774817ccb33..4fbcfef760086928b2e0e75fe4fc56f1b249b5fd 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -17,6 +17,7 @@ public class AuthorityCLI extends CLI { addModule(new AuthorityCreateCLI(this)); addModule(new AuthorityDisableCLI(this)); addModule(new AuthorityEnableCLI(this)); + addModule(new AuthorityRemoveCLI(this)); } public String getFullName() { diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..42265b180ba85b6097ef2354e969fbd0495ac73b --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java @@ -0,0 +1,72 @@ +package com.netscape.cmstools.authority; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityRemoveCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityRemoveCLI(AuthorityCLI authorityCLI) { + super("del", "Delete Authority", authorityCLI); + this.authorityCLI = authorityCLI; + + options.addOption(null, "force", false, "Force delete"); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No ID specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + if (!cmd.hasOption("force")) { + System.out.print("Are you sure (Y/N)? "); + System.out.flush(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + String line = reader.readLine(); + if (!line.equalsIgnoreCase("Y")) { + System.exit(-1); + } + } + + String aidString = cmdArgs[0]; + authorityCLI.authorityClient.deleteCA(aidString); + MainCLI.printMessage("Deleted authority \"" + aidString + "\""); + } + +} -- 2.4.3