001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.auth;
019
020
021import com.nimbusds.common.contenttype.ContentType;
022import com.nimbusds.oauth2.sdk.ParseException;
023import com.nimbusds.oauth2.sdk.http.HTTPRequest;
024import com.nimbusds.oauth2.sdk.id.ClientID;
025import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
026import com.nimbusds.oauth2.sdk.util.StringUtils;
027
028import javax.security.auth.x500.X500Principal;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033
034/**
035 * Base abstract class for client authentication at the Token endpoint.
036 *
037 * <p>Related specifications:
038 *
039 * <ul>
040 *     <li>OAuth 2.0 (RFC 6749), section 2.3.
041 *     <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and
042 *         Authorization Grants (RFC 7523), section 2.2.
043 *     <li>OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound
044 *         Access Tokens (draft-ietf-oauth-mtls-15), section 2.
045 * </ul>
046 */
047public abstract class ClientAuthentication {
048        
049        
050        /**
051         * The client authentication method.
052         */
053        private final ClientAuthenticationMethod method;
054
055
056        /**
057         * The client ID.
058         */
059        private final ClientID clientID;
060        
061        
062        /**
063         * Creates a new abstract client authentication.
064         *
065         * @param method   The client authentication method. Must not be
066         *                 {@code null}.
067         * @param clientID The client identifier. Must not be {@code null}.
068         */
069        protected ClientAuthentication(final ClientAuthenticationMethod method, final ClientID clientID) {
070        
071                if (method == null)
072                        throw new IllegalArgumentException("The client authentication method must not be null");
073                
074                this.method = method;
075
076
077                if (clientID == null)
078                        throw new IllegalArgumentException("The client identifier must not be null");
079
080                this.clientID = clientID;
081        }
082        
083        
084        /**
085         * Returns the client authentication method.
086         *
087         * @return The client authentication method.
088         */
089        public ClientAuthenticationMethod getMethod() {
090        
091                return method;
092        }
093
094
095        /**
096         * Returns the client identifier.
097         *
098         * @return The client identifier.
099         */
100        public ClientID getClientID() {
101
102                return clientID;
103        }
104        
105        
106        /**
107         * Returns the name of the form parameters, if such are used by the
108         * authentication method.
109         *
110         * @return The form parameter names, empty set if none.
111         */
112        public abstract Set<String> getFormParameterNames();
113        
114        
115        /**
116         * Parses the specified HTTP request for a supported client 
117         * authentication (see {@link ClientAuthenticationMethod}). This method
118         * is intended to aid parsing of authenticated 
119         * {@link com.nimbusds.oauth2.sdk.TokenRequest}s.
120         *
121         * @param httpRequest The HTTP request to parse. Must not be 
122         *                    {@code null}.
123         *
124         * @return The client authentication method, {@code null} if none or 
125         *         the method is not supported.
126         *
127         * @throws ParseException If the inferred client authentication 
128         *                        couldn't be parsed.
129         */
130        public static ClientAuthentication parse(final HTTPRequest httpRequest)
131                throws ParseException {
132        
133                // Check for client secret basic
134                if (httpRequest.getAuthorization() != null && 
135                    httpRequest.getAuthorization().startsWith("Basic")) {
136                        
137                        return ClientSecretBasic.parse(httpRequest);
138                }
139                
140                // The other methods require HTTP POST with URL-encoded params
141                if (httpRequest.getMethod() != HTTPRequest.Method.POST &&
142                    ! httpRequest.getEntityContentType().matches(ContentType.APPLICATION_URLENCODED)) {
143                        return null; // no auth
144                }
145                
146                Map<String,List<String>> params = httpRequest.getBodyAsFormParameters();
147                
148                // We have client secret post
149                if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_id")) && StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_secret"))) {
150                        return ClientSecretPost.parse(httpRequest);
151                }
152                
153                // Do we have a signed JWT assertion?
154                if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) && StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) {
155                        return JWTAuthentication.parse(httpRequest);
156                }
157                
158                // Client TLS?
159                if (httpRequest.getClientX509Certificate() != null && StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_id"))) {
160                        
161                        // Check for self-issued first (not for self-signed (too expensive in terms of CPU time)
162                        
163                        X500Principal issuer = httpRequest.getClientX509Certificate().getIssuerX500Principal();
164                        X500Principal subject = httpRequest.getClientX509Certificate().getSubjectX500Principal();
165                        
166                        if (issuer != null && issuer.equals(subject)) {
167                                // Additional checks
168                                if (httpRequest.getClientX509CertificateRootDN() != null) {
169                                        // If TLS proxy set issuer header it must match the certificate's
170                                        if (! httpRequest.getClientX509CertificateRootDN().equalsIgnoreCase(issuer.toString())) {
171                                                throw new ParseException("Client X.509 certificate issuer DN doesn't match HTTP request metadata");
172                                        }
173                                }
174                                if (httpRequest.getClientX509CertificateSubjectDN() != null) {
175                                        // If TLS proxy set subject header it must match the certificate's
176                                        if (! httpRequest.getClientX509CertificateSubjectDN().equalsIgnoreCase(subject.toString())) {
177                                                throw new ParseException("Client X.509 certificate subject DN doesn't match HTTP request metadata");
178                                        }
179                                }
180                                
181                                // Self-issued (assumes self-signed)
182                                return SelfSignedTLSClientAuthentication.parse(httpRequest);
183                        } else {
184                                // PKI bound
185                                return PKITLSClientAuthentication.parse(httpRequest);
186                        }
187                }
188                
189                return null; // no auth
190        }
191        
192        
193        /**
194         * Applies the authentication to the specified HTTP request by setting 
195         * its Authorization header and/or POST entity-body parameters 
196         * (according to the implemented client authentication method).
197         *
198         * @param httpRequest The HTTP request. Must not be {@code null}.
199         */
200        public abstract void applyTo(final HTTPRequest httpRequest);
201}