Blame SOURCES/0001-CVE-2021-20179-Fix-renewal-profile-approval-process.patch

7e02d1
From 8b3cb80954a932867c2d4d96eb1cced83fa78996 Mon Sep 17 00:00:00 2001
7e02d1
From: Fraser Tweedale <ftweedal@redhat.com>
7e02d1
Date: Wed, 13 Jan 2021 18:27:46 +1100
7e02d1
Subject: [PATCH] Fix renewal profile approval process
7e02d1
7e02d1
Due to a recent change in PKI CLI, the CLI now passes along user
7e02d1
authentication with submissions to the renewal endpoint. Unlike the EE
7e02d1
pages, the REST API has passed along this authentication for a while.
7e02d1
Due to a bug in the RenewalProcessor, requests with credentials against
7e02d1
profiles with no authentication method and no ACLs result in the
7e02d1
certificiate automatically being approved. This occurs because, when
7e02d1
an earlier commit (cb9eb967b5e24f5fde8bbf8ae87aa615b7033db7) modified
7e02d1
the code to allow Light-Weight SubCAs to issue certificates, validation
7e02d1
wasn't done on the passed principal, to see if it was a trusted agent.
7e02d1
Because profiles requring Agent approval have an empty ACL list (as, no
7e02d1
user should be able to submit a certificate request and have it
7e02d1
automatically signed without agent approval), authorize allows any user
7e02d1
to approve this request and thus accepts the AuthToken.
7e02d1
7e02d1
Critical analysis: the RenewalProcessor code interprets (authToken
7e02d1
!= null) as evidence that the authenticated user is /authorized/ to
7e02d1
immediately issue the certificate.  This mismatch of concerns (authn
7e02d1
vs authz) resulted in a misunderstanding of system behaviour.  The
7e02d1
"latent" AuthToken (from the HTTP request) was assigned to authToken
7e02d1
without realising that authorization needed to be performed.
7e02d1
7e02d1
We fix this by splitting the logic on whether the profile defines an
7e02d1
authenticator.  If so, we (re)authenticate and authorize the user
7e02d1
according to the profile configuration.
7e02d1
7e02d1
If the profile does not define an authenticator but there is a
7e02d1
principal in the HTTP request, if (and only if) the user has
7e02d1
permission to approve certificate requests *and* the requested
7e02d1
renewal profile is caManualRenewal (which is hardcoded to be used
7e02d1
for LWCA renewal), then we issue the certificate immediately.  This
7e02d1
special case ensures that LWCA renewal keeps working.
7e02d1
7e02d1
Otherwise, if there is no principal in the HTTP request or the
7e02d1
principal does not have permission to approve certificate requests,
7e02d1
we leave the authToken unset.  The resulting renewal request will be
7e02d1
created with status PENDING, i.e. enqueued for agent review.
7e02d1
7e02d1
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
7e02d1
Signed-off-by: Alexander Scheel <ascheel@redhat.com>
7e02d1
---
7e02d1
 .../com/netscape/ca/CertificateAuthority.java | 10 +++
7e02d1
 .../cms/servlet/cert/RenewalProcessor.java    | 75 +++++++++++++++++--
7e02d1
 2 files changed, 79 insertions(+), 6 deletions(-)
7e02d1
7e02d1
diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java
7e02d1
index 07f29fead..50292201b 100644
7e02d1
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
7e02d1
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
7e02d1
@@ -2962,6 +2962,16 @@ public class CertificateAuthority
7e02d1
         }
7e02d1
 
7e02d1
         ProfileSubsystem ps = engine.getProfileSubsystem();
7e02d1
+        /* NOTE: hard-coding the profile to use for Lightweight CA renewal
7e02d1
+         * might be OK, but caManualRenewal was not the right one to use.
7e02d1
+         * As a consequence, we have an undesirable special case in
7e02d1
+         * RenewalProcessor.processRenewal().
7e02d1
+         *
7e02d1
+         * We should introduce a new profile specifically for LWCA renewal,
7e02d1
+         * with an authenticator and ACLs to match the authz requirements
7e02d1
+         * for the renewAuthority REST resource itself.  Then we can use
7e02d1
+         * it here, and remove the workaround from RenewalProcessor.
7e02d1
+         */
7e02d1
         Profile profile = ps.getProfile("caManualRenewal");
7e02d1
         CertEnrollmentRequest req = CertEnrollmentRequestFactory.create(
7e02d1
             new ArgBlock(), profile, httpReq.getLocale());
7e02d1
diff --git a/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
7e02d1
index 917c64856..75677b5e4 100644
7e02d1
--- a/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
7e02d1
+++ b/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
7e02d1
@@ -31,6 +31,7 @@ import java.util.Map;
7e02d1
 import javax.servlet.http.HttpServletRequest;
7e02d1
 
