1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18 package io.undertow.security.impl;
19
20 import io.undertow.security.api.AuthenticationMechanism;
21 import io.undertow.security.api.AuthenticationMechanismFactory;
22 import io.undertow.security.api.SecurityContext;
23 import io.undertow.security.idm.Account;
24 import io.undertow.security.idm.Credential;
25 import io.undertow.security.idm.IdentityManager;
26 import io.undertow.security.idm.X509CertificateCredential;
27 import io.undertow.server.HttpServerExchange;
28 import io.undertow.server.RenegotiationRequiredException;
29 import io.undertow.server.SSLSessionInfo;
30 import io.undertow.server.handlers.form.FormParserFactory;
31
32 import org.xnio.SslClientAuthMode;
33
34 import javax.net.ssl.SSLPeerUnverifiedException;
35
36 import java.io.IOException;
37 import java.security.cert.Certificate;
38 import java.security.cert.X509Certificate;
39 import java.util.Map;
40
41 /**
42  * The Client Cert based authentication mechanism.
43  * <p>
44  * When authenticate is called the current request is checked to see if it a SSL request, this is further checked to identify if
45  * the client has been verified at the SSL level.
46  *
47  * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
48  */

49 public class ClientCertAuthenticationMechanism implements AuthenticationMechanism {
50
51     public static final AuthenticationMechanismFactory FACTORY = new Factory();
52
53     public static final String FORCE_RENEGOTIATION = "force_renegotiation";
54
55     private final String name;
56     private final IdentityManager identityManager;
57
58     /**
59      * If we should force a renegotiation if client certs were not supplied. <code>true</code> by default
60      */

61     private final boolean forceRenegotiation;
62
63     public ClientCertAuthenticationMechanism() {
64         this(true);
65     }
66
67     public ClientCertAuthenticationMechanism(boolean forceRenegotiation) {
68         this("CLIENT_CERT", forceRenegotiation);
69     }
70
71     public ClientCertAuthenticationMechanism(final String mechanismName) {
72         this(mechanismName, true);
73     }
74
75     public ClientCertAuthenticationMechanism(final String mechanismName, boolean forceRenegotiation) {
76         this(mechanismName, forceRenegotiation, null);
77     }
78
79     public ClientCertAuthenticationMechanism(final String mechanismName, boolean forceRenegotiation, IdentityManager identityManager) {
80         this.name = mechanismName;
81         this.forceRenegotiation = forceRenegotiation;
82         this.identityManager = identityManager;
83     }
84
85     @SuppressWarnings("deprecation")
86     private IdentityManager getIdentityManager(SecurityContext securityContext) {
87         return identityManager != null ? identityManager : securityContext.getIdentityManager();
88     }
89
90     public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) {
91         SSLSessionInfo sslSession = exchange.getConnection().getSslSessionInfo();
92         if (sslSession != null) {
93             try {
94                 Certificate[] clientCerts = getPeerCertificates(exchange, sslSession, securityContext);
95                 if (clientCerts[0] instanceof X509Certificate) {
96                     Credential credential = new X509CertificateCredential((X509Certificate) clientCerts[0]);
97
98                     IdentityManager idm = getIdentityManager(securityContext);
99                     Account account = idm.verify(credential);
100                     if (account != null) {
101                         securityContext.authenticationComplete(account, name, false);
102                         return AuthenticationMechanismOutcome.AUTHENTICATED;
103                     }
104                 }
105             } catch (SSLPeerUnverifiedException e) {
106                 // No action - this mechanism can not attempt authentication without peer certificates so allow it to drop out
107                 // to NOT_ATTEMPTED.
108             }
109         }
110
111         /*
112          * For ClientCert we do not have a concept of a failed authentication, if the client did use a key then it was deemed
113          * acceptable for the connection to be established, this mechanism then just 'attempts' to use it for authentication but
114          * does not mandate success.
115          */

116
117         return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
118     }
119
120     private Certificate[] getPeerCertificates(final HttpServerExchange exchange, SSLSessionInfo sslSession, SecurityContext securityContext) throws SSLPeerUnverifiedException {
121         try {
122             return sslSession.getPeerCertificates();
123         } catch (RenegotiationRequiredException e) {
124             //we only renegotiate if authentication is required
125             if (forceRenegotiation && securityContext.isAuthenticationRequired()) {
126                 try {
127                     sslSession.renegotiate(exchange, SslClientAuthMode.REQUESTED);
128                     return sslSession.getPeerCertificates();
129
130                 } catch (IOException e1) {
131                     //ignore
132                 } catch (RenegotiationRequiredException e1) {
133                     //ignore
134                 }
135             }
136         }
137         throw new SSLPeerUnverifiedException("");
138     }
139
140     @Override
141     public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
142         return ChallengeResult.NOT_SENT;
143     }
144
145     public static final class Factory implements AuthenticationMechanismFactory {
146
147         @Deprecated
148         public Factory(IdentityManager identityManager) {}
149
150         public Factory() {}
151
152         @Override
153         public AuthenticationMechanism create(String mechanismName,IdentityManager identityManager, FormParserFactory formParserFactory, Map<String, String> properties) {
154             String forceRenegotiation = properties.get(FORCE_RENEGOTIATION);
155             return new ClientCertAuthenticationMechanism(mechanismName, forceRenegotiation == null ? true : "true".equals(forceRenegotiation), identityManager);
156         }
157     }
158
159 }
160