>From cac8b430db52b3e738508ca2e514358f7203fb22 Mon Sep 17 00:00:00 2001
From: Andrew Wnuk <awnuk@redhat.com>
Date: Fri, 25 Oct 2013 14:15:08 -0700
Subject: [PATCH] REST interface extension

This patch provides REST interface extension allowing recovery of asymmetric keys.

Ticket #439.
---
 .../src/com/netscape/certsrv/key/KeyData.java      | 16 +++++
 .../netscape/certsrv/key/KeyRecoveryRequest.java   | 33 +++++++++++
 .../netscape/certsrv/key/KeyRequestResource.java   |  9 +++
 .../src/com/netscape/certsrv/key/KeyResource.java  | 11 ++++
 .../src/com/netscape/certsrv/kra/KRAClient.java    | 22 +++++++
 .../com/netscape/cms/servlet/key/KeyService.java   | 61 +++++++++++++++++++
 .../cms/servlet/request/KeyRequestService.java     | 69 ++++++++++++++++++++++
 .../src/com/netscape/cms/servlet/test/DRMTest.java | 38 ++++++++++++
 8 files changed, 259 insertions(+)

diff --git a/base/common/src/com/netscape/certsrv/key/KeyData.java b/base/common/src/com/netscape/certsrv/key/KeyData.java
index 2ee7d1b..b718614 100644
--- a/base/common/src/com/netscape/certsrv/key/KeyData.java
+++ b/base/common/src/com/netscape/certsrv/key/KeyData.java
@@ -39,6 +39,9 @@ public class KeyData {
     @XmlElement
     String nonceData;
 
+    @XmlElement
+    String p12Data;
+
     public KeyData() {
         // required for JAXB (defaults)
     }
@@ -73,4 +76,17 @@ public class KeyData {
         this.nonceData = nonceData;
     }
 
+    /**
+     * @return the p12Data
+     */
+    public String getP12Data() {
+        return p12Data;
+    }
+
+    /**
+     * @param p12Data the p12Data to set
+     */
+    public void setP12Data(String p12Data) {
+        this.p12Data = p12Data;
+    }
 }
diff --git a/base/common/src/com/netscape/certsrv/key/KeyRecoveryRequest.java b/base/common/src/com/netscape/certsrv/key/KeyRecoveryRequest.java
index d14f612..aa69d83 100644
--- a/base/common/src/com/netscape/certsrv/key/KeyRecoveryRequest.java
+++ b/base/common/src/com/netscape/certsrv/key/KeyRecoveryRequest.java
@@ -64,6 +64,12 @@ public class KeyRecoveryRequest {
     @XmlElement
     protected String nonceData;
 
+    @XmlElement
+    protected String certificate;
+
+    @XmlElement
+    protected String passphrase;
+
     public KeyRecoveryRequest() {
         // required for JAXB (defaults)
     }
@@ -152,4 +158,31 @@ public class KeyRecoveryRequest {
         this.nonceData = nonceData;
     }
 
+    /**
+     * @return the certificate
+     */
+    public String getCertificate() {
+        return certificate;
+    }
+
+    /**
+     * @param certificate the certificate to set
+     */
+    public void setCertificate(String certificate) {
+        this.certificate = certificate;
+    }
+
+    /**
+     * @return the passphrase
+     */
+    public String getPassphrase() {
+        return passphrase;
+    }
+
+    /**
+     * @param passphrase the passphrase to set
+     */
+    public void setPassphrase(String passphrase) {
+        this.passphrase = passphrase;
+    }
 }
