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.CollectionUtils; 026import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 027import com.nimbusds.oauth2.sdk.util.StringUtils; 028 029import javax.security.auth.x500.X500Principal; 030import java.util.*; 031 032 033/** 034 * Base abstract class for client authentication. 035 * 036 * <p>Related specifications: 037 * 038 * <ul> 039 * <li>OAuth 2.0 (RFC 6749) 040 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 041 * Authorization Grants (RFC 7523) 042 * <li>OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound 043 * Access Tokens (RFC 8705) 044 * </ul> 045 */ 046public abstract class ClientAuthentication { 047 048 049 /** 050 * The client authentication method. 051 */ 052 private final ClientAuthenticationMethod method; 053 054 055 /** 056 * The client ID. 057 */ 058 private final ClientID clientID; 059 060 061 /** 062 * Creates a new abstract client authentication. 063 * 064 * @param method The client authentication method. Must not be 065 * {@code null}. 066 * @param clientID The client identifier. Must not be {@code null}. 067 */ 068 protected ClientAuthentication(final ClientAuthenticationMethod method, final ClientID clientID) { 069 070 this.method = Objects.requireNonNull(method); 071 this.clientID = Objects.requireNonNull(clientID); 072 } 073 074 075 /** 076 * Returns the client authentication method. 077 * 078 * @return The client authentication method. 079 */ 080 public ClientAuthenticationMethod getMethod() { 081 082 return method; 083 } 084 085 086 /** 087 * Returns the client identifier. 088 * 089 * @return The client identifier. 090 */ 091 public ClientID getClientID() { 092 093 return clientID; 094 } 095 096 097 /** 098 * Returns the names of the form parameters if used by the 099 * authentication method. 100 * 101 * @return The form parameter names, empty set if none. 102 */ 103 public abstract Set<String> getFormParameterNames(); 104 105 106 /** 107 * Parses the specified HTTP request for a supported client 108 * authentication (see {@link ClientAuthenticationMethod}). 109 * 110 * @param httpRequest The HTTP request to parse. Must not be 111 * {@code null}. 112 * 113 * @return The client authentication, {@code null} if none or the HTTP 114 * method is not supported. 115 * 116 * @throws ParseException If the client authentication couldn't be 117 * parsed. 118 */ 119 public static ClientAuthentication parse(final HTTPRequest httpRequest) 120 throws ParseException { 121 122 List<ClientAuthentication> candidates = parseCandidates(httpRequest); 123 if (CollectionUtils.isEmpty(candidates)) { 124 return null; 125 } else { 126 return candidates.get(0); 127 } 128 } 129 130 131 /** 132 * Parses the specified HTTP request for a supported client 133 * authentication (see {@link ClientAuthenticationMethod}). 134 * 135 * @param httpRequest The HTTP request to parse. Must not be 136 * {@code null}. 137 * 138 * @return The client authentication candidates, {@code null} if none 139 * or the HTTP method is not supported. 140 * 141 * @throws ParseException If the client authentication couldn't be 142 * parsed. 143 */ 144 public static List<ClientAuthentication> parseCandidates(final HTTPRequest httpRequest) 145 throws ParseException { 146 147 // Preserve legacy order: client_secret_basic, client_secret_post, JWT auth, TLS auth 148 Map<ClientAuthenticationMethod, ClientAuthentication> candidates = new LinkedHashMap<>(); 149 150 // Check for client_secret_basic 151 if (httpRequest.getAuthorization() != null && 152 httpRequest.getAuthorization().startsWith("Basic ")) { 153 154 candidates.put(ClientAuthenticationMethod.CLIENT_SECRET_BASIC, ClientSecretBasic.parse(httpRequest)); 155 } 156 157 // The other methods require HTTP POST with URL-encoded params 158 if (httpRequest.getMethod().equals(HTTPRequest.Method.POST) 159 && httpRequest.getEntityContentType() != null 160 && httpRequest.getEntityContentType().matches(ContentType.APPLICATION_URLENCODED)) { 161 162 Map<String,List<String>> params = httpRequest.getBodyAsFormParameters(); 163 164 // We have client_secret_post 165 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_id")) && StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_secret"))) { 166 candidates.put(ClientAuthenticationMethod.CLIENT_SECRET_POST, ClientSecretPost.parse(httpRequest)); 167 } 168 169 // Do we have a signed JWT assertion? 170 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) && StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) { 171 JWTAuthentication jwtAuth = JWTAuthentication.parse(httpRequest); 172 candidates.put(jwtAuth.getMethod(), jwtAuth); 173 } 174 175 // Client TLS? 176 if (httpRequest.getClientX509Certificate() != null && StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_id"))) { 177 178 // Check for self-issued first (not for self-signed - too CPU expensive) 179 180 X500Principal issuer = httpRequest.getClientX509Certificate().getIssuerX500Principal(); 181 X500Principal subject = httpRequest.getClientX509Certificate().getSubjectX500Principal(); 182 183 if (issuer.equals(subject)) { 184 // Additional checks 185 if (httpRequest.getClientX509CertificateRootDN() != null) { 186 // If TLS proxy set issuer header it must match the certificate's 187 if (! httpRequest.getClientX509CertificateRootDN().equalsIgnoreCase(issuer.toString())) { 188 throw new ParseException("Client X.509 certificate issuer DN doesn't match HTTP request metadata"); 189 } 190 } 191 if (httpRequest.getClientX509CertificateSubjectDN() != null) { 192 // If TLS proxy set subject header it must match the certificate's 193 if (! httpRequest.getClientX509CertificateSubjectDN().equalsIgnoreCase(subject.toString())) { 194 throw new ParseException("Client X.509 certificate subject DN doesn't match HTTP request metadata"); 195 } 196 } 197 198 // Self-issued (assumes self-signed) 199 candidates.put(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, SelfSignedTLSClientAuthentication.parse(httpRequest)); 200 } else { 201 // PKI bound 202 candidates.put(ClientAuthenticationMethod.TLS_CLIENT_AUTH, PKITLSClientAuthentication.parse(httpRequest)); 203 } 204 } 205 } 206 207 if (candidates.isEmpty()) { 208 return null; // no auth 209 } 210 211 Utils.ensureSameClientID(candidates.values()); 212 213 return new ArrayList<>(candidates.values()); 214 } 215 216 217 /** 218 * Applies the authentication to the specified HTTP request by setting 219 * its Authorization header and/or POST entity-body parameters 220 * (according to the implemented client authentication method). 221 * 222 * @param httpRequest The HTTP request. Must not be {@code null}. 223 */ 224 public abstract void applyTo(final HTTPRequest httpRequest); 225}