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