diff --git a/doc/licenses/bouncycastle-pkix-fips-1.0.7/LICENSE b/doc/licenses/bouncycastle-pkix-fips-1.0.7/LICENSE
new file mode 100644
index 000000000..a02bc176b
--- /dev/null
+++ b/doc/licenses/bouncycastle-pkix-fips-1.0.7/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2000 - 2021 The Legion of the Bouncy Castle Inc.
+(https://www.bouncycastle.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/doc/licenses/bouncycastle-pkix-fips-1.0.7/README b/doc/licenses/bouncycastle-pkix-fips-1.0.7/README
new file mode 100644
index 000000000..b362257b8
--- /dev/null
+++ b/doc/licenses/bouncycastle-pkix-fips-1.0.7/README
@@ -0,0 +1,8 @@
+BouncyCastle PKIX APIs, FIPS Distribution (https://www.bouncycastle.org/fips-java)
+-----------------------------------------------------------------------
+
+ Version: 1.0.7
+ From: 'The Legion of Bouncy Castle' (https://www.bouncycastle.org)
+ License(s):
+ MIT (bundled/bouncycastle-pkix-fips-1.0.7/LICENSE)
+
diff --git a/doc/licenses/bouncycastle-pkix-fips-1.0.7/dep-coordinates.txt b/doc/licenses/bouncycastle-pkix-fips-1.0.7/dep-coordinates.txt
new file mode 100644
index 000000000..23ca14e38
--- /dev/null
+++ b/doc/licenses/bouncycastle-pkix-fips-1.0.7/dep-coordinates.txt
@@ -0,0 +1 @@
+org.bouncycastle:bcpkix-fips:jar:1.0.7
diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/pom.xml b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/pom.xml
index c6542ae56..7ece6b40e 100644
--- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/pom.xml
+++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/pom.xml
@@ -119,6 +119,13 @@
jsr311-api
+
+
+ org.bouncycastle
+ bcpkix-fips
+ 1.0.7
+
+
diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java
index 21a2e0c25..75a190d35 100644
--- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java
+++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java
@@ -19,17 +19,14 @@
package org.apache.guacamole.auth.ssl;
import com.google.inject.Inject;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
-import java.security.Principal;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
+import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.naming.InvalidNameException;
@@ -46,9 +43,14 @@ import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
+import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
import org.apache.guacamole.auth.sso.NonceService;
import org.apache.guacamole.auth.sso.SSOResource;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.openssl.PEMParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -247,27 +249,25 @@ public class SSLClientAuthenticationResource extends SSOResource {
public String getUsername(byte[] certificate) throws GuacamoleException {
// Parse and re-verify certificate is valid with respect to timestamps
- X509Certificate cert;
- try (InputStream input = new ByteArrayInputStream(certificate)) {
+ X509CertificateHolder cert;
+ try (Reader reader = new StringReader(new String(certificate, StandardCharsets.UTF_8))) {
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- cert = (X509Certificate) certFactory.generateCertificate(input);
+ PEMParser parser = new PEMParser(reader);
+ cert = (X509CertificateHolder) parser.readObject();
// Verify certificate is valid (it should be given pre-validation
// from SSL termination, but it's worth rechecking for sanity)
- cert.checkValidity();
+ if (!cert.isValidOn(new Date()))
+ throw new GuacamoleClientException("Certificate has expired.");
}
- catch (CertificateException e) {
- throw new GuacamoleClientException("Certificate is not valid: " + e.getMessage(), e);
- }
catch (IOException e) {
- throw new GuacamoleClientException("Certificate could not be read: " + e.getMessage(), e);
+ throw new GuacamoleServerException("Certificate could not be read: " + e.getMessage(), e);
}
- // Extract user's DN from their X.509 certificate
- Principal principal = cert.getSubjectX500Principal();
- return getUsername(principal.getName());
+ // Extract user's DN from their X.509 certificate in LDAP (RFC 4919) format
+ X500Name subject = X500Name.getInstance(RFC4519Style.INSTANCE, cert.getSubject());
+ return getUsername(subject.toString());
}