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