diff --git a/base/common/src/com/netscape/certsrv/key/KeyRequestResource.java b/base/common/src/com/netscape/certsrv/key/KeyRequestResource.java
index 6fc12d5..3610fed 100644
--- a/base/common/src/com/netscape/certsrv/key/KeyRequestResource.java
+++ b/base/common/src/com/netscape/certsrv/key/KeyRequestResource.java
@@ -72,6 +72,12 @@ public interface KeyRequestResource {
     public KeyRequestInfo recoverKey(KeyRecoveryRequest data);
 
     @POST
+    @Path("recoverKey")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public KeyRequestInfo requestKeyRecovery(KeyRecoveryRequest data);
+
+    @POST
     @Path("{id}/approve")
     public void approveRequest(@PathParam("id") RequestId id);
 
@@ -83,4 +89,7 @@ public interface KeyRequestResource {
     @Path("{id}/cancel")
     public void cancelRequest(@PathParam("id") RequestId id);
 
+    @POST
+    @Path("{id}/approveRecovery")
+    public void approveRecoveryRequest(@PathParam("id") RequestId id);
 }
diff --git a/base/common/src/com/netscape/certsrv/key/KeyResource.java b/base/common/src/com/netscape/certsrv/key/KeyResource.java
index bcca6bb..a18e798 100644
--- a/base/common/src/com/netscape/certsrv/key/KeyResource.java
+++ b/base/common/src/com/netscape/certsrv/key/KeyResource.java
@@ -43,4 +43,15 @@ public interface KeyResource {
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
     public KeyData retrieveKey(MultivaluedMap<String, String> form);
+
+    /**
+     * Used to recover a key
+     * @param data
+     * @return
+     */
+    @POST
+    @Path("retrieveKey")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public KeyData recoverKey(KeyRecoveryRequest data);
 }
diff --git a/base/common/src/com/netscape/certsrv/kra/KRAClient.java b/base/common/src/com/netscape/certsrv/kra/KRAClient.java
index 658aba5..c9fc4b0 100644
--- a/base/common/src/com/netscape/certsrv/kra/KRAClient.java
+++ b/base/common/src/com/netscape/certsrv/kra/KRAClient.java
@@ -140,4 +140,26 @@ public class KRAClient extends SubsystemClient {
     public KeyRequestInfo getRequest(RequestId id) {
         return keyRequestClient.getRequestInfo(id);
     }
+
+    public RequestId requestKeyRecovery(String keyId, String b64Certificate) {
+        // create key recovery request
+        KeyRecoveryRequest data = new KeyRecoveryRequest();
+        data.setKeyId(new KeyId(keyId));
+        data.setCertificate(b64Certificate);
+        KeyRequestInfo info = keyRequestClient.requestKeyRecovery(data);
+        return info.getRequestId();
+    }
+
+    public void approveRecoveryRequest(RequestId recoveryId) {
+        // approve key recovery request
+        keyRequestClient.approveRecoveryRequest(recoveryId);
+    }
+
+    public KeyData recoverKey(RequestId requestId, String passphrase) {
+        // recover key based on approved request
+        KeyRecoveryRequest data = new KeyRecoveryRequest();
+        data.setRequestId(requestId);
+        data.setPassphrase(passphrase);
+        return keyClient.recoverKey(data);
+    }
 }
diff --git a/base/common/src/com/netscape/cms/servlet/key/KeyService.java b/base/common/src/com/netscape/cms/servlet/key/KeyService.java
index f642417..93a73a3 100644
--- a/base/common/src/com/netscape/cms/servlet/key/KeyService.java
+++ b/base/common/src/com/netscape/cms/servlet/key/KeyService.java
@@ -48,6 +48,7 @@ import com.netscape.certsrv.key.KeyRecoveryRequest;
 import com.netscape.certsrv.key.KeyRequestInfo;
 import com.netscape.certsrv.key.KeyResource;
 import com.netscape.certsrv.kra.IKeyRecoveryAuthority;
+import com.netscape.certsrv.kra.IKeyService;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.request.IRequest;
 import com.netscape.certsrv.request.IRequestQueue;
@@ -55,6 +56,7 @@ import com.netscape.certsrv.request.RequestId;
 import com.netscape.certsrv.request.RequestStatus;
 import com.netscape.cms.servlet.base.PKIService;
 import com.netscape.cmsutil.ldap.LDAPUtil;
+import com.netscape.cmsutil.util.Utils;
 
 /**
  * @author alee
@@ -77,6 +79,7 @@ public class KeyService extends PKIService implements KeyResource {
     private IKeyRepository repo;
     private IKeyRecoveryAuthority kra;
     private IRequestQueue queue;
+    private IKeyService service;
 
     private final static String LOGGING_SIGNED_AUDIT_SECURITY_DATA_RETRIEVE_KEY =
             "LOGGING_SIGNED_AUDIT_SECURITY_DATA_RETRIEVE_KEY_5";
@@ -88,6 +91,7 @@ public class KeyService extends PKIService implements KeyResource {
         kra = ( IKeyRecoveryAuthority ) CMS.getSubsystem( "kra" );
         repo = kra.getKeyRepository();
         queue = kra.getRequestQueue();
+        service = (IKeyService) kra;
     }
 
     /**
@@ -369,4 +373,61 @@ public class KeyService extends PKIService implements KeyResource {
                 reason);
         auditor.log(msg);
     }
+
+    /**
+     * Used to retrieve a key
+     * @param data
+     * @return
+     */
+    public KeyData recoverKey(KeyRecoveryRequest data) {
+        // confirm request exists
+        RequestId reqId = data.getRequestId();
+        if (reqId == null) {
+            auditRetrieveKey(ILogger.FAILURE, null, null, "Request id not found");
+            throw new BadRequestException("Request id not found.");
+        }
+
+        IRequest request = null;
+        try {
+            request = queue.findRequest(reqId);
+        } catch (EBaseException e) {
+            auditRetrieveKey(ILogger.FAILURE, reqId, null, "Failed to get request");
+        }
+        if (request == null) {
+            auditRetrieveKey(ILogger.FAILURE, reqId, null, "Request not found");
+            throw new HTTPGoneException("No request record.");
+        }
+        String type = request.getRequestType();
+        RequestStatus status = request.getRequestStatus();
+        if (!IRequest.KEYRECOVERY_REQUEST.equals(type) ||
+            !status.equals(RequestStatus.APPROVED)) {
+            auditRetrieveKey(ILogger.FAILURE, reqId, null, "Unauthorized request.");
+            throw new UnauthorizedException("Unauthorized request.");
+        }
+
+        String passphrase = data.getPassphrase();
+        byte pkcs12[] = null;
+        try {
+            pkcs12 = service.doKeyRecovery(reqId.toString(), passphrase);
+        } catch (EBaseException e) {
+            auditRetrieveKey(ILogger.FAILURE, reqId, null, "Failed to recover key.");
+        }
+        if (pkcs12 == null) {
+            auditRetrieveKey(ILogger.FAILURE, reqId, null, "Key not recovered.");
+            throw new HTTPGoneException("Key not recovered.");
+        }
+        String pkcs12base64encoded = Utils.base64encode(pkcs12);
+
+        KeyData keyData = new KeyData();
+        keyData.setP12Data(pkcs12base64encoded);
+
+        try {
+            queue.processRequest(request);
+            queue.markAsServiced(request);
+        } catch (EBaseException e) {
+            auditRetrieveKey(ILogger.FAILURE, reqId, null, "Request update error.");
+        }
+
+        return keyData;
+    }
 }