7e02d1
 import org.apache.commons.lang.StringUtils;
7e02d1
+import org.dogtagpki.server.authorization.AuthzToken;
7e02d1
 import org.mozilla.jss.netscape.security.x509.BasicConstraintsExtension;
7e02d1
 import org.mozilla.jss.netscape.security.x509.X509CertImpl;
7e02d1
 
7e02d1
@@ -267,16 +268,78 @@ public class RenewalProcessor extends CertProcessor {
7e02d1
 
7e02d1
             // before creating the request, authenticate the request
7e02d1
             IAuthToken authToken = null;
7e02d1
-            Principal principal = request.getUserPrincipal();
7e02d1
-            if (principal instanceof PKIPrincipal)
7e02d1
-                authToken = ((PKIPrincipal) principal).getAuthToken();
7e02d1
-            if (authToken == null && authenticator != null) {
7e02d1
-                authToken = authenticate(request, origReq, authenticator, context, true, credentials);
7e02d1
+
7e02d1
+            if (authenticator != null) {
7e02d1
+                /* The profile specifies an authenticator.  Use it to
7e02d1
+                 * authenticate the user.  Ignore the "latent" session
7e02d1
+                 * principal (if any).
7e02d1
+                 */
7e02d1
+                authToken = authenticate(
7e02d1
+                    request,
7e02d1
+                    origReq,
7e02d1
+                    authenticator,
7e02d1
+                    context,
7e02d1
+                    true /* isRenewal */,
7e02d1
+                    credentials);
7e02d1
+            } else {
7e02d1
+                /* When authenticator is null, we expect manual agent
7e02d1
+                 * review (leave authToken as null).
7e02d1
+                 *
7e02d1
+                 * But as a special case to ensure Lightweight CA (LWCA)
7e02d1
+                 * renewal works, if there is a latent user in the HTTP
7e02d1
+                 * request, we use that user (i.e. set authToken to the
7e02d1
+                 * principal's IAuthToken) if and only if:
7e02d1
+                 *
7e02d1
+                 * - The renewal profile is caManualRenewal (LWCA renewal
7e02d1
+                 *   is hardcoded to use this profile); AND
7e02d1
+                 *
7e02d1
+                 * - The latent user is authorized to "execute"
7e02d1
+                 *   certificate requests (i.e. agent approval)
7e02d1
+                 *
7e02d1
+                 * See also CertificateAuthority.renewAuthority().
7e02d1
+                 */
7e02d1
+
7e02d1
+                Principal principal = request.getUserPrincipal();
7e02d1
+                if (
7e02d1
+                    renewProfileId.equals("caManualRenewal")
7e02d1
+                    && principal instanceof PKIPrincipal
7e02d1
+                ) {
7e02d1
+                    IAuthToken latentToken = ((PKIPrincipal) principal).getAuthToken();
7e02d1
+                    AuthzToken authzToken = authorize(
7e02d1
+                        "DirAclAuthz", latentToken, "certServer.ca.certrequests", "execute");
7e02d1
+                    if (authzToken != null) {
7e02d1
+                        // Success (no exception); user is authorized to approve
7e02d1
+                        // cert requests.  Set the authToken.
7e02d1
+                        //
7e02d1
+                        // NOTE: This authz does not replace or subsume the
7e02d1
+                        // profile-specific authz check below.
7e02d1
+                        authToken = latentToken;
7e02d1
+                    } else {
7e02d1
+                        // leave authToken as null to enqueue a pending request.
7e02d1
+                    }
7e02d1
+                } else {
7e02d1
+                    // not caManualRenewal or no latent principal;
7e02d1
+                    // leave authToken as null to enqueue a pending request.
7e02d1
+                }
7e02d1
             }
7e02d1
 
7e02d1
-            // authentication success, now authorize
7e02d1
+            /* Authorize the request.
7e02d1
+             *
7e02d1
+             * If authToken != null, it will be checked against ACLs specified
7e02d1
+             * in the profile (if any).  If ACLs are defined and authToken does
7e02d1
+             * not match, throws an authorization exception.
7e02d1
+             *
7e02d1
+             * If authToken == null, no check is performed (even if the profile
7e02d1
+             * defines ACLs).  This is fine, because null authToken will cause
7e02d1
+             * the request status to be 'pending' [agent approval].
7e02d1
+             */
7e02d1
             authorize(profileId, renewProfile, authToken);
7e02d1
 
7e02d1
+            /* At this point, the request will be created.  If authToken
7e02d1
+             * is non-null, then the certificate will be issued
7e02d1
+             * immediately.  Otherwise the request will be pending. */
7e02d1
+
7e02d1
+
7e02d1
             ///////////////////////////////////////////////
7e02d1
             // create and populate requests
7e02d1
             ///////////////////////////////////////////////
7e02d1
-- 
7e02d1
2.29.2
7e02d1