diff --git a/SOURCES/0001-CVE-2021-20179-Fix-renewal-profile-approval-process.patch b/SOURCES/0001-CVE-2021-20179-Fix-renewal-profile-approval-process.patch new file mode 100644 index 0000000..0063437 --- /dev/null +++ b/SOURCES/0001-CVE-2021-20179-Fix-renewal-profile-approval-process.patch @@ -0,0 +1,170 @@ +From 8b3cb80954a932867c2d4d96eb1cced83fa78996 Mon Sep 17 00:00:00 2001 +From: Fraser Tweedale +Date: Wed, 13 Jan 2021 18:27:46 +1100 +Subject: [PATCH] Fix renewal profile approval process + +Due to a recent change in PKI CLI, the CLI now passes along user +authentication with submissions to the renewal endpoint. Unlike the EE +pages, the REST API has passed along this authentication for a while. +Due to a bug in the RenewalProcessor, requests with credentials against +profiles with no authentication method and no ACLs result in the +certificiate automatically being approved. This occurs because, when +an earlier commit (cb9eb967b5e24f5fde8bbf8ae87aa615b7033db7) modified +the code to allow Light-Weight SubCAs to issue certificates, validation +wasn't done on the passed principal, to see if it was a trusted agent. +Because profiles requring Agent approval have an empty ACL list (as, no +user should be able to submit a certificate request and have it +automatically signed without agent approval), authorize allows any user +to approve this request and thus accepts the AuthToken. + +Critical analysis: the RenewalProcessor code interprets (authToken +!= null) as evidence that the authenticated user is /authorized/ to +immediately issue the certificate. This mismatch of concerns (authn +vs authz) resulted in a misunderstanding of system behaviour. The +"latent" AuthToken (from the HTTP request) was assigned to authToken +without realising that authorization needed to be performed. + +We fix this by splitting the logic on whether the profile defines an +authenticator. If so, we (re)authenticate and authorize the user +according to the profile configuration. + +If the profile does not define an authenticator but there is a +principal in the HTTP request, if (and only if) the user has +permission to approve certificate requests *and* the requested +renewal profile is caManualRenewal (which is hardcoded to be used +for LWCA renewal), then we issue the certificate immediately. This +special case ensures that LWCA renewal keeps working. + +Otherwise, if there is no principal in the HTTP request or the +principal does not have permission to approve certificate requests, +we leave the authToken unset. The resulting renewal request will be +created with status PENDING, i.e. enqueued for agent review. + +Signed-off-by: Fraser Tweedale +Signed-off-by: Alexander Scheel +--- + .../com/netscape/ca/CertificateAuthority.java | 10 +++ + .../cms/servlet/cert/RenewalProcessor.java | 75 +++++++++++++++++-- + 2 files changed, 79 insertions(+), 6 deletions(-) + +diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java +index 07f29fead..50292201b 100644 +--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java ++++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java +@@ -2962,6 +2962,16 @@ public class CertificateAuthority + } + + ProfileSubsystem ps = engine.getProfileSubsystem(); ++ /* NOTE: hard-coding the profile to use for Lightweight CA renewal ++ * might be OK, but caManualRenewal was not the right one to use. ++ * As a consequence, we have an undesirable special case in ++ * RenewalProcessor.processRenewal(). ++ * ++ * We should introduce a new profile specifically for LWCA renewal, ++ * with an authenticator and ACLs to match the authz requirements ++ * for the renewAuthority REST resource itself. Then we can use ++ * it here, and remove the workaround from RenewalProcessor. ++ */ + Profile profile = ps.getProfile("caManualRenewal"); + CertEnrollmentRequest req = CertEnrollmentRequestFactory.create( + new ArgBlock(), profile, httpReq.getLocale()); +diff --git a/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java +index 917c64856..75677b5e4 100644 +--- a/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java ++++ b/base/ca/src/com/netscape/cms/servlet/cert/RenewalProcessor.java +@@ -31,6 +31,7 @@ import java.util.Map; + import javax.servlet.http.HttpServletRequest; + + import org.apache.commons.lang.StringUtils; ++import org.dogtagpki.server.authorization.AuthzToken; + import org.mozilla.jss.netscape.security.x509.BasicConstraintsExtension; + import org.mozilla.jss.netscape.security.x509.X509CertImpl; + +@@ -267,16 +268,78 @@ public class RenewalProcessor extends CertProcessor { + + // before creating the request, authenticate the request + IAuthToken authToken = null; +- Principal principal = request.getUserPrincipal(); +- if (principal instanceof PKIPrincipal) +- authToken = ((PKIPrincipal) principal).getAuthToken(); +- if (authToken == null && authenticator != null) { +- authToken = authenticate(request, origReq, authenticator, context, true, credentials); ++ ++ if (authenticator != null) { ++ /* The profile specifies an authenticator. Use it to ++ * authenticate the user. Ignore the "latent" session ++ * principal (if any). ++ */ ++ authToken = authenticate( ++ request, ++ origReq, ++ authenticator, ++ context, ++ true /* isRenewal */, ++ credentials); ++ } else { ++ /* When authenticator is null, we expect manual agent ++ * review (leave authToken as null). ++ * ++ * But as a special case to ensure Lightweight CA (LWCA) ++ * renewal works, if there is a latent user in the HTTP ++ * request, we use that user (i.e. set authToken to the ++ * principal's IAuthToken) if and only if: ++ * ++ * - The renewal profile is caManualRenewal (LWCA renewal ++ * is hardcoded to use this profile); AND ++ * ++ * - The latent user is authorized to "execute" ++ * certificate requests (i.e. agent approval) ++ * ++ * See also CertificateAuthority.renewAuthority(). ++ */ ++ ++ Principal principal = request.getUserPrincipal(); ++ if ( ++ renewProfileId.equals("caManualRenewal") ++ && principal instanceof PKIPrincipal ++ ) { ++ IAuthToken latentToken = ((PKIPrincipal) principal).getAuthToken(); ++ AuthzToken authzToken = authorize( ++ "DirAclAuthz", latentToken, "certServer.ca.certrequests", "execute"); ++ if (authzToken != null) { ++ // Success (no exception); user is authorized to approve ++ // cert requests. Set the authToken. ++ // ++ // NOTE: This authz does not replace or subsume the ++ // profile-specific authz check below. ++ authToken = latentToken; ++ } else { ++ // leave authToken as null to enqueue a pending request. ++ } ++ } else { ++ // not caManualRenewal or no latent principal; ++ // leave authToken as null to enqueue a pending request. ++ } + } + +- // authentication success, now authorize ++ /* Authorize the request. ++ * ++ * If authToken != null, it will be checked against ACLs specified ++ * in the profile (if any). If ACLs are defined and authToken does ++ * not match, throws an authorization exception. ++ * ++ * If authToken == null, no check is performed (even if the profile ++ * defines ACLs). This is fine, because null authToken will cause ++ * the request status to be 'pending' [agent approval]. ++ */ + authorize(profileId, renewProfile, authToken); + ++ /* At this point, the request will be created. If authToken ++ * is non-null, then the certificate will be issued ++ * immediately. Otherwise the request will be pending. */ ++ ++ + /////////////////////////////////////////////// + // create and populate requests + /////////////////////////////////////////////// +-- +2.29.2 + diff --git a/SOURCES/0004-pkispawn-fails-against-389-ds-1.4.3.19-3458-3465.patch b/SOURCES/0004-pkispawn-fails-against-389-ds-1.4.3.19-3458-3465.patch new file mode 100644 index 0000000..0c1dbcf --- /dev/null +++ b/SOURCES/0004-pkispawn-fails-against-389-ds-1.4.3.19-3458-3465.patch @@ -0,0 +1,80 @@ +From d17df6f22376753b5cd156f1b7f51837cae1f522 Mon Sep 17 00:00:00 2001 +From: jmagne +Date: Mon, 22 Feb 2021 13:44:20 -0800 +Subject: [PATCH] pkispawn fails against 389-ds 1.4.3.19 #3458 (#3465) + +Add suggested patch from stanislavlevin to solve this issue. +Also add f34 to the ipa tests,this time really add the tests. +Upon further review, back out of f34 tests until the infractructure +supports it. + +Also hardcode tomcat app setting in spec file for the moment to +avoid possible glitches on certain platform. + +Co-authored-by: Jack Magne +--- + .../com/netscape/cmscore/apps/CMSEngine.java | 18 +++++++----------- + 1 file changed, 7 insertions(+), 11 deletions(-) + +diff --git a/base/server/src/com/netscape/cmscore/apps/CMSEngine.java b/base/server/src/com/netscape/cmscore/apps/CMSEngine.java +index 295c4d4cc..f40f99136 100644 +--- a/base/server/src/com/netscape/cmscore/apps/CMSEngine.java ++++ b/base/server/src/com/netscape/cmscore/apps/CMSEngine.java +@@ -156,9 +156,8 @@ public class CMSEngine { + + private static final int PW_OK =0; + //private static final int PW_BAD_SETUP = 1; +- private static final int PW_INVALID_PASSWORD = 2; ++ private static final int PW_INVALID_CREDENTIALS = 2; + private static final int PW_CANNOT_CONNECT = 3; +- private static final int PW_NO_USER = 4; + private static final int PW_MAX_ATTEMPTS = 3; + + +@@ -332,16 +331,16 @@ public class CMSEngine { + } + + int iteration = 0; +- int result = PW_INVALID_PASSWORD; ++ int result = PW_INVALID_CREDENTIALS; + + do { + String passwd = mPasswordStore.getPassword(tag, iteration); + result = testLDAPConnection(tag, connInfo, binddn, passwd); + iteration++; +- } while ((result == PW_INVALID_PASSWORD) && (iteration < PW_MAX_ATTEMPTS)); ++ } while ((result == PW_INVALID_CREDENTIALS) && (iteration < PW_MAX_ATTEMPTS)); + + if (result != PW_OK) { +- if ((result == PW_NO_USER) && (tag.equals("replicationdb"))) { ++ if ((result == PW_INVALID_CREDENTIALS) && (tag.equals("replicationdb"))) { + logger.warn( + "CMSEngine: password test execution failed for replicationdb " + + "with NO_SUCH_USER. This may not be a latest instance. Ignoring .."); +@@ -364,7 +363,7 @@ public class CMSEngine { + int ret = PW_OK; + + if (StringUtils.isEmpty(pwd)) { +- return PW_INVALID_PASSWORD; ++ return PW_INVALID_CREDENTIALS; + } + + String host = info.getHost(); +@@ -383,12 +382,9 @@ public class CMSEngine { + + switch (e.getLDAPResultCode()) { + case LDAPException.NO_SUCH_OBJECT: +- logger.debug("CMSEngine: user does not exist: " + binddn); +- ret = PW_NO_USER; +- break; + case LDAPException.INVALID_CREDENTIALS: +- logger.debug("CMSEngine: invalid password"); +- ret = PW_INVALID_PASSWORD; ++ logger.debug("CMSEngine: invalid credentials"); ++ ret = PW_INVALID_CREDENTIALS; + break; + default: + logger.debug("CMSEngine: unable to connect to " + name + ": " + e.getMessage()); +-- +2.29.2 + diff --git a/SPECS/pki-core.spec b/SPECS/pki-core.spec index 004174b..95554a9 100644 --- a/SPECS/pki-core.spec +++ b/SPECS/pki-core.spec @@ -13,7 +13,7 @@ License: GPLv2 and LGPLv2 # For development (unsupported) releases, use x.y.z-0.n.unstable with alpha/beta phase. # For official (supported) releases, use x.y.z-r where r >=1 without alpha/beta phase. Version: 10.9.4 -Release: 1%{?_timestamp}%{?_commit_id}%{?dist} +Release: 3%{?_timestamp}%{?_commit_id}%{?dist} #global _phase -a1 # To create a tarball from a version tag: @@ -36,6 +36,8 @@ Source: https://github.com/dogtagpki/pki/archive/v%{version}%{?_phase}/pki-%{ver # BUILDSTDERR: Download error on https://pypi.org/simple/pytest-runner/: # [Errno 111] Connection refused -- Some packages may not be found! Patch1: 0001-Removed-dependency-on-pytest-runner.patch +Patch2: 0001-CVE-2021-20179-Fix-renewal-profile-approval-process.patch +Patch3: 0004-pkispawn-fails-against-389-ds-1.4.3.19-3458-3465.patch ################################################################################ # NSS @@ -1363,6 +1365,12 @@ fi ################################################################################ %changelog +* Thu Mar 11 2021 Red Hat PKI Team 10.9.4-3 +- Bug # 1933146 - PKI instance creation failed with new 389-ds-base build + +* Thu Feb 11 2021 Red Hat PKI Team 10.9.4-2 +- CVE-2021-20179: Fix unprivileged users can renew any certificate + * Fri Sep 11 2020 Red Hat PKI Team 10.9.4-1 - Rebased to PKI 10.9.4 - Red Hat Bugzilla #1873235 - Fix SSL_ERROR_INAPPROPRIATE_FALLBACK_ALERT in pki ca-user-cert-add