diff --git a/base/common/src/com/netscape/cms/servlet/request/KeyRequestService.java b/base/common/src/com/netscape/cms/servlet/request/KeyRequestService.java
index 8db16b5..cdf2b8b 100644
--- a/base/common/src/com/netscape/cms/servlet/request/KeyRequestService.java
+++ b/base/common/src/com/netscape/cms/servlet/request/KeyRequestService.java
@@ -18,6 +18,9 @@
 
 package com.netscape.cms.servlet.request;
 
+import java.math.BigInteger;
+import java.security.cert.CertificateException;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
@@ -25,6 +28,8 @@ import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Request;
 import javax.ws.rs.core.UriInfo;
 
+import netscape.security.x509.X509CertImpl;
+
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
@@ -35,12 +40,17 @@ import com.netscape.certsrv.key.KeyRecoveryRequest;
 import com.netscape.certsrv.key.KeyRequestInfo;
 import com.netscape.certsrv.key.KeyRequestInfos;
 import com.netscape.certsrv.key.KeyRequestResource;
+import com.netscape.certsrv.kra.IKeyRecoveryAuthority;
+import com.netscape.certsrv.kra.IKeyService;
+import com.netscape.certsrv.request.IRequest;
+import com.netscape.certsrv.request.IRequestQueue;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.request.RequestId;
 import com.netscape.certsrv.request.RequestNotFoundException;
 import com.netscape.cms.servlet.base.PKIService;
 import com.netscape.cms.servlet.key.KeyRequestDAO;
 import com.netscape.cmsutil.ldap.LDAPUtil;
+import com.netscape.cmsutil.util.Utils;
 
 /**
  * @author alee
@@ -74,6 +84,16 @@ public class KeyRequestService extends PKIService implements KeyRequestResource
     public static final int DEFAULT_MAXRESULTS = 100;
     public static final int DEFAULT_MAXTIME = 10;
 
+    private IKeyRecoveryAuthority kra;
+    private IRequestQueue queue;
+    private IKeyService service;
+
+    public KeyRequestService() {
+        kra = ( IKeyRecoveryAuthority ) CMS.getSubsystem( "kra" );
+        queue = kra.getRequestQueue();
+        service = (IKeyService) kra;
+    }
+
     /**
      * Used to retrieve key request info for a specific request
      */
@@ -158,6 +178,39 @@ public class KeyRequestService extends PKIService implements KeyRequestResource
         return info;
     }
 
+    public KeyRequestInfo requestKeyRecovery(KeyRecoveryRequest data) {
+        KeyRequestInfo info = null;
+        if (data == null) {
+            throw new BadRequestException("Invalid request.");
+        }
+        String keyId = data.getKeyId().toString();
+        String b64Certificate = data.getCertificate();
+        byte[] certData = Utils.base64decode(b64Certificate);
+        String agentID = servletRequest.getUserPrincipal().getName();
+        String requestId = null;
+        try {
+            requestId = service.initAsyncKeyRecovery(new BigInteger(keyId), new X509CertImpl(certData), agentID);
+            auditRecoveryRequestMade(info.getRequestId(), ILogger.SUCCESS, data.getKeyId());
+        } catch (EBaseException e) {
+            e.printStackTrace();
+            auditRecoveryRequestMade(null, ILogger.FAILURE, data.getKeyId());
+            throw new PKIException(e.toString());
+        } catch (CertificateException e) {
+            e.printStackTrace();
+            throw new PKIException(e.toString());
+        }
+        IRequest request = null;
+        try {
+            request = queue.findRequest(new RequestId(requestId));
+        } catch (EBaseException e) {
+            e.printStackTrace();
+            auditRecoveryRequestMade(null, ILogger.FAILURE, data.getKeyId());
+            throw new PKIException(e.toString());
+        }
+        KeyRequestDAO dao = new KeyRequestDAO();
+        return dao.createCMSRequestInfo(request, uriInfo);
+    }
+
     public void approveRequest(RequestId id) {
         if (id == null) {
             throw new BadRequestException("Invalid request id.");
@@ -206,6 +259,22 @@ public class KeyRequestService extends PKIService implements KeyRequestResource
         }
     }
 
+    public void approveRecoveryRequest(RequestId id) {
+        if (id == null) {
+            throw new BadRequestException("Invalid request id.");
+        }
+        String agentID = servletRequest.getUserPrincipal().getName();
+
+        try {
+            service.addAgentAsyncKeyRecovery(id.toString(), agentID);
+            auditRecoveryRequestChange(id, ILogger.SUCCESS, "approve");
+        } catch (EBaseException e) {
+            e.printStackTrace();
+            auditRecoveryRequestChange(id, ILogger.FAILURE, "approve");
+            throw new PKIException(e.toString());
+        }
+    }
+
     /**
      * Used to generate list of key requests based on the search parameters
      */
diff --git a/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java b/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
index 8b254a5..3c178eb 100644
--- a/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
+++ b/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
@@ -491,6 +491,44 @@ public class DRMTest {
         } catch (RequestNotFoundException e) {
             log("Success: getting non-existent request throws an exception: "+e.getMessage()+" ("+e.getRequestId().toHexString()+")");
         }
+
+        // Test 24: Request x509 key recovery
+        // This test requires to retrieve keyId and matching certificate
+        // from installed instances of CA and DRM
+        String keyID = "1";
+        String b64Certificate = "MIIC+TCCAeGgAwIBAgIBDDANBgkqhkiG9w0BAQsFADBOMSswKQYDVQQKDCJ1c2Vy"+
+                                "c3lzLnJlZGhhdC5jb20gU2VjdXJpdHkgRG9tYWluMR8wHQYDVQQDDBZDQSBTaWdu"+
+                                "aW5nIENlcnRpZmljYXRlMB4XDTEzMTAyNTE5MzQwM1oXDTE0MDQyMzE5MzQwM1ow"+
+                                "EzERMA8GCgmSJomT8ixkAQEMAXgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB"+
+                                "ALhLfGmKvxFsKXPh49q1QsluXU3WlyS1XnpDLgOAhgTNgO4sG6CpPdv6hZYIvQBb"+
+                                "ZQ5bhuML+NXK+Q+EIiNk1cUTxgL3a30sPzy6QaFWxwM8i4uXm4nCBYv7T+n4V6/O"+
+                                "xHIM2Ch/dviAb3vz+M9trErv9t+d2H8jNXT3sHuDb/kvAgMBAAGjgaAwgZ0wHwYD"+
+                                "VR0jBBgwFoAUh1cxWFRY+nMsx4odQQI1GqyFxP8wSwYIKwYBBQUHAQEEPzA9MDsG"+
+                                "CCsGAQUFBzABhi9odHRwOi8vZG9ndGFnMjAudXNlcnN5cy5yZWRoYXQuY29tOjgw"+
+                                "ODAvY2Evb2NzcDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYBBQUHAwIG"+
+                                "CCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4IBAQCvmbUzQOouE2LgQQcKfmgwwJMJ"+
+                                "9tMrPwDUtyFdaIFoPL4uZaujSscaN4IWK2r5vIMJ65jwYCI7sI9En2ZfO28J9dQj"+
+                                "lpqu6TaJ+xtaMk7OvXpVB7lJk73HAttMGjETlkoq/6EjxcugmJsDqVD0b2tO7Vd0"+
+                                "hroBe2uPDHM2ASewZF415lUcRh0URtmxSazTInbyxpmy1wgSJQ0C6fMCeT+hUFlA"+
+                                "0P4k1TIprapGVq7FpKcqlhK2gTBfTSnoO7gmXG/9MxJiYpb/Aph8ptXq6quHz1Mj"+
+                                "greWr3xTsy6gF2yphUEkGHh4v22XvK+FLx9Jb6zloMWA2GG9gpUpvMnl1fH4";
+
+        log("Requesting X509 key recovery.");
+        recoveryRequestId = client.requestKeyRecovery(keyID, b64Certificate);
+        log("Requesting X509 key recovery request: " + recoveryRequestId);
+
+        // Test 25: Approve x509 key recovery
+        log("Approving X509 key recovery request: " + recoveryRequestId);
+        client.approveRecoveryRequest(recoveryRequestId);
+
+        // Test 26: Recover x509 key
+        log("Recovering X509 key based on request: " + recoveryRequestId);
+        try {
+            KeyData recoveredX509Key = client.recoverKey(recoveryRequestId, "netscape");
+            log("Success: X509Key recovered: "+ recoveredX509Key.getP12Data());
+        } catch (RequestNotFoundException e) {
+            log("Error: recovering X509Key");
+        }
     }
 
     private static void log(String string) {
-- 
1.8.3.1

