org.jolokia.jvmagent.security.ClientCertAuthenticator Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jolokia.jvmagent.security;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.auth.x500.X500Principal;
import com.sun.net.httpserver.Authenticator;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpPrincipal;
import com.sun.net.httpserver.HttpsExchange;
import org.jolokia.jvmagent.JolokiaServerConfig;
public class ClientCertAuthenticator extends Authenticator {
// ASN.1 path to the extended usage info within a CERT
static final String CLIENTAUTH_OID = "1.3.6.1.5.5.7.3.2";
// whether to use client cert authentication
// TODO: not used
private final boolean useSslClientAuthentication;
private final List allowedPrincipals;
private final boolean extendedClientCheck;
/**
* Constructor
*
* @param pConfig full server config (in contrast to the jolokia config use by the http-handler)
*/
public ClientCertAuthenticator(JolokiaServerConfig pConfig) {
useSslClientAuthentication = pConfig.useSslClientAuthentication();
allowedPrincipals = parseAllowedPrincipals(pConfig);
extendedClientCheck = pConfig.getExtendedClientCheck();
}
@Override
public Result authenticate(HttpExchange httpExchange) {
if( !(httpExchange instanceof HttpsExchange) ) {
return new Failure(500);
}
try {
HttpsExchange httpsExchange = (HttpsExchange) httpExchange;
X509Certificate certificate = getClientCert(httpsExchange);
if (certificate == null) {
return new Failure(401);
}
checkCertForClientUsage(certificate);
checkCertForAllowedPrincipals(httpsExchange);
String name="";
try {
name = httpsExchange.getSSLSession().getPeerPrincipal().getName();
} catch (SSLPeerUnverifiedException ignore) {
}
return new Success(new HttpPrincipal(name, "ssl"));
} catch (SecurityException e) {
return new Failure(403);
}
}
// =================================================================================
private X509Certificate getClientCert(HttpsExchange pHttpsExchange) {
try {
Certificate[] peerCerts = pHttpsExchange.getSSLSession().getPeerCertificates();
return peerCerts != null && peerCerts.length > 0 ? (X509Certificate) peerCerts[0] : null;
} catch (SSLPeerUnverifiedException e) {
throw new SecurityException("SSL Peer couldn't be verified");
}
}
// Check the cert's principal against the list of given allowedPrincipals.
// If no allowedPrincipals are given than every principal is allowed.
// If an empty list as allowedPrincipals is given, no one is allowed to access
private void checkCertForClientUsage(X509Certificate clientCert) {
try {
// We required that the extended key usage must be present if we are using
// client cert authentication
if (extendedClientCheck &&
(clientCert.getExtendedKeyUsage() == null ||
!clientCert.getExtendedKeyUsage().contains(CLIENTAUTH_OID))) {
throw new SecurityException("No extended key usage available");
}
} catch (CertificateParsingException e) {
throw new SecurityException("Can't parse client cert");
}
}
private void checkCertForAllowedPrincipals(HttpsExchange pHttpsExchange) {
if (allowedPrincipals != null) {
X500Principal certPrincipal;
try {
certPrincipal = (X500Principal) pHttpsExchange.getSSLSession().getPeerPrincipal();
Set certPrincipalRdns = getPrincipalRdns(certPrincipal);
boolean matchFound = false;
for (LdapName principal : allowedPrincipals) {
if (certPrincipalRdns.containsAll(principal.getRdns())) {
matchFound = true;
break;
}
}
if (!matchFound) {
throw new SecurityException("Principal " + certPrincipal + " not allowed");
}
} catch (SSLPeerUnverifiedException e) {
throw new SecurityException("SSLPeer unverified");
} catch (ClassCastException e) {
throw new SecurityException("Internal: Invalid Principal class provided " + e);
}
}
}
private Set getPrincipalRdns(X500Principal principal) {
try {
LdapName certAsLdapName =new LdapName(principal.getName());
return new HashSet<>(certAsLdapName.getRdns());
} catch (InvalidNameException e) {
throw new SecurityException("Cannot parse '" + principal + "' as LDAP name");
}
}
private List parseAllowedPrincipals(JolokiaServerConfig pConfig) {
List principals = pConfig.getClientPrincipals();
if (principals != null) {
List ret = new ArrayList<>();
for (String principal : principals) {
try {
ret.add(new LdapName(principal));
} catch (InvalidNameException e) {
throw new IllegalArgumentException("Principal '" + principal + "' cannot be parsed as X500 RDNs");
}
}
return ret;
} else {
return null;
}
}
}