001/* 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2008 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * $Id: AuthnQueryUtil.java,v 1.8 2008/12/03 00:32:31 hengming Exp $ 026 * 027 * Portions Copyrighted 2010-2015 ForgeRock AS. 028 */ 029package com.sun.identity.saml2.profile; 030 031import java.util.ArrayList; 032import java.util.Date; 033import java.util.Iterator; 034import java.util.List; 035import java.security.PrivateKey; 036import java.security.cert.X509Certificate; 037import java.util.Set; 038import javax.servlet.http.HttpServletRequest; 039import javax.servlet.http.HttpServletResponse; 040import javax.xml.soap.SOAPException; 041import javax.xml.soap.SOAPMessage; 042 043import com.sun.identity.saml2.common.SAML2FailoverUtils; 044import com.sun.identity.saml2.common.SOAPCommunicator; 045import org.forgerock.openam.federation.saml2.SAML2TokenRepositoryException; 046import org.w3c.dom.Element; 047 048import com.sun.identity.saml.xmlsig.KeyProvider; 049import com.sun.identity.saml2.assertion.Assertion; 050import com.sun.identity.saml2.assertion.AssertionFactory; 051import com.sun.identity.saml2.assertion.AuthnContext; 052import com.sun.identity.saml2.assertion.AuthnStatement; 053import com.sun.identity.saml2.assertion.EncryptedAssertion; 054import com.sun.identity.saml2.assertion.EncryptedID; 055import com.sun.identity.saml2.assertion.Issuer; 056import com.sun.identity.saml2.assertion.NameID; 057import com.sun.identity.saml2.assertion.Subject; 058import com.sun.identity.saml2.common.SAML2Constants; 059import com.sun.identity.saml2.common.SAML2Exception; 060import com.sun.identity.saml2.common.SAML2Utils; 061import com.sun.identity.saml2.jaxb.entityconfig.AuthnAuthorityConfigElement; 062import com.sun.identity.saml2.jaxb.metadata.AuthnAuthorityDescriptorElement; 063import com.sun.identity.saml2.jaxb.metadata.AuthnQueryServiceElement; 064import com.sun.identity.saml2.jaxb.metadata.SPSSODescriptorElement; 065import com.sun.identity.saml2.key.KeyUtil; 066import com.sun.identity.saml2.meta.SAML2MetaException; 067import com.sun.identity.saml2.meta.SAML2MetaManager; 068import com.sun.identity.saml2.plugins.IDPAccountMapper; 069import com.sun.identity.saml2.plugins.IDPAuthnContextMapper; 070import com.sun.identity.saml2.protocol.AuthnQuery; 071import com.sun.identity.saml2.protocol.ProtocolFactory; 072import com.sun.identity.saml2.protocol.RequestedAuthnContext; 073import com.sun.identity.saml2.protocol.Response; 074import com.sun.identity.saml2.protocol.Status; 075import com.sun.identity.saml2.protocol.StatusCode; 076 077/** 078 * This class provides methods to send or process <code>AuthnQuery</code>. 079 * 080 * @supported.api 081 */ 082 083public class AuthnQueryUtil { 084 085 static KeyProvider keyProvider = KeyUtil.getKeyProviderInstance(); 086 static SAML2MetaManager metaManager = SAML2Utils.getSAML2MetaManager(); 087 088 private AuthnQueryUtil() { 089 } 090 091 /** 092 * This method sends the <code>AuthnQuery</code> to specifiied 093 * authentication authority and returns <code>Response</code> coming 094 * from the authentication authority. 095 * 096 * @param authnQuery the <code>AuthnQuery</code> object 097 * @param authnAuthorityEntityID entity ID of authentication authority 098 * @param realm the realm of hosted entity 099 * @param binding the binding 100 * 101 * @return the <code>Response</code> object 102 * @exception SAML2Exception if the operation is not successful 103 * 104 * @supported.api 105 */ 106 public static Response sendAuthnQuery(AuthnQuery authnQuery, 107 String authnAuthorityEntityID, String realm, String binding) 108 throws SAML2Exception { 109 110 SAML2MetaManager metaManager = SAML2Utils.getSAML2MetaManager(); 111 AuthnAuthorityDescriptorElement aad = null; 112 try { 113 aad = metaManager.getAuthnAuthorityDescriptor(realm, 114 authnAuthorityEntityID); 115 } catch (SAML2MetaException sme) { 116 SAML2Utils.debug.error("AttributeService.sendAuthnQuery:", 117 sme); 118 throw new SAML2Exception( 119 SAML2Utils.bundle.getString("metaDataError")); 120 } 121 122 if (aad == null) { 123 throw new SAML2Exception( 124 SAML2Utils.bundle.getString("authnAuthorityNotFound")); 125 } 126 127 if (binding == null) { 128 throw new SAML2Exception( 129 SAML2Utils.bundle.getString("unsupportedBinding")); 130 } 131 132 String location = null; 133 List authnService = aad.getAuthnQueryService(); 134 for(Iterator iter = authnService.iterator(); iter.hasNext(); ) { 135 AuthnQueryServiceElement authnService1 = 136 (AuthnQueryServiceElement)iter.next(); 137 if (binding.equalsIgnoreCase(authnService1.getBinding())) { 138 location = authnService1.getLocation(); 139 break; 140 } 141 } 142 if (location == null) { 143 throw new SAML2Exception( 144 SAML2Utils.bundle.getString("unsupportedBinding")); 145 } 146 147 if (binding.equalsIgnoreCase(SAML2Constants.SOAP)) { 148 signAuthnQuery(authnQuery, realm, false); 149 return sendAuthnQuerySOAP(authnQuery, location, 150 authnAuthorityEntityID, realm, aad); 151 } else { 152 throw new SAML2Exception( 153 SAML2Utils.bundle.getString("unsupportedBinding")); 154 } 155 } 156 157 /** 158 * This method processes the <code>AuthnQuery</code> coming 159 * from a requester. 160 * 161 * @param authnQuery the <code>AuthnQuery</code> object 162 * @param request the <code>HttpServletRequest</code> object 163 * @param response the <code>HttpServletResponse</code> object 164 * @param authnAuthorityEntityID entity ID of authentication authority 165 * @param realm the realm of hosted entity 166 * 167 * @return the <code>Response</code> object 168 * @exception SAML2Exception if the operation is not successful 169 */ 170 public static Response processAuthnQuery(AuthnQuery authnQuery, 171 HttpServletRequest request, HttpServletResponse response, 172 String authnAuthorityEntityID, String realm) throws SAML2Exception { 173 174 try { 175 verifyAuthnQuery(authnQuery, authnAuthorityEntityID, realm); 176 } catch(SAML2Exception se) { 177 SAML2Utils.debug.error("AuthnQueryUtil.processAuthnQuery:", se); 178 return SAML2Utils.getErrorResponse(authnQuery, 179 SAML2Constants.REQUESTER, null, se.getMessage(), null); 180 } 181 182 Issuer issuer = authnQuery.getIssuer(); 183 String spEntityID = issuer.getValue(); 184 AuthnAuthorityDescriptorElement aad = null; 185 SAML2MetaManager metaManager = SAML2Utils.getSAML2MetaManager(); 186 try { 187 aad = metaManager.getAuthnAuthorityDescriptor(realm, 188 authnAuthorityEntityID); 189 } catch (SAML2MetaException sme) { 190 SAML2Utils.debug.error("AuthnQueryUtil.processAuthnQuery:", sme); 191 return SAML2Utils.getErrorResponse(authnQuery, 192 SAML2Constants.RESPONDER, null, 193 SAML2Utils.bundle.getString("metaDataError"), null); 194 } 195 196 if (aad == null) { 197 return SAML2Utils.getErrorResponse(authnQuery, 198 SAML2Constants.REQUESTER, null, 199 SAML2Utils.bundle.getString("authnAuthorityNotFound"), null); 200 } 201 202 NameID nameID = getNameID(authnQuery.getSubject(), realm, 203 authnAuthorityEntityID); 204 205 if (nameID == null) { 206 return SAML2Utils.getErrorResponse(authnQuery, 207 SAML2Constants.REQUESTER, SAML2Constants.UNKNOWN_PRINCIPAL, 208 null, null); 209 } 210 211 IDPAccountMapper idpAcctMapper = SAML2Utils.getIDPAccountMapper( 212 realm, authnAuthorityEntityID); 213 214 String userID = idpAcctMapper.getIdentity(nameID, 215 authnAuthorityEntityID, spEntityID, realm); 216 217 if (userID == null) { 218 return SAML2Utils.getErrorResponse(authnQuery, 219 SAML2Constants.REQUESTER, SAML2Constants.UNKNOWN_PRINCIPAL, 220 null, null); 221 } 222 223 IDPAuthnContextMapper idpAuthnContextMapper = 224 IDPSSOUtil.getIDPAuthnContextMapper(realm, authnAuthorityEntityID); 225 226 // get assertion for matching authncontext using session 227 List returnAssertions = new ArrayList(); 228 String qSessionIndex = authnQuery.getSessionIndex(); 229 RequestedAuthnContext requestedAC = 230 authnQuery.getRequestedAuthnContext(); 231 232 List assertions = null; 233 String cacheKey = userID.toLowerCase(); 234 AssertionFactory assertionFactory = AssertionFactory.getInstance(); 235 if (SAML2FailoverUtils.isSAML2FailoverEnabled()) { 236 if (SAML2Utils.debug.messageEnabled()) { 237 SAML2Utils.debug.message("AuthnQueryUtil.processAuthnQuery: " + 238 "getting user assertions from DB. user = " + cacheKey); 239 } 240 List list = null; 241 try { 242 list = SAML2FailoverUtils.retrieveSAML2TokensWithSecondaryKey(cacheKey); 243 } catch(SAML2TokenRepositoryException se) { 244 SAML2Utils.debug.error("AuthnQueryUtil.processAuthnQuery: " + 245 "Unable to obtain user assertions from CTS Repository. user = " + cacheKey, se); 246 } 247 if (list != null && !list.isEmpty()) { 248 assertions = new ArrayList(); 249 for (Iterator iter = list.iterator(); iter.hasNext(); ) { 250 String assertionStr = (String)iter.next(); 251 assertions.add(assertionFactory.createAssertion( 252 assertionStr)); 253 } 254 } 255 } else { 256 assertions = (List)IDPCache.assertionCache.get(cacheKey); 257 } 258 259 if ((assertions != null) && (!assertions.isEmpty())) { 260 261 synchronized (assertions) { 262 for(Iterator aIter = assertions.iterator(); aIter.hasNext();) { 263 Assertion assertion = (Assertion)aIter.next(); 264 265 if (!assertion.isTimeValid()) { 266 if (SAML2Utils.debug.messageEnabled()) { 267 SAML2Utils.debug.message( 268 "AuthnQueryUtil.processAuthnQuery: " + 269 " assertion " + assertion.getID() + 270 " expired."); 271 } 272 continue; 273 } 274 275 List authnStmts = assertion.getAuthnStatements(); 276 277 for(Iterator asIter = authnStmts.iterator(); 278 asIter.hasNext();){ 279 280 AuthnStatement authnStmt = 281 (AuthnStatement)asIter.next(); 282 AuthnContext authnStmtAC = authnStmt.getAuthnContext(); 283 String sessionIndex = authnStmt.getSessionIndex(); 284 285 String authnStmtACClassRef = 286 authnStmtAC.getAuthnContextClassRef(); 287 if (SAML2Utils.debug.messageEnabled()) { 288 SAML2Utils.debug.message( 289 "AuthnQueryUtil.processAuthnQuery: " + 290 "authnStmtACClassRef is " + 291 authnStmtACClassRef + ", sessionIndex = " + 292 sessionIndex); 293 } 294 295 if ((qSessionIndex != null) && 296 (qSessionIndex.length() != 0) && 297 (!qSessionIndex.equals(sessionIndex))) { 298 continue; 299 } 300 301 if (requestedAC != null) { 302 List requestedACClassRefs = 303 requestedAC.getAuthnContextClassRef(); 304 String comparison = requestedAC.getComparison(); 305 306 if (idpAuthnContextMapper.isAuthnContextMatching( 307 requestedACClassRefs, authnStmtACClassRef, 308 comparison, realm, authnAuthorityEntityID)) { 309 310 returnAssertions.add(assertion); 311 break; 312 } 313 } else { 314 returnAssertions.add(assertion); 315 break; 316 } 317 } 318 } 319 } // end assertion iterator while. 320 } 321 322 ProtocolFactory protocolFactory = ProtocolFactory.getInstance(); 323 Response samlResp = protocolFactory.createResponse(); 324 if (!returnAssertions.isEmpty()) { 325 samlResp.setAssertion(returnAssertions); 326 } 327 samlResp.setID(SAML2Utils.generateID()); 328 samlResp.setInResponseTo(authnQuery.getID()); 329 330 samlResp.setVersion(SAML2Constants.VERSION_2_0); 331 samlResp.setIssueInstant(new Date()); 332 333 Status status = protocolFactory.createStatus(); 334 StatusCode statusCode = protocolFactory.createStatusCode(); 335 statusCode.setValue(SAML2Constants.SUCCESS); 336 status.setStatusCode(statusCode); 337 samlResp.setStatus(status); 338 339 Issuer respIssuer = assertionFactory.createIssuer(); 340 respIssuer.setValue(authnAuthorityEntityID); 341 samlResp.setIssuer(respIssuer); 342 343 signResponse(samlResp, authnAuthorityEntityID, realm, false); 344 345 return samlResp; 346 } 347 348 private static void signAuthnQuery(AuthnQuery authnQuery, String realm, 349 boolean includeCert) throws SAML2Exception { 350 351 String spEntityID = authnQuery.getIssuer().getValue(); 352 353 String alias = SAML2Utils.getSigningCertAlias(realm, spEntityID, 354 SAML2Constants.SP_ROLE); 355 356 PrivateKey signingKey = keyProvider.getPrivateKey(alias); 357 if (signingKey == null) { 358 throw new SAML2Exception( 359 SAML2Utils.bundle.getString("missingSigningCertAlias")); 360 } 361 362 X509Certificate signingCert = null; 363 if (includeCert) { 364 signingCert = keyProvider.getX509Certificate(alias); 365 } 366 367 if (signingKey != null) { 368 authnQuery.sign(signingKey, signingCert); 369 } 370 } 371 372 private static void verifyAuthnQuery(AuthnQuery authnQuery, 373 String authnAuthorityEntityID, String realm) throws SAML2Exception { 374 375 if (!authnQuery.isSigned()) { 376 throw new SAML2Exception(SAML2Utils.bundle.getString( 377 "authnQueryNotSigned")); 378 } 379 380 Issuer issuer = authnQuery.getIssuer(); 381 String spEntityID = issuer.getValue(); 382 383 if (!SAML2Utils.isSourceSiteValid(issuer, realm, 384 authnAuthorityEntityID)) { 385 386 throw new SAML2Exception(SAML2Utils.bundle.getString( 387 "authnQueryIssuerInvalid")); 388 } 389 SPSSODescriptorElement spSSODesc = SAML2Utils.getSAML2MetaManager() 390 .getSPSSODescriptor(realm, spEntityID); 391 if (spSSODesc == null) { 392 throw new SAML2Exception(SAML2Utils.bundle.getString( 393 "authnQueryIssuerNotFound")); 394 } 395 Set<X509Certificate> signingCerts = KeyUtil.getVerificationCerts(spSSODesc, spEntityID, SAML2Constants.SP_ROLE); 396 397 if (!signingCerts.isEmpty()) { 398 boolean valid = authnQuery.isSignatureValid(signingCerts); 399 if (SAML2Utils.debug.messageEnabled()) { 400 SAML2Utils.debug.message( 401 "AuthnQueryUtil.verifyAuthnQuery: " + 402 "Signature validity is : " + valid); 403 } 404 if (!valid) { 405 throw new SAML2Exception(SAML2Utils.bundle.getString( 406 "invalidSignatureAuthnQuery")); 407 } 408 } else { 409 throw new SAML2Exception( 410 SAML2Utils.bundle.getString("missingSigningCertAlias")); 411 } 412 } 413 414 private static void signResponse(Response response, 415 String authnAuthorityEntityID, String realm, boolean includeCert) 416 throws SAML2Exception { 417 418 String alias = SAML2Utils.getSigningCertAlias(realm, 419 authnAuthorityEntityID, SAML2Constants.AUTHN_AUTH_ROLE); 420 421 PrivateKey signingKey = keyProvider.getPrivateKey(alias); 422 if (signingKey == null) { 423 throw new SAML2Exception( 424 SAML2Utils.bundle.getString("missingSigningCertAlias")); 425 } 426 427 X509Certificate signingCert = null; 428 if (includeCert) { 429 signingCert = keyProvider.getX509Certificate(alias); 430 } 431 432 if (signingKey != null) { 433 response.sign(signingKey, signingCert); 434 } 435 } 436 437 private static Response sendAuthnQuerySOAP(AuthnQuery authnQuery, 438 String authnServiceURL, String authnAuthorityEntityID, String realm, 439 AuthnAuthorityDescriptorElement aad) throws SAML2Exception { 440 441 String authnQueryXMLString = authnQuery.toXMLString(true, true); 442 if (SAML2Utils.debug.messageEnabled()) { 443 SAML2Utils.debug.message("AuthnQueryUtil.sendAuthnQuerySOAP: " + 444 "authnQueryXMLString = " + authnQueryXMLString); 445 SAML2Utils.debug.message("AuthnQueryUtil.sendAuthnQuerySOAP: " + 446 "authnServiceURL= " + authnServiceURL); 447 } 448 449 AuthnAuthorityConfigElement config = 450 metaManager.getAuthnAuthorityConfig(realm, authnAuthorityEntityID); 451 authnServiceURL = SAML2Utils.fillInBasicAuthInfo(config, 452 authnServiceURL); 453 454 SOAPMessage resMsg = null; 455 try { 456 resMsg = SOAPCommunicator.getInstance().sendSOAPMessage(authnQueryXMLString, 457 authnServiceURL, true); 458 } catch (SOAPException se) { 459 SAML2Utils.debug.error( 460 "AuthnQueryUtil.sendAuthnQuerySOAP: ", se); 461 throw new SAML2Exception( 462 SAML2Utils.bundle.getString("errorSendingAuthnQuery")); 463 } 464 465 Element respElem = SOAPCommunicator.getInstance().getSamlpElement(resMsg, "Response"); 466 Response response = 467 ProtocolFactory.getInstance().createResponse(respElem); 468 469 if (SAML2Utils.debug.messageEnabled()) { 470 SAML2Utils.debug.message("AuthnQueryUtil.sendAuthnQuerySOAP: " + 471 "response = " + response.toXMLString(true, true)); 472 } 473 474 verifyResponse(response, authnQuery, authnAuthorityEntityID, realm, 475 aad); 476 477 return response; 478 } 479 480 private static void verifyResponse(Response response, 481 AuthnQuery authnQuery, String authnAuthorityEntityID, String realm, 482 AuthnAuthorityDescriptorElement aad) throws SAML2Exception { 483 484 String authnQueryID = authnQuery.getID(); 485 if ((authnQueryID != null) && 486 (!authnQueryID.equals(response.getInResponseTo()))) { 487 488 throw new SAML2Exception( 489 SAML2Utils.bundle.getString("invalidInResponseToAuthnQuery")); 490 } 491 492 Issuer respIssuer = response.getIssuer(); 493 if (respIssuer == null) { 494 return; 495 } 496 497 if (!authnAuthorityEntityID.equals(respIssuer.getValue())) { 498 throw new SAML2Exception(SAML2Utils.bundle.getString( 499 "responseIssuerMismatch")); 500 } 501 502 if (!response.isSigned()) { 503 throw new SAML2Exception(SAML2Utils.bundle.getString( 504 "responseNotSigned")); 505 } 506 507 Set<X509Certificate> signingCerts = KeyUtil.getVerificationCerts(aad, authnAuthorityEntityID, 508 SAML2Constants.AUTHN_AUTH_ROLE); 509 510 if (signingCerts.isEmpty()) { 511 throw new SAML2Exception(SAML2Utils.bundle.getString("missingSigningCertAlias")); 512 } 513 boolean valid = response.isSignatureValid(signingCerts); 514 if (SAML2Utils.debug.messageEnabled()) { 515 SAML2Utils.debug.message("AuthnQueryUtil.verifyResponse: " + 516 "Signature validity is : " + valid); 517 } 518 if (!valid) { 519 throw new SAML2Exception(SAML2Utils.bundle.getString( 520 "invalidSignatureOnResponse")); 521 } 522 523 String spEntityID = authnQuery.getIssuer().getValue(); 524 525 List<Assertion> assertions = response.getAssertion(); 526 if (assertions == null) { 527 List<EncryptedAssertion> encAssertions = response.getEncryptedAssertion(); 528 if (encAssertions != null && !encAssertions.isEmpty()) { 529 Set<PrivateKey> privateKeys = KeyUtil.getDecryptionKeys(realm, spEntityID, SAML2Constants.SP_ROLE); 530 for (EncryptedAssertion eAssertion : encAssertions) { 531 Assertion assertion = eAssertion.decrypt(privateKeys); 532 if (assertions == null) { 533 assertions = new ArrayList<>(); 534 } 535 assertions.add(assertion); 536 } 537 } 538 } 539 540 if ((assertions == null) || (assertions.isEmpty())) { 541 return; 542 } 543 544 signingCerts = KeyUtil.getVerificationCerts(aad, authnAuthorityEntityID, SAML2Constants.IDP_ROLE); 545 546 for(Iterator iter = assertions.iterator(); iter.hasNext(); ) { 547 Assertion assertion = (Assertion)iter.next(); 548 if (assertion.isSigned()) { 549 550 if (signingCerts.isEmpty()) { 551 throw new SAML2Exception(SAML2Utils.bundle.getString("missingSigningCertAlias")); 552 } 553 554 valid = assertion.isSignatureValid(signingCerts); 555 if (SAML2Utils.debug.messageEnabled()) { 556 SAML2Utils.debug.message( 557 "AuthnQueryUtil.verifyResponse: " + 558 "Signature validity is : " + valid); 559 } 560 if (!valid) { 561 throw new SAML2Exception(SAML2Utils.bundle.getString( 562 "invalidSignatureOnAssertion")); 563 } 564 } 565 } 566 } 567 568 private static NameID getNameID(Subject subject, String realm, String authnAuthorityEntityID) { 569 NameID nameID = subject.getNameID(); 570 if (nameID == null) { 571 EncryptedID encryptedID = subject.getEncryptedID(); 572 try { 573 nameID = encryptedID.decrypt(KeyUtil.getDecryptionKeys(realm, authnAuthorityEntityID, 574 SAML2Constants.AUTHN_AUTH_ROLE)); 575 } catch (SAML2Exception ex) { 576 if (SAML2Utils.debug.messageEnabled()) { 577 SAML2Utils.debug.message("AuthnQueryUtil.getNameID:", ex); 578 } 579 return null; 580 } 581 } 582 583 if (!SAML2Utils.isPersistentNameID(nameID)) { 584 return null; 585 } 586 587 return nameID; 588 } 589}