001/** 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2006 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: SAMLUtils.java,v 1.16 2010/01/09 19:41:06 qcheng Exp $ 026 * 027 */ 028 029/** 030 * Portions Copyrighted 2012 ForgeRock Inc 031 */ 032package com.sun.identity.saml.common; 033 034import java.util.Collections; 035import java.util.Date; 036import java.util.Map; 037import java.util.Vector; 038import java.util.Iterator; 039import java.util.List; 040import java.util.Set; 041import java.util.HashSet; 042import java.util.Enumeration; 043import java.util.ArrayList; 044import java.util.HashMap; 045import java.util.StringTokenizer; 046 047import java.text.StringCharacterIterator; 048import java.text.CharacterIterator; 049import java.io.UnsupportedEncodingException; 050import java.io.PrintWriter; 051import java.io.IOException; 052import java.io.ByteArrayInputStream; 053 054import java.security.MessageDigest; 055 056import java.net.URL; 057import java.net.MalformedURLException; 058 059import org.w3c.dom.*; 060 061import com.sun.identity.shared.ldap.util.DN; 062import com.sun.identity.shared.ldap.util.RDN; 063import com.sun.identity.common.PeriodicGroupRunnable; 064import com.sun.identity.common.ScheduleableGroupAction; 065import com.sun.identity.common.SystemConfigurationUtil; 066import com.sun.identity.common.SystemConfigurationException; 067import com.sun.identity.common.SystemTimerPool; 068import com.sun.identity.common.TaskRunnable; 069import com.sun.identity.common.TimerPool; 070import com.sun.identity.shared.xml.XMLUtils; 071import com.sun.identity.shared.encode.URLEncDec; 072import com.sun.identity.shared.encode.Base64; 073import com.sun.identity.shared.encode.CookieUtils; 074import javax.servlet.ServletException; 075import javax.servlet.http.Cookie; 076import javax.servlet.http.HttpServletResponse; 077import javax.servlet.http.HttpServletRequest; 078import javax.servlet.RequestDispatcher; 079 080import javax.security.auth.callback.Callback; 081import javax.security.auth.callback.NameCallback; 082import javax.security.auth.callback.UnsupportedCallbackException; 083import javax.xml.soap.MimeHeaders; 084import javax.xml.soap.MimeHeader; 085 086import com.sun.identity.saml.assertion.SubjectConfirmation; 087import com.sun.identity.saml.assertion.Assertion; 088import com.sun.identity.saml.assertion.Attribute; 089import com.sun.identity.saml.assertion.AttributeStatement; 090import com.sun.identity.saml.assertion.AuthenticationStatement; 091import com.sun.identity.saml.assertion.AudienceRestrictionCondition; 092import com.sun.identity.saml.assertion.Condition; 093import com.sun.identity.saml.assertion.Conditions; 094import com.sun.identity.saml.assertion.Statement; 095import com.sun.identity.saml.assertion.SubjectStatement; 096import com.sun.identity.saml.common.SAMLConstants; 097import com.sun.identity.saml.common.SAMLServiceManager; 098import com.sun.identity.saml.xmlsig.XMLSignatureManager; 099import com.sun.identity.saml.plugins.PartnerAccountMapper; 100import com.sun.identity.saml.protocol.*; 101import com.sun.identity.saml.servlet.POSTCleanUpRunnable; 102import com.sun.identity.shared.Constants; 103import com.sun.identity.plugin.session.SessionException; 104import com.sun.identity.plugin.session.SessionManager; 105import com.sun.identity.plugin.session.SessionProvider; 106import com.sun.identity.plugin.configuration.ConfigurationException; 107import com.sun.identity.plugin.configuration.ConfigurationInstance; 108import com.sun.identity.plugin.configuration.ConfigurationManager; 109import com.sun.identity.saml.assertion.Subject; 110import com.sun.identity.saml.SAMLClient; 111import com.sun.identity.plugin.session.SessionException; 112import com.sun.identity.plugin.session.SessionManager; 113import com.sun.identity.plugin.session.SessionProvider; 114import com.sun.identity.federation.common.FSUtils; 115 116import javax.xml.parsers.DocumentBuilder; 117import java.io.ByteArrayInputStream; 118import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer; 119//import com.sun.org.apache.xml.internal.security.Init; 120 121/** 122 * This class contains some utility methods for processing SAML protocols. 123 * 124 * @supported.api 125 */ 126public class SAMLUtils extends SAMLUtilsCommon { 127 128 /** 129 * Attribute that specifies maximum content length for SAML request in 130 * <code>AMConfig.properties</code> file. 131 */ 132 public static final String HTTP_MAX_CONTENT_LENGTH = 133 "com.sun.identity.saml.request.maxContentLength"; 134 135 /** 136 * Default maximum content length is set to 16k. 137 */ 138 public static final int defaultMaxLength = 16384; 139 140 /** 141 * Default maximum content length in string format. 142 */ 143 public static final String DEFAULT_CONTENT_LENGTH = 144 String.valueOf(defaultMaxLength); 145 146 private static int maxContentLength = 0; 147 private static Map idTimeMap = Collections.synchronizedMap(new HashMap()); 148 private static TaskRunnable cGoThrough = null; 149 private static TaskRunnable cPeriodic = null; 150 private static Object ssoToken; 151 152 static { 153 com.sun.org.apache.xml.internal.security.Init.init(); 154 if (SystemConfigurationUtil.isServerMode()) { 155 long period = ((Integer) SAMLServiceManager.getAttribute( 156 SAMLConstants.CLEANUP_INTERVAL_NAME)).intValue() * 1000; 157 cGoThrough = new POSTCleanUpRunnable(period, idTimeMap); 158 TimerPool timerPool = SystemTimerPool.getTimerPool(); 159 timerPool.schedule(cGoThrough, new Date(((System.currentTimeMillis() 160 + period) / 1000) * 1000)); 161 ScheduleableGroupAction periodicAction = new 162 ScheduleableGroupAction() { 163 public void doGroupAction(Object obj) { 164 idTimeMap.remove(obj); 165 } 166 }; 167 cPeriodic = new PeriodicGroupRunnable(periodicAction, period, 168 180000, true); 169 timerPool.schedule(cPeriodic, new Date(((System.currentTimeMillis() + 170 period) / 1000) * 1000)); 171 } 172 try { 173 maxContentLength = Integer.parseInt(SystemConfigurationUtil. 174 getProperty(SAMLUtils.HTTP_MAX_CONTENT_LENGTH, 175 SAMLUtils.DEFAULT_CONTENT_LENGTH)); 176 } catch (NumberFormatException ne) { 177 SAMLUtils.debug.error("Wrong format of SAML request max content " 178 + "length. Take default value."); 179 maxContentLength= SAMLUtils.defaultMaxLength; 180 } 181 } 182 183 /** 184 * Constructor 185 * iPlanet-PRIVATE-DEFAULT-CONSTRUCTOR 186 */ 187 private SAMLUtils() { 188 } 189 190 191 /** 192 * Generates an ID String with length of SAMLConstants.ID_LENGTH. 193 * @return string the ID String; or null if it fails. 194 */ 195 public static String generateAssertionID() { 196 String encodedID = generateID(); 197 if (encodedID == null) { 198 return null; 199 } 200 201 String id = null; 202 try { 203 id = SystemConfigurationUtil.getServerID( 204 SAMLServiceManager.getServerProtocol(), 205 SAMLServiceManager.getServerHost(), 206 Integer.parseInt(SAMLServiceManager.getServerPort()), 207 SAMLServiceManager.getServerURI()); 208 } catch (Exception ex) { 209 if (SAMLUtils.debug.messageEnabled()) { 210 SAMLUtils.debug.message("SAMLUtil:generateAssertionID: " 211 + "exception obtain serverID:", ex); 212 } 213 } 214 if (id == null) { 215 return encodedID; 216 } else { 217 return (encodedID + id); 218 } 219 } 220 221 /** 222 * Verifies if an element is a type of a specific query. 223 * Currently, this method is used by class AuthenticationQuery, 224 * AuthorizationDecisionQuery, and AttributeQuery. 225 * @param element a DOM Element which needs to be verified. 226 * @param queryname A specific name of a query, for example, 227 * AuthenticationQuery, AuthorizationDecisionQuery, or 228 * AttributeQuery. 229 * @return true if the element is a type of the specified query; false 230 * otherwise. 231 */ 232 public static boolean checkQuery(Element element, String queryname) { 233 String tag = element.getLocalName(); 234 if (tag == null) { 235 return false; 236 } else if (tag.equals("Query") || tag.equals("SubjectQuery")) { 237 NamedNodeMap nm = element.getAttributes(); 238 int len = nm.getLength(); 239 String attrName; 240 Attr attr; 241 boolean found = false; 242 for (int j = 0; j < len; j++) { 243 attr = (Attr) nm.item(j); 244 attrName = attr.getLocalName(); 245 if ((attrName != null) && (attrName.equals("type")) && 246 (attr.getNodeValue().equals(queryname + "Type"))) { 247 found = true; 248 break; 249 } 250 } 251 if (!found) { 252 return false; 253 } 254 } else if (!tag.equals(queryname)) { 255 return false; 256 } 257 return true; 258 } 259 260 /** 261 * Generates sourceID of a site. 262 * @param siteURL a String that uniquely identifies a site. 263 * @return <code>Base64</code> encoded SHA digest of siteURL. 264 */ 265 public static String generateSourceID(String siteURL) { 266 if ((siteURL == null) || (siteURL.length() == 0)) { 267 SAMLUtils.debug.error("SAMLUtils.genrateSourceID: empty siteURL."); 268 return null; 269 } 270 MessageDigest md = null; 271 try { 272 md = MessageDigest.getInstance("SHA"); 273 } catch (Exception e) { 274 SAMLUtils.debug.error("SAMLUtils.generateSourceID: Exception when" 275 + " generating digest:",e); 276 return null; 277 } 278 md.update(SAMLUtils.stringToByteArray(siteURL)); 279 byte byteResult[] = md.digest(); 280 String result = null; 281 try { 282 result = Base64.encode(byteResult).trim(); 283 } catch (Exception e) { 284 SAMLUtils.debug.error("SAMLUtils.generateSourceID: Exception:",e); 285 } 286 return result; 287 } 288 289 /** 290 * Generates assertion handle. 291 * @return 20-byte random string to be used to form an artifact. 292 */ 293 public static String generateAssertionHandle() { 294 if (random == null) { 295 return null; 296 } 297 byte bytes[] = new byte[SAMLConstants.ID_LENGTH]; 298 random.nextBytes(bytes); 299 String id = null; 300 try { 301 id = SystemConfigurationUtil.getServerID( 302 SAMLServiceManager.getServerProtocol(), 303 SAMLServiceManager.getServerHost(), 304 Integer.parseInt(SAMLServiceManager.getServerPort()), 305 SAMLServiceManager.getServerURI()); 306 } catch (Exception ex) { 307 if (SAMLUtils.debug.messageEnabled()) { 308 SAMLUtils.debug.message("SAMLUtil:generateAssertionHandle: " 309 + "exception obtain serverID:", ex); 310 } 311 } 312 if (id != null) { 313 byte idBytes[] = stringToByteArray(id); 314 // TODO: should we check if idBytes.length == 2 ? 315 if (idBytes.length < bytes.length) { 316 for (int i = 1; i <= idBytes.length; i++) { 317 bytes[bytes.length - i] = idBytes[idBytes.length - i]; 318 } 319 } 320 } 321 return byteArrayToString(bytes); 322 } 323 324 /** 325 * Converts a HEX encoded string to a byte array. 326 * @param hexString HEX encoded string 327 * @return byte array. 328 */ 329 public static byte[] hexStringToByteArray(String hexString) { 330 int read = hexString.length(); 331 byte[] byteArray = new byte[read/2]; 332 for (int i=0, j=0; i < read; i++, j++) { 333 String part = hexString.substring(i,i+2); 334 byteArray[j] = 335 new Short(Integer.toString(Integer.parseInt(part,16))). 336 byteValue(); 337 i++; 338 } 339 return byteArray; 340 } 341 342 /** 343 * Converts HEX encoded string to Base64 encoded string. 344 * @param hexString HEX encoded string. 345 * @return Base64 encoded string. 346 */ 347 public static String hexStringToBase64(String hexString) { 348 int read = hexString.length(); 349 byte[] byteArray = new byte[read/2]; 350 for (int i=0, j=0; i < read; i++, j++) { 351 String part = hexString.substring(i,i+2); 352 byteArray[j] = 353 new Short(Integer.toString(Integer.parseInt(part,16))). 354 byteValue(); 355 i++; 356 } 357 String encodedID = null; 358 try { 359 encodedID = Base64.encode(byteArray).trim(); 360 } catch (Exception e) { 361 if (SAMLUtils.debug.messageEnabled()) { 362 SAMLUtils.debug.message("SAMLUtil:hexStringToBase64: " 363 + "exception encode input:", e); 364 } 365 } 366 if (SAMLUtils.debug.messageEnabled()) { 367 SAMLUtils.debug.message("base 64 source id is :"+encodedID); 368 } 369 return encodedID; 370 } 371 372 /** 373 * Gets sourceSite corresponding to an issuer from the partner URL list. 374 * @param issuer The issuer string. 375 * @return SAMLServiceManager.SOAPEntry of the issuer if it's on the list; 376 * null otherwise. 377 */ 378 public static SAMLServiceManager.SOAPEntry getSourceSite(String issuer) { 379 if (issuer == null) { 380 return null; 381 } 382 Map entries = (Map) SAMLServiceManager.getAttribute( 383 SAMLConstants.PARTNER_URLS); 384 if (entries == null) { 385 SAMLUtils.debug.error("SAMLUtils.isOnPartnerURLList: PartnerURL " 386 + "list is null."); 387 return null; 388 } 389 390 Iterator entryIter = entries.values().iterator(); 391 boolean found = false; 392 SAMLServiceManager.SOAPEntry srcSite = null; 393 String theIssuer = null; 394 while (entryIter.hasNext()) { 395 srcSite = (SAMLServiceManager.SOAPEntry) entryIter.next(); 396 if ((srcSite != null) && 397 ((theIssuer = srcSite.getIssuer()) != null) && 398 (theIssuer.equals(issuer))) { 399 found = true; 400 break; 401 } 402 } 403 if (found) { 404 return srcSite; 405 } else { 406 return null; 407 } 408 } 409 410 /** 411 * Returns site ID based on the host name. The site ID 412 * will be in Base64 encoded format. This method will print out site ID 413 * to the standard output 414 * @param args host name 415 */ 416 public static void main(String args[]) { 417 418 if (args.length != 1) { 419 System.out.println("usage : java SAMLUtils <host_name>"); 420 return; 421 } 422 423 System.out.println(generateSourceID(args[0])); 424 } 425 426 /** 427 * Checks if a <code>SubjectConfirmation</code> is correct. 428 * @param sc <code>SubjectConfirmation</code> instance to be checked. 429 * @return true if the <code>SubjectConfirmation</code> instance passed in 430 * has only one <code>ConfirmationMethod</code>, and this 431 * <code>ConfirmationMethod</code> is set to 432 * <code>SAMLConstants.CONFIRMATION_METHOD_IS</code>. 433 */ 434 public static boolean isCorrectConfirmationMethod(SubjectConfirmation sc) { 435 if (sc == null) { 436 return false; 437 } 438 439 Set cmSet = sc.getConfirmationMethod(); 440 if ((cmSet == null) || (cmSet.size() != 1)) { 441 if (SAMLUtils.debug.messageEnabled()) { 442 SAMLUtils.debug.message("SAMLUtils.isCorrectConfirmationMethod:" 443 + " missing ConfirmationMethod in the Subject."); 444 } 445 return false; 446 } 447 448 String conMethod = (String) cmSet.iterator().next(); 449 if ((conMethod == null) || 450 (!conMethod.equals(SAMLConstants.CONFIRMATION_METHOD_IS))) { 451 if (SAMLUtils.debug.messageEnabled()) { 452 SAMLUtils.debug.message("SAMLUtils.isCorrectConfirmationMethod:" 453 + " wrong ConfirmationMethod value."); 454 } 455 return false; 456 } 457 458 return true; 459 } 460 461 /** 462 * Returns true if the assertion is valid both time wise and 463 * signature wise, and contains at least one AuthenticationStatement. 464 * @param assertion <code>Assertion</code> instance to be checked. 465 * @return <code>true</code> if the assertion is valid both time wise and 466 * signature wise, and contains at least one AuthenticationStatement. 467 */ 468 public static boolean isAuthNAssertion(Assertion assertion) { 469 if (assertion == null) { 470 return false; 471 } 472 473 if ((!assertion.isTimeValid()) || (!assertion.isSignatureValid())) { 474 return false; 475 } 476 477 Set statements = assertion.getStatement(); 478 Statement statement = null; 479 Iterator iterator = statements.iterator(); 480 while (iterator.hasNext()) { 481 statement = (Statement) iterator.next(); 482 if (statement.getStatementType() == 483 Statement.AUTHENTICATION_STATEMENT) { 484 return true; 485 } 486 } // loop through statements 487 return false; 488 } 489 490 /** 491 * Converts a string to a byte array. 492 * @param input a String to be converted. 493 * @return result byte array. 494 */ 495 public static byte[] stringToByteArray(String input) { 496 char chars[] = input.toCharArray(); 497 byte bytes[] = new byte[chars.length]; 498 for (int i = 0; i < chars.length; i++) { 499 bytes[i] = (byte) chars[i]; 500 } 501 return bytes; 502 } 503 504 505 /** 506 * Returns server ID. 507 * @param idTypeString An ID string 508 * @return server ID part of the id. 509 */ 510 public static String getServerID(String idTypeString) { 511 if (idTypeString == null) { 512 return null; 513 } 514 int len = idTypeString.length(); 515 String id = null; 516 if (len >= SAMLConstants.SERVER_ID_LENGTH) { 517 id = idTypeString.substring((len - SAMLConstants.SERVER_ID_LENGTH), 518 len); 519 return id; 520 } else { 521 return null; 522 } 523 } 524 525 /** 526 * Returns server url of a site. 527 * @param str Server ID. 528 * @return Server url corresponding to the server id. 529 */ 530 public static String getServerURL(String str) { 531 String id = SAMLUtils.getServerID(str); 532 if (id == null) { 533 return null; 534 } 535 536 if (SAMLUtils.debug.messageEnabled()) { 537 SAMLUtils.debug.message("SAMLUtils.getServerURL: id=" + id); 538 } 539 540 String remoteUrl = null; 541 try { 542 remoteUrl = SystemConfigurationUtil.getServerFromID(id); 543 } catch (SystemConfigurationException se) { 544 if (SAMLUtils.debug.messageEnabled()) { 545 SAMLUtils.debug.message("SAMLUtils.getServerURL: ServerEntry" + 546 "NotFoundException for " + id); 547 } 548 return null; 549 } 550 String thisUrl = SAMLServiceManager.getServerURL(); 551 if (SAMLUtils.debug.messageEnabled()) { 552 SAMLUtils.debug.message("SAMLUtils.getServerURL: remoteUrl=" + 553 remoteUrl + ", thisUrl=" + thisUrl); 554 } 555 if ((remoteUrl == null) || (thisUrl == null) || 556 (remoteUrl.equalsIgnoreCase(thisUrl))) { 557 return null; 558 } else { 559 return remoteUrl; 560 } 561 } 562 563 /** 564 * Returns full service url. 565 * @param shortUrl short URL of the service. 566 * @return full service url. 567 */ 568 public static String getFullServiceURL(String shortUrl) { 569 String result = null; 570 try { 571 URL u = new URL(shortUrl); 572 URL weburl = SystemConfigurationUtil.getServiceURL( 573 SAMLConstants.SAML_AM_NAMING, u.getProtocol(), u.getHost(), 574 u.getPort(), u.getPath()); 575 result = weburl.toString(); 576 if (SAMLUtils.debug.messageEnabled()) { 577 SAMLUtils.debug.message("SAMLUtils.getFullServiceURL:" + 578 "full remote URL is: " + result); 579 } 580 } catch (Exception e) { 581 if (SAMLUtils.debug.warningEnabled()) { 582 SAMLUtils.debug.warning("SAMLUtils.getFullServiceURL:" + 583 "Exception:", e); 584 } 585 } 586 return result; 587 } 588 589 /** 590 * Returns attributes included in <code>AttributeStatement</code> of the 591 * assertion. 592 * @param envParameters return map which includes name value pairs of 593 * attributes included in <code>AttributeStatement</code> of the assertion 594 * @param assertion an <code>Assertion</code> object which contains 595 * <code>AttributeStatement</code> 596 * @param subject the <code>Subject</code> instance from 597 * <code>AuthenticationStatement</code>. The <code>Subject</code> 598 * included in <code>AttributeStatement</code> must match this 599 * <code>Subject</code> instance. 600 */ 601 public static void addEnvParamsFromAssertion(Map envParameters, 602 Assertion assertion, 603 com.sun.identity.saml.assertion.Subject subject) { 604 Set statements = assertion.getStatement(); 605 Statement statement = null; 606 Iterator stmtIter = null; 607 List attrs = null; 608 Iterator attrIter = null; 609 Attribute attribute = null; 610 Element attrValue = null; 611 List attrValues = null; 612 String attrName = null; 613 String attrValueString = null; 614 if ((statements != null) && (!statements.isEmpty())) { 615 stmtIter = statements.iterator(); 616 while (stmtIter.hasNext()) { 617 statement = (Statement) stmtIter.next(); 618 if (statement.getStatementType() == 619 Statement.ATTRIBUTE_STATEMENT) { 620 // check for subject 621 if (!subject.equals( 622 ((AttributeStatement)statement).getSubject())) { 623 continue; 624 } 625 626 attrs = ((AttributeStatement) statement).getAttribute(); 627 attrIter = attrs.iterator(); 628 while (attrIter.hasNext()) { 629 attribute = (Attribute) attrIter.next(); 630 try { 631 attrValues = attribute.getAttributeValue(); 632 } catch (Exception e) { 633 debug.error("SAMLUtils.addEnvParamsFromAssertion:"+ 634 " cannot obtain attribute value:", e); 635 continue; 636 } 637 attrName = attribute.getAttributeName(); 638 List attrValueList = null; 639 640 for(Iterator avIter = attrValues.iterator(); 641 avIter.hasNext(); ) { 642 643 attrValue = (Element) avIter.next(); 644 if (!XMLUtils.hasElementChild(attrValue)) { 645 attrValueString = 646 XMLUtils.getElementValue(attrValue); 647 if (attrValueList == null) { 648 attrValueList = new ArrayList(); 649 } 650 attrValueList.add(attrValueString); 651 } 652 } 653 if (attrValueList != null) { 654 if (debug.messageEnabled()) { 655 debug.message( 656 "SAMLUtils.addEnvParamsFromAssertion:" + 657 " attrName = " + attrName + 658 " attrValue = " + attrValueList); 659 } 660 String[] attrValueStrs = (String[])attrValueList. 661 toArray(new String[attrValueList.size()]); 662 try { 663 envParameters.put(attrName, attrValueStrs); 664 } catch (Exception ex) { 665 if (debug.messageEnabled()) { 666 debug.message( 667 "SAMLUtils.addEnvParamsFromAssertion:", 668 ex); 669 } 670 } 671 } else if (debug.messageEnabled()) { 672 if (debug.messageEnabled()) { 673 debug.message( 674 "SAMLUtils.addEnvParamsFromAssertion:" + 675 " attrName = " + attrName + 676 " has no value"); 677 } 678 } 679 } 680 } // if it's an attribute statement 681 } 682 } 683 } 684 685 /** 686 * Returns maximum content length of a SAML request. 687 * @return maximum content length of a SAML request. 688 */ 689 public static int getMaxContentLength() { 690 return maxContentLength; 691 } 692 693 // ************************************************************************ 694 // Methods used by SAML Servlets 695 // ************************************************************************ 696 697 /** 698 * Checks content length of a http request to avoid dos attack. 699 * In case SAML inter-op with other SAML vendor who may not provide content 700 * length in HttpServletRequest. We decide to support no length restriction 701 * for Http communication. Here, we use a special value (e.g. 0) to 702 * indicate that no enforcement is required. 703 * @param request <code>HttpServletRequest</code> instance to be checked. 704 * @exception ServletException if context length of the request exceeds 705 * maximum content length allowed. 706 */ 707 public static void checkHTTPContentLength(HttpServletRequest request) 708 throws ServletException { 709 if (maxContentLength != 0) { 710 int length = request.getContentLength(); 711 if (SAMLUtils.debug.messageEnabled()) { 712 SAMLUtils.debug.message("HttpRequest content length= " +length); 713 } 714 if (length > maxContentLength) { 715 if (SAMLUtils.debug.messageEnabled()) { 716 SAMLUtils.debug.message( 717 "content length too large" + length); 718 } 719 throw new ServletException( 720 SAMLUtils.bundle.getString("largeContentLength")); 721 } 722 } 723 } 724 725 /** 726 * Post assertions and attributes to the target url. 727 * This method opens a URL connection to the target specified and POSTs 728 * assertions to it using the passed HttpServletResponse object. It POSTs 729 * multiple parameter names "assertion" with value being each of the 730 * <code>Assertion</code> in the passed Set. 731 * @param response <code>HttpServletResponse</code> object 732 * @param assertion List of <code>Assertion</code>s to be posted. 733 * @param targeturl target url 734 * @param attrMap Map of attributes to be posted to the target 735 */ 736 public static void postToTarget(HttpServletResponse response, 737 List assertion, String targeturl, Map attrMap) throws IOException { 738 PrintWriter out = response.getWriter(); 739 out.println("<HTML>"); 740 out.println("<HEAD>\n"); 741 out.println("<TITLE>Access rights validated</TITLE>\n"); 742 out.println("</HEAD>\n"); 743 out.println("<BODY Onload=\"document.forms[0].submit()\">"); 744 Iterator it = null; 745 if (SAMLUtils.debug.messageEnabled()) { 746 out.println("<H1>Access rights validated</H1>\n"); 747 out.println("<meta http-equiv=\"refresh\" content=\"20\">\n"); 748 out.println("<P>We have verified your access rights <STRONG>" + 749 "</STRONG> according to the assertion shown " 750 +"below. \n"); 751 out.println("You are being redirected to the resource.\n"); 752 out.println("Please wait ......\n"); 753 out.println("</P>\n"); 754 out.println("<HR><P>\n"); 755 if (assertion != null) { 756 it = assertion.iterator(); 757 while (it.hasNext()) { 758 out.println(SAMLUtils.displayXML((String)it.next())); 759 } 760 } 761 out.println("</P>\n"); 762 } 763 out.println("<FORM METHOD=\"POST\" ACTION=\"" + targeturl + "\">"); 764 if (assertion != null) { 765 it = assertion.iterator(); 766 while (it.hasNext()) { 767 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ 768 SAMLConstants.POST_ASSERTION_NAME + "\""); 769 out.println("VALUE=\"" + 770 URLEncDec.encode((String)it.next()) + "\">"); 771 } 772 } 773 if (attrMap != null && !attrMap.isEmpty()) { 774 StringBuffer attrNamesSB = new StringBuffer(); 775 Set entrySet = attrMap.entrySet(); 776 for(Iterator iter = entrySet.iterator(); iter.hasNext();) { 777 Map.Entry entry = (Map.Entry)iter.next(); 778 String attrName = HTMLEncode((String)entry.getKey(), '\"'); 779 String attrValue = HTMLEncode((String)entry.getValue(), '\"'); 780 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ attrName + 781 "\" VALUE=\"" + attrValue + "\">"); 782 if (attrNamesSB.length() > 0) { 783 attrNamesSB.append(":"); 784 } 785 attrNamesSB.append(attrName); 786 } 787 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ 788 SAMLConstants.POST_ATTR_NAMES + "\" VALUE=\"" + 789 attrNamesSB + "\">"); 790 } 791 out.println("</FORM>"); 792 out.println("</BODY></HTML>"); 793 out.close(); 794 } 795 796 /** 797 * Returns true of false based on whether the target passed as parameter 798 * accepts form POST. 799 * @param targetIn url to be checked 800 * @return true if it should post assertion to the target passed in; false 801 * otherwise. 802 */ 803 public static boolean postYN(String targetIn) { 804 SAMLUtils.debug.message("Inside postYN()"); 805 if ((targetIn == null) || (targetIn.length() == 0)) { 806 return false; 807 } 808 Set targets = (Set) SAMLServiceManager. 809 getAttribute(SAMLConstants.POST_TO_TARGET_URLS); 810 if ((targets == null) || (targets.size() == 0)) { 811 return false; 812 } 813 URL targetUrl = null; 814 try { 815 targetUrl = new URL(targetIn); 816 } catch (MalformedURLException me ) { 817 SAMLUtils.debug.error("SAMLUtils:postYN(): Malformed URL passed"); 818 return false; 819 } 820 String targetInHost = targetUrl.getHost(); 821 int targetInPort = targetUrl.getPort(); 822 String targetInPath = targetUrl.getPath(); 823 // making target string without protocol 824 String targetToCompare = new StringBuffer(targetInHost.toLowerCase()) 825 .append(":").append(String.valueOf(targetInPort)) 826 .append("/").append(targetInPath).toString(); 827 if (targets.contains(targetToCompare)) { 828 return true; 829 } else { 830 return false; 831 } 832 } 833 834 /** 835 * Replaces every occurence of ch with 836 * "&#<ascii code of ch>;" 837 * @param srcStr orginal string to to be encoded. 838 * @param ch the charactor needs to be encoded. 839 * @return encoded string 840 */ 841 public static String HTMLEncode(String srcStr, char ch) { 842 if (srcStr == null) { 843 return null; 844 } 845 846 int fromIndex = 0; 847 int toIndex; 848 StringBuffer dstSB = new StringBuffer(); 849 850 while((toIndex = srcStr.indexOf(ch, fromIndex)) != -1) { 851 dstSB.append(srcStr.substring(fromIndex, toIndex)) 852 .append("&#" + (int)ch + ";"); 853 fromIndex = toIndex + 1; 854 } 855 dstSB.append(srcStr.substring(fromIndex)); 856 857 return dstSB.toString(); 858 } 859 860 /** 861 * Displays an XML string. 862 * This is a utility function used to hack up an HTML display of an XML 863 * string. 864 * @param input original string 865 * @return encoded string so it can be displayed properly by html. 866 */ 867 public static String displayXML(String input) { 868 debug.message("In displayXML "); 869 StringCharacterIterator iter = new StringCharacterIterator(input); 870 StringBuffer buf = new StringBuffer(); 871 872 for(char c = iter.first();c != CharacterIterator.DONE;c = iter.next()) { 873 if (c=='>') { 874 buf.append(">"); 875 } else if (c=='<') { 876 buf.append("<"); 877 } else if (c=='\n'){ 878 buf.append("<BR>\n"); 879 } else { 880 buf.append(c); 881 } 882 } 883 return buf.toString(); 884 } 885 886 /** 887 * Gets the list of <code>Assertion</code> objects from a list of 888 * 'String' assertions. 889 * @param assertions List of assertions in string format 890 * @return List of <code>Assertion</code> objects 891 */ 892 public static List getListOfAssertions(List assertions) { 893 List returnAssertions = new ArrayList(); 894 try { 895 if (assertions != null) { 896 Iterator it = assertions.iterator(); 897 while (it.hasNext()) { 898 Document doc = XMLUtils.toDOMDocument((String)it.next(), 899 debug); 900 Element root = doc.getDocumentElement(); 901 if (root != null) { 902 Assertion assertion = new Assertion(root); 903 returnAssertions.add(assertion); 904 } 905 } 906 } 907 } catch (Exception e) { 908 if (debug.messageEnabled()) { 909 debug.message("SAMLUtils.getListOfAssertions : " + 910 "Exception : ", e); 911 } 912 } 913 return returnAssertions; 914 } 915 916 917 // ************************************************************************ 918 // Methods used / shared by SAML Authentication Module and SAML Servlets 919 // ************************************************************************ 920 921 /** 922 * Returns byte array from a SAML <code>Response</code>. 923 * @param samlResponse <code>Response</code> object 924 * @return byte array 925 * @exception SAMLException if error occurrs during the process. 926 */ 927 public static byte[] getResponseBytes(Response samlResponse) 928 throws SAMLException 929 { 930 byte ret[] = null; 931 try { 932 ret = samlResponse.toString(true, true, true). 933 getBytes(SAMLConstants.DEFAULT_ENCODING); 934 } catch (UnsupportedEncodingException ue) { 935 if (debug.messageEnabled()) { 936 debug.message("getResponseBytes : " , ue); 937 } 938 throw new SAMLException(ue.getMessage()); 939 } 940 return ret; 941 } 942 943 /** 944 * Returns <code>Response</code> object from byte array. 945 * @param bytes byte array 946 * @return <code>Response</code> object 947 */ 948 public static Response getResponse(byte [] bytes) { 949 Response temp = null; 950 if (bytes == null) { 951 return null; 952 } 953 try { 954 temp = Response.parseXML(new ByteArrayInputStream(bytes)); 955 } catch (SAMLException se) { 956 debug.error("getResponse : " , se); 957 } 958 return temp; 959 } 960 961 /** 962 * Verifies a <code>Response</code>. 963 * @param response SAML <code>Response</code> object 964 * @param requestUrl this server's POST profile URL 965 * @param request <code>HttpServletRequest</code> object 966 * @return true if the response is valid; false otherwise. 967 */ 968 public static boolean verifyResponse(Response response, 969 String requestUrl, HttpServletRequest request) { 970 if (!response.isSignatureValid()) { 971 debug.message("verifyResponse: Response's signature is invalid."); 972 return false; 973 } 974 975 // check Recipient == this server's POST profile URL(requestURL) 976 String recipient = response.getRecipient(); 977 if ((recipient == null) || (recipient.length() == 0) || 978 ((!equalURL(recipient, requestUrl)) && 979 (!equalURL(recipient,getLBURL(requestUrl, request))))) { 980 debug.error("verifyResponse : Incorrect Recipient."); 981 return false; 982 } 983 984 // check status of the Response 985 if (!response.getStatus().getStatusCode().getValue().endsWith( 986 SAMLConstants.STATUS_CODE_SUCCESS_NO_PREFIX)) { 987 debug.error("verifyResponse : Incorrect StatusCode value."); 988 return false; 989 } 990 991 return true; 992 } 993 994 private static String getLBURL(String requestUrl, 995 HttpServletRequest request) 996 { 997 String host = request.getHeader("host"); 998 if (host == null) { 999 return requestUrl; 1000 } 1001 int index = requestUrl.indexOf("//"); 1002 if (index == -1) { 1003 return requestUrl; 1004 } 1005 StringBuffer sb = new StringBuffer(200); 1006 sb.append(requestUrl.substring(0, index + 2)).append(host); 1007 String rest = requestUrl.substring(index +2, requestUrl.length()); 1008 if ((index = rest.indexOf("/")) != -1) { 1009 sb.append(rest.substring(index, rest.length())); 1010 } 1011 if (debug.messageEnabled()) { 1012 debug.message("getLBURL: LBURL = " + sb.toString()); 1013 } 1014 return sb.toString().trim(); 1015 } 1016 1017 // ************************************************************************ 1018 // Methods used by SAML Authentication Module 1019 // ************************************************************************ 1020 1021 /** 1022 * Gets List of assertions in String format from a list of 1023 * <code>Assertion</code> objects. 1024 * @param assertions List of <code>Assertion</code> objects. 1025 * @return List of assertions in String format 1026 */ 1027 public static List getStrAssertions(List assertions) { 1028 List returnAssertions = new ArrayList(); 1029 if (assertions != null) { 1030 Iterator it = assertions.iterator(); 1031 while (it.hasNext()) { 1032 returnAssertions.add( 1033 ((Assertion)(it.next())).toString(true,true)); 1034 } 1035 } 1036 return returnAssertions; 1037 } 1038 1039 /** 1040 * Verifies Signature for Post response. 1041 * @param samlResponse <code>Response</code> object from post profile. 1042 * @return true if the signature on the reponse is valid; false otherwise. 1043 */ 1044 public static boolean verifySignature(Response samlResponse) { 1045 if ((samlResponse != null) && 1046 (!samlResponse.isSigned() || (!samlResponse.isSignatureValid()))) { 1047 return false; 1048 } 1049 return true; 1050 } 1051 1052 /** 1053 * Gets Attribute Map to be set in the Session. 1054 * @param partnerdest <code>SOAPEntry</code> object 1055 * @param assertions List of <code>Assertion</code>s 1056 * @param subject <code>Subject</code> object 1057 * @param target target of final SSO 1058 * @return Map which contains name and attributes. 1059 * @exception Exception if an error occurrs. 1060 */ 1061 public static Map getAttributeMap( 1062 SAMLServiceManager.SOAPEntry partnerdest, 1063 List assertions, 1064 com.sun.identity.saml.assertion.Subject subject, 1065 String target) 1066 throws Exception { 1067 String srcID = partnerdest.getSourceID(); 1068 String name = null; 1069 String org = null; 1070 Map attrMap = new HashMap(); 1071 PartnerAccountMapper paMapper = partnerdest.getPartnerAccountMapper(); 1072 1073 if (paMapper != null) { 1074 Map map = paMapper.getUser(assertions, srcID, target); 1075 name = (String) map.get(PartnerAccountMapper.NAME); 1076 org = (String) map.get(PartnerAccountMapper.ORG); 1077 attrMap = (Map) map.get(PartnerAccountMapper.ATTRIBUTE); 1078 } 1079 1080 if (attrMap == null) { 1081 attrMap = new HashMap(); 1082 } 1083 attrMap.put(SAMLConstants.USER_NAME, name); 1084 if ((org != null) && (org.length() != 0)) { 1085 attrMap.put(SessionProvider.REALM, org); 1086 } else { 1087 attrMap.put(SessionProvider.REALM, "/"); 1088 } 1089 1090 if (debug.messageEnabled()) { 1091 debug.message("getAttributeMap : " + "name = " + 1092 name + ", realm=" + org + ", attrMap = " + attrMap); 1093 } 1094 1095 return attrMap; 1096 } 1097 1098 /** 1099 * Checks response and get back a Map of relevant data including, 1100 * Subject, SOAPEntry for the partner and the List of Assertions. 1101 * @param response <code>Response</code> object 1102 * @return Map of data including Subject, SOAPEntry, and list of assertions. 1103 */ 1104 public static Map verifyAssertionAndGetSSMap(Response response) { 1105 // loop to check assertions 1106 com.sun.identity.saml.assertion.Subject subject = null; 1107 SAMLServiceManager.SOAPEntry srcSite = null; 1108 List assertions = response.getAssertion(); 1109 Iterator iter = assertions.iterator(); 1110 Assertion assertion = null; 1111 String aIDString = null; 1112 String issuer = null; 1113 Iterator stmtIter = null; 1114 Statement statement = null; 1115 int stmtType = Statement.NOT_SUPPORTED; 1116 com.sun.identity.saml.assertion.Subject sub = null; 1117 SubjectConfirmation subConf = null; 1118 Set confMethods = null; 1119 String confMethod = null; 1120 Date date = null; 1121 while (iter.hasNext()) { 1122 assertion = (Assertion) iter.next(); 1123 aIDString = assertion.getAssertionID(); 1124 // make sure it's not being used 1125 if (idTimeMap.containsKey(aIDString)) { 1126 debug.error("verifyAssertion " 1127 + "AndGetSSMap: Assertion: " + aIDString + " is used."); 1128 return null; 1129 } 1130 1131 // check issuer of the assertions 1132 issuer = assertion.getIssuer(); 1133 if ((srcSite = SAMLUtils.getSourceSite(issuer)) == null) { 1134 debug.error("verifyAsserti " 1135 + "onAndGetSSMap: issuer is not on the Partner list."); 1136 return null; 1137 } 1138 1139 if (!assertion.isSignatureValid()) { 1140 debug.error("verifyAssertion " 1141 + "AndGetSSMap: assertion's signature is not valid."); 1142 return null; 1143 } 1144 1145 // must be valid (timewise) 1146 if (!assertion.isTimeValid()) { 1147 debug.error("verifyAssertion " 1148 + "AndGetSSMap: assertion's time is not valid."); 1149 return null; 1150 } 1151 1152 // TODO: IssuerInstant of the assertion is within a few minutes 1153 // This is a MAY in spec. Which number to use for the few minutes? 1154 1155 // TODO: check AudienceRestrictionCondition 1156 1157 //for each assertion, loop to check each statement 1158 stmtIter = assertion.getStatement().iterator(); 1159 while (stmtIter.hasNext()) { 1160 statement = (Statement) stmtIter.next(); 1161 stmtType = statement.getStatementType(); 1162 if ((stmtType == Statement.AUTHENTICATION_STATEMENT) || 1163 (stmtType == Statement.ATTRIBUTE_STATEMENT) || 1164 (stmtType == Statement.AUTHORIZATION_DECISION_STATEMENT)) { 1165 sub = ((SubjectStatement)statement).getSubject(); 1166 1167 // ConfirmationMethod of each subject must be set to bearer 1168 if (((subConf = sub.getSubjectConfirmation()) == null) || 1169 ((confMethods = subConf.getConfirmationMethod()) 1170 == null) || 1171 (confMethods.size() != 1)) { 1172 debug.error("verify " 1173 + "AssertionAndGetSSMap: missing or extra " 1174 + "ConfirmationMethod."); 1175 return null; 1176 } 1177 if (((confMethod = (String) confMethods.iterator().next()) 1178 == null) || 1179 (!confMethod.equals( 1180 SAMLConstants.CONFIRMATION_METHOD_BEARER))) { 1181 debug.error("verify " 1182 + "AssertionAndGetSSMap:wrong ConfirmationMethod."); 1183 return null; 1184 } 1185 1186 //TODO: must contain same Subject for all statements? 1187 1188 if (stmtType == Statement.AUTHENTICATION_STATEMENT) { 1189 //TODO: if it has SubjectLocality,its IP must == sender 1190 // browser IP. This is a MAY item in the spec. 1191 if (subject == null) { 1192 subject = sub; 1193 } 1194 } 1195 } 1196 } 1197 1198 // add the assertion to idTimeMap 1199 if (debug.messageEnabled()) { 1200 debug.message("Adding " + aIDString + " to idTimeMap."); 1201 } 1202 Conditions conds = assertion.getConditions(); 1203 if ((conds != null) && ((date = conds.getNotOnorAfter()) != null)) { 1204 cGoThrough.addElement(aIDString); 1205 idTimeMap.put(aIDString, new Long(date.getTime())); 1206 } else { 1207 cPeriodic.addElement(aIDString); 1208 // it doesn't matter what we store for the value. 1209 idTimeMap.put(aIDString, aIDString); 1210 } 1211 } 1212 1213 // must have at least one SSO assertion 1214 if ((subject == null) || (srcSite == null)) { 1215 debug.error("verifyAssertion AndGetSSMap: couldn't find Subject."); 1216 return null; 1217 } 1218 Map ssMap = new HashMap(); 1219 ssMap.put(SAMLConstants.SUBJECT, subject); 1220 ssMap.put(SAMLConstants.SOURCE_SITE_SOAP_ENTRY, srcSite); 1221 ssMap.put(SAMLConstants.POST_ASSERTION, assertions); 1222 return ssMap; 1223 } 1224 1225 /** 1226 * Checks if the Assertion is time valid and 1227 * if the Assertion is allowed by AudienceRestrictionCondition. 1228 * 1229 * @param assertion an Assertion object 1230 * @return true if the operation is successful otherwise, return false 1231 * @exception IOException IOException 1232 */ 1233 private static boolean checkCondition(Assertion assertion) 1234 throws IOException 1235 { 1236 if (assertion == null) { 1237 return false; 1238 } 1239 if (!assertion.isSignatureValid()) { 1240 debug.error(bundle.getString("assertionSignatureNotValid")); 1241 return false; 1242 } 1243 // check if the Assertion is time valid 1244 if (!(assertion.isTimeValid())) { 1245 debug.error(bundle.getString("assertionTimeNotValid")); 1246 return false; 1247 } 1248 // check the Assertion is allowed by AudienceRestrictionCondition 1249 Conditions cnds = assertion.getConditions(); 1250 Set audienceCnd = new HashSet(); 1251 audienceCnd = cnds.getAudienceRestrictionCondition(); 1252 Iterator it = null; 1253 if (audienceCnd != null) { 1254 if (!audienceCnd.isEmpty()) { 1255 it = audienceCnd.iterator(); 1256 while (it.hasNext()) { 1257 if ((((AudienceRestrictionCondition) it.next()). 1258 evaluate()) == Condition.INDETERMINATE ) { 1259 if (debug.messageEnabled()) { 1260 debug.message("Audience " + 1261 "RestrictionConditions is indeterminate."); 1262 } 1263 } else { 1264 debug.error("Failed AudienceRestrictionCondition"); 1265 return false; 1266 } 1267 } 1268 } 1269 } 1270 return true; 1271 } 1272 1273 /** 1274 * Determines if there is a valid SSO Assertion 1275 * inside of SAML Response. 1276 * 1277 * @param assertions a List of <code>Assertion</code> objects 1278 * @return a Subject object 1279 * @exception IOException IOException 1280 */ 1281 public static com.sun.identity.saml.assertion.Subject examAssertions( 1282 List assertions) throws IOException { 1283 if (assertions == null) { 1284 return null; 1285 } 1286 boolean validation = false; 1287 com.sun.identity.saml.assertion.Subject subject = null; 1288 Iterator iter = assertions.iterator(); 1289 1290 while (iter.hasNext()) { 1291 Assertion assertion = (Assertion)iter.next(); 1292 1293 if (!checkCondition(assertion)) { 1294 return null; 1295 } 1296 debug.message("Passed checking Conditions!"); 1297 1298 // exam the Statement inside the Assertion 1299 Set statements = new HashSet(); 1300 statements = assertion.getStatement(); 1301 1302 if (statements == null || statements.isEmpty()) { 1303 debug.error(bundle.getString("noStatement")); 1304 return null; 1305 } 1306 Iterator iterator = statements.iterator(); 1307 while (iterator.hasNext()) { 1308 Statement statement = (Statement) iterator.next(); 1309 subject = ((SubjectStatement)statement).getSubject(); 1310 SubjectConfirmation sc = subject.getSubjectConfirmation(); 1311 Set cm = new HashSet(); 1312 cm = sc.getConfirmationMethod(); 1313 if (cm == null || cm.isEmpty()) { 1314 debug.error("Subject confirmation method is null"); 1315 return null; 1316 } 1317 String conMethod = (String) cm.iterator().next(); 1318 // add checking artifact confirmation method identifier based 1319 // on Assertion version number 1320 if ((conMethod != null) && (assertion.getMajorVersion() == 1321 SAMLConstants.ASSERTION_MAJOR_VERSION) && 1322 (((assertion.getMinorVersion() == 1323 SAMLConstants.ASSERTION_MINOR_VERSION_ONE) && 1324 conMethod.equals(SAMLConstants.CONFIRMATION_METHOD_ARTIFACT)) 1325 || 1326 ((assertion.getMinorVersion() == 1327 SAMLConstants.ASSERTION_MINOR_VERSION_ZERO) && 1328 (conMethod.equals( 1329 SAMLConstants.DEPRECATED_CONFIRMATION_METHOD_ARTIFACT))))) { 1330 if (debug.messageEnabled()) { 1331 debug.message("Correct Confirmation method"); 1332 } 1333 } else { 1334 debug.error("Wrong Confirmation Method."); 1335 return null; 1336 } 1337 if (statement instanceof AuthenticationStatement) { 1338 //found an SSO Assertion 1339 validation = true; 1340 } 1341 } // end of while (iterator.hasNext()) for Statements 1342 } // end of while (iter.hasNext()) for Assertions 1343 1344 if (!validation) { 1345 debug.error(bundle.getString("noSSOAssertion")); 1346 return null; 1347 } 1348 return subject; 1349 } 1350 1351 /** 1352 * Return whether the signature on the object is valid or not. 1353 * @param xmlString input XML String 1354 * @param idAttribute ASSERTION_ID_ATTRIBUTE or RESPONSE_ID_ATTRIBUTE 1355 * @param issuer the issuer of the Assertion 1356 * @return true if the signature on the object is valid; false otherwise. 1357 */ 1358 public static boolean checkSignatureValid(String xmlString, 1359 String idAttribute, 1360 String issuer) 1361 { 1362 String certAlias = null; 1363 boolean valid = true; 1364 Map entries = (Map) SAMLServiceManager.getAttribute( 1365 SAMLConstants.PARTNER_URLS); 1366 if (entries != null) { 1367 SAMLServiceManager.SOAPEntry srcSite = 1368 (SAMLServiceManager.SOAPEntry) entries.get(issuer); 1369 if (srcSite != null) { 1370 certAlias = srcSite.getCertAlias(); 1371 } 1372 } 1373 1374 try { 1375 XMLSignatureManager manager = XMLSignatureManager.getInstance(); 1376 valid = manager.verifyXMLSignature(xmlString, 1377 idAttribute, certAlias); 1378 } catch (Exception e) { 1379 SAMLUtils.debug.warning("SAMLUtils.checkSignatureValid:"+ 1380 " signature validation exception", e); 1381 valid = false; 1382 } 1383 if (!valid) { 1384 if (SAMLUtils.debug.messageEnabled()) { 1385 SAMLUtils.debug.message("SAMLUtils.checkSignatureValid:"+ 1386 " Couldn't verify signature."); 1387 } 1388 } 1389 return valid; 1390 } 1391 1392 /** 1393 * Sets the given <code>HttpServletResponse</code> object with the 1394 * headers in the given <code>MimeHeaders</code> object. 1395 * @param headers the <code>MimeHeaders</code> object 1396 * @param response the <code>HttpServletResponse</code> object to which the 1397 * headers are to be written. 1398 */ 1399 public static void setMimeHeaders( 1400 MimeHeaders headers, HttpServletResponse response) { 1401 1402 if(headers == null || response == null) { 1403 debug.message("SAMLUtils.setMimeHeaders : null input"); 1404 return; 1405 } 1406 1407 for (Iterator iter = headers.getAllHeaders(); iter.hasNext();){ 1408 MimeHeader header = (MimeHeader)iter.next(); 1409 1410 String[] values = headers.getHeader(header.getName()); 1411 if (values.length == 1) { 1412 response.setHeader(header.getName(), header.getValue()); 1413 } else { 1414 StringBuffer concat = new StringBuffer(); 1415 int i = 0; 1416 while (i < values.length) { 1417 if (i != 0) { 1418 concat.append(','); 1419 } 1420 concat.append(values[i++]); 1421 } 1422 response.setHeader(header.getName(),concat.toString()); 1423 } 1424 1425 } 1426 return; 1427 } 1428 1429 /** 1430 * Returns a <code>MimeHeaders</code> object that contains the headers 1431 * in the given <code>HttpServletRequest</code> object. 1432 * 1433 * @param req the <code>HttpServletRequest</code> object. 1434 * @return a new <code>MimeHeaders</code> object containing the headers. 1435 */ 1436 public static MimeHeaders getMimeHeaders(HttpServletRequest req) { 1437 1438 MimeHeaders headers = new MimeHeaders(); 1439 1440 if(req == null) { 1441 debug.message("SAMLUtils.getMimeHeaders: null input"); 1442 return headers; 1443 } 1444 1445 Enumeration enumerator = req.getHeaderNames(); 1446 1447 while(enumerator.hasMoreElements()) { 1448 String headerName = (String)enumerator.nextElement(); 1449 String headerValue = req.getHeader(headerName); 1450 1451 StringTokenizer values = new StringTokenizer(headerValue, ","); 1452 while(values.hasMoreTokens()) { 1453 headers.addHeader(headerName, values.nextToken().trim()); 1454 } 1455 } 1456 1457 return headers; 1458 } 1459 1460 /** 1461 * Returns the authenticaion login url with goto parameter 1462 * in the given <code>HttpServletRequest</code> object. 1463 * 1464 * @param req the <code>HttpServletRequest</code> object. 1465 * @return a new authenticaion login url with goto parameter. 1466 */ 1467 public static String getLoginRedirectURL(HttpServletRequest req) { 1468 String qs = req.getQueryString(); 1469 String gotoUrl = req.getRequestURL().toString(); 1470 String key = null; 1471 if (qs != null && qs.length() > 0) { 1472 gotoUrl = gotoUrl + "?" + qs; 1473 int startIdx = -1; 1474 int endIdx = -1; 1475 StringBuffer result = new StringBuffer(); 1476 if ((startIdx = qs.indexOf((String) SAMLServiceManager. 1477 getAttribute(SAMLConstants.TARGET_SPECIFIER))) > 0) { 1478 result.append(qs.substring(0, startIdx - 1)); 1479 } 1480 if ((endIdx = qs.indexOf("&", startIdx)) != -1) { 1481 if (startIdx == 0) { 1482 result.append(qs.substring(endIdx + 1)); 1483 } else { 1484 result.append(qs.substring(endIdx)); 1485 } 1486 } 1487 key = result.toString(); 1488 } 1489 1490 String reqUrl = req.getScheme() + "://" + req.getServerName() + ":" + 1491 req.getServerPort() + req.getContextPath(); 1492 String redirectUrl = null; 1493 if (key == null || key.equals("")) { 1494 redirectUrl = reqUrl +"/UI/Login?goto=" + 1495 URLEncDec.encode(gotoUrl); 1496 } else { 1497 redirectUrl = reqUrl +"/UI/Login?" + key + "&goto="+ 1498 URLEncDec.encode(gotoUrl); 1499 } 1500 if (SAMLUtils.debug.messageEnabled()) { 1501 SAMLUtils.debug.message("Redirect to auth login via:" + 1502 redirectUrl); 1503 } 1504 return redirectUrl; 1505 } 1506 1507 1508 /** 1509 * Processes SAML Artifact 1510 * @param artifact SAML Artifact 1511 * @param target Target URL 1512 * @return Attribute Map 1513 * @exception SAMLException if failed to get the Assertions or 1514 * Attribute Map. 1515 */ 1516 public static Map processArtifact(String[] artifact, String target) 1517 throws SAMLException { 1518 List assts = null; 1519 Subject assertionSubject = null; 1520 AssertionArtifact firstArtifact = null; 1521 Map sessMap = null; 1522 // Call SAMLClient to do the Single-sign-on 1523 try { 1524 assts = SAMLClient.artifactQueryHandler(artifact, (String) null); 1525 //exam the SAML response 1526 if ((assertionSubject = examAssertions(assts)) == null) { 1527 return null; 1528 } 1529 firstArtifact = new AssertionArtifact(artifact[0]); 1530 String sid = firstArtifact.getSourceID(); 1531 Map partner = (Map) SAMLServiceManager.getAttribute( 1532 SAMLConstants.PARTNER_URLS); 1533 if (partner == null) { 1534 throw new SAMLException(bundle.getString 1535 ("nullPartnerUrl")); 1536 } 1537 SAMLServiceManager.SOAPEntry partnerdest = 1538 (SAMLServiceManager.SOAPEntry) partner.get(sid); 1539 if (partnerdest == null) { 1540 throw new SAMLException(bundle.getString 1541 ("failedAccountMapping")); 1542 } 1543 sessMap = getAttributeMap(partnerdest, assts, 1544 assertionSubject, target); 1545 } catch (Exception se) { 1546 debug.error("SAMLUtils.processArtifact :" , se); 1547 throw new SAMLException( 1548 bundle.getString("failProcessArtifact")); 1549 } 1550 return sessMap; 1551 } 1552 1553 /** 1554 * Creates Session 1555 * @param request HttpServletRequest 1556 * @param response HttpServletResponse 1557 * @param attrMap Attribute Map 1558 * @exception if failed to create Session 1559 */ 1560 public static Object generateSession(HttpServletRequest request, 1561 HttpServletResponse response, 1562 Map attrMap) throws SAMLException { 1563 Map sessionInfoMap = new HashMap(); 1564 String realm = (String) attrMap.get(SessionProvider.REALM); 1565 if ((realm == null) || (realm.length() == 0)) { 1566 realm = "/"; 1567 } 1568 sessionInfoMap.put(SessionProvider.REALM, realm); 1569 String principalName = 1570 (String) attrMap.get(SessionProvider.PRINCIPAL_NAME); 1571 if (principalName == null) { 1572 principalName = (String) attrMap.get(SAMLConstants.USER_NAME); 1573 } 1574 sessionInfoMap.put(SessionProvider.PRINCIPAL_NAME, principalName); 1575 //TODO: sessionInfoMap.put(SessionProvider.AUTH_LEVEL, "0"); 1576 Object session = null; 1577 try { 1578 SessionProvider sessionProvider = SessionManager.getProvider(); 1579 session = sessionProvider.createSession( 1580 sessionInfoMap, request, response, null); 1581 setAttrMapInSession(sessionProvider, attrMap, session); 1582 } catch (SessionException se) { 1583 if (debug.messageEnabled()) { 1584 debug.message("SAMLUtils.generateSession:", se); 1585 } 1586 throw new SAMLException(se); 1587 } 1588 return session; 1589 } 1590 1591 /** 1592 * Processes SAML Response 1593 * @param samlResponse SAML Response object 1594 * @param target Target URL 1595 * @return Attribute Map 1596 * @exception SAMLException if failed to get Attribute Map. 1597 */ 1598 public static Map processResponse(Response samlResponse, String target) 1599 throws SAMLException { 1600 List assertions = null; 1601 SAMLServiceManager.SOAPEntry partnerdest = null; 1602 Subject assertionSubject = null; 1603 if (samlResponse.isSigned()) { 1604 // verify the signature 1605 boolean isSignedandValid = verifySignature(samlResponse); 1606 if (!isSignedandValid) { 1607 throw new SAMLException(bundle.getString("invalidResponse")); 1608 } 1609 } 1610 // check Assertion and get back a Map of relevant data including, 1611 // Subject, SOAPEntry for the partner and the List of Assertions. 1612 Map ssMap = verifyAssertionAndGetSSMap(samlResponse); 1613 if (debug.messageEnabled()) { 1614 debug.message("processResponse: ssMap = " + ssMap); 1615 } 1616 1617 if (ssMap == null) { 1618 throw new SAMLException(bundle.getString("invalidAssertion")); 1619 } 1620 assertionSubject = (com.sun.identity.saml.assertion.Subject) 1621 ssMap.get(SAMLConstants.SUBJECT); 1622 if (assertionSubject == null) { 1623 throw new SAMLException(bundle.getString("nullSubject")); 1624 } 1625 1626 partnerdest = (SAMLServiceManager.SOAPEntry)ssMap 1627 .get(SAMLConstants.SOURCE_SITE_SOAP_ENTRY); 1628 if (partnerdest == null) { 1629 throw new SAMLException(bundle.getString("failedAccountMapping")); 1630 } 1631 1632 assertions = (List)ssMap.get(SAMLConstants.POST_ASSERTION); 1633 Map sessMap = null; 1634 try { 1635 sessMap = getAttributeMap(partnerdest, assertions, 1636 assertionSubject, target); 1637 } catch (Exception se) { 1638 debug.error("SAMLUtils.processResponse :" , se); 1639 throw new SAMLException( 1640 bundle.getString("failProcessResponse")); 1641 } 1642 return sessMap; 1643 } 1644 1645 /** 1646 *Sets the attribute map in the session 1647 * 1648 *@param attrMap, the Attribute Map 1649 *@param session, the valid session object 1650 *@exception SessionException if failed to set Attribute in the 1651 * Session. 1652 */ 1653 private static void setAttrMapInSession( 1654 SessionProvider sessionProvider, 1655 Map attrMap, Object session) 1656 throws SessionException { 1657 if (attrMap != null && !attrMap.isEmpty()) { 1658 Set entrySet = attrMap.entrySet(); 1659 for(Iterator iter = entrySet.iterator(); iter.hasNext();) { 1660 Map.Entry entry = (Map.Entry)iter.next(); 1661 String attrName = (String)entry.getKey(); 1662 String[] attrValues = null; 1663 if (attrName.equals(SAMLConstants.USER_NAME) || 1664 attrName.equals(SessionProvider.PRINCIPAL_NAME)) { 1665 String attrValue = (String)entry.getValue(); 1666 attrValues = new String[1]; 1667 attrValues[0] = attrValue; 1668 } else if (attrName.equals(SessionProvider.REALM) || 1669 attrName.equals(SessionProvider.AUTH_LEVEL)) { 1670 // ignore 1671 continue; 1672 } else { 1673 attrValues = (String[])entry.getValue(); 1674 } 1675 sessionProvider.setProperty(session, attrName, attrValues); 1676 if (debug.messageEnabled()) { 1677 debug.message("SAMLUtils.setAttrMapInSessioin: attrName ="+ 1678 attrName); 1679 } 1680 } 1681 } 1682 } 1683 1684 /** 1685 * Compares two URLs to see if they are equal. Two URLs are equal if 1686 * they have same protocol, host, port and path (case ignored). 1687 * Note : the method is provided to avoid URL.equals() call which requires 1688 * name lookup. Name lookup is a blocking operation and very expensive 1689 * if the hostname could not be resolved. 1690 * 1691 * @return true if the URLs are equal, false otherwise. 1692 */ 1693 private static boolean equalURL(String url1, String url2) { 1694 try { 1695 URL u1 = new URL(url1); 1696 URL u2 = new URL(url2); 1697 int port1 = u1.getPort(); 1698 if (port1 == -1) { 1699 port1 = u1.getDefaultPort(); 1700 } 1701 int port2 = u2.getPort(); 1702 if (port2 == -1) { 1703 port2 = u2.getDefaultPort(); 1704 } 1705 if ((u1.getProtocol().equalsIgnoreCase(u2.getProtocol())) && 1706 (u1.getHost().equalsIgnoreCase(u2.getHost())) && 1707 (port1 == port2) && 1708 (u1.getPath().equalsIgnoreCase(u2.getPath()))) { 1709 return true; 1710 } else { 1711 return false; 1712 } 1713 } catch (MalformedURLException m) { 1714 debug.message("Error in SAMLUtils.equalURL", m); 1715 return false; 1716 } 1717 } 1718 1719 /** 1720 * Gets input Node Canonicalized 1721 * 1722 * @param node Node 1723 * @return Canonical element if the operation succeeded. 1724 * Otherwise, return null. 1725 */ 1726 public static Element getCanonicalElement(Node node) { 1727 try { 1728 Canonicalizer c14n = Canonicalizer.getInstance( 1729 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); 1730 byte outputBytes[] = c14n.canonicalizeSubtree(node); 1731 DocumentBuilder documentBuilder = 1732 XMLUtils.getSafeDocumentBuilder(false); 1733 Document doc = documentBuilder.parse( 1734 new ByteArrayInputStream(outputBytes)); 1735 Element result = doc.getDocumentElement(); 1736 return result; 1737 } catch (Exception e) { 1738 SAMLUtils.debug.error("Response:getCanonicalElement: " + 1739 "Error while performing canonicalization on " + 1740 "the input Node."); 1741 return null; 1742 } 1743 } 1744 1745 /** 1746 * Sends to error page URL for SAML protocols. If the error page is 1747 * hosted in the same web application, forward is used with 1748 * parameters. Otherwise, redirection or HTTP POST is used with 1749 * parameters. 1750 * Three parameters are passed to the error URL: 1751 * -- errorcode : Error key, this is the I18n key of the error message. 1752 * -- httpstatuscode : Http status code for the error 1753 * -- message : detailed I18n'd error message 1754 * @param request HttpServletRequest object 1755 * @param response HttpServletResponse object 1756 * @param httpStatusCode Http Status code 1757 * @param errorCode Error code 1758 * @param errorMsg Detailed error message 1759 */ 1760 public static void sendError(HttpServletRequest request, 1761 HttpServletResponse response, int httpStatusCode, 1762 String errorCode, String errorMsg) { 1763 String errorUrl = SystemConfigurationUtil.getProperty( 1764 SAMLConstants.ERROR_PAGE_URL, 1765 SAMLConstants.DEFAULT_ERROR_PAGE_URL); 1766 if(debug.messageEnabled()) { 1767 debug.message("SAMLUtils.sendError: error page" + errorUrl); 1768 } 1769 String tmp = errorUrl.toLowerCase(); 1770 if (!tmp.startsWith("http://") && !tmp.startsWith("https://")) { 1771 // use forward 1772 String jointString = "?"; 1773 if (errorUrl.indexOf("?") != -1) { 1774 jointString = "&"; 1775 } 1776 String newUrl = errorUrl.trim() + jointString 1777 + SAMLConstants.ERROR_CODE + "=" + errorCode + "&" 1778 + SAMLConstants.HTTP_STATUS_CODE + "=" + httpStatusCode 1779 + "&" + SAMLConstants.ERROR_MESSAGE + "=" 1780 + URLEncDec.encode(errorMsg); 1781 1782 RequestDispatcher dispatcher = 1783 request.getRequestDispatcher(newUrl); 1784 try { 1785 dispatcher.forward(request, response); 1786 } catch (ServletException e) { 1787 debug.error("SAMLUtils.sendError: Exception " 1788 + "occured while trying to forward to resource:" 1789 + newUrl , e); 1790 try { 1791 response.sendError( 1792 HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 1793 e.getMessage()); 1794 } catch (IOException ioe) { 1795 debug.error("SAMLUtils.sendError IOException", ioe); 1796 } 1797 } catch (IOException e) { 1798 debug.error("SAMLUtils.sendError: Exception " 1799 + "occured while trying to forward to resource:" 1800 + newUrl , e); 1801 try { 1802 response.sendError( 1803 HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 1804 e.getMessage()); 1805 } catch (IOException ioe) { 1806 debug.error("SAMLUtils.sendError IOException", ioe); 1807 } 1808 } 1809 } else { 1810 String binding = SystemConfigurationUtil.getProperty( 1811 SAMLConstants.ERROR_PAGE_HTTP_BINDING, 1812 SAMLConstants.HTTP_POST); 1813 if(SAMLConstants.HTTP_REDIRECT.equals(binding)) { 1814 // use FSUtils, this may be redirection or forward 1815 String jointString = "?"; 1816 if (errorUrl.indexOf("?") != -1) { 1817 jointString = "&"; 1818 } 1819 String newUrl = errorUrl.trim() + jointString 1820 + SAMLConstants.ERROR_CODE + "=" + errorCode + "&" 1821 + SAMLConstants.HTTP_STATUS_CODE + "=" + httpStatusCode 1822 + "&" + SAMLConstants.ERROR_MESSAGE + "=" 1823 + URLEncDec.encode(errorMsg); 1824 1825 FSUtils.forwardRequest(request, response, newUrl) ; 1826 } else { 1827 try { 1828 response.setContentType("text/html; charset=UTF-8"); 1829 PrintWriter out = response.getWriter(); 1830 out.println("<HTML>"); 1831 out.println("<BODY Onload=\"document.forms[0].submit()\">"); 1832 out.println("<FORM METHOD=\"POST\" ACTION=\"" 1833 + errorUrl + "\">"); 1834 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\"" + 1835 SAMLConstants.ERROR_CODE + "\" "); 1836 out.println("VALUE=\"" + errorCode + "\">"); 1837 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\"" + 1838 SAMLConstants.ERROR_MESSAGE + "\" VALUE=\"" + 1839 URLEncDec.encode(errorMsg) + "\">"); 1840 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\"" + 1841 SAMLConstants.HTTP_STATUS_CODE + "\" VALUE=\"" + 1842 httpStatusCode + "\">"); 1843 out.println("<NOSCRIPT><CENTER>"); 1844 out.println("<INPUT TYPE=\"SUBMIT\" VALUE=\"" + 1845 bundle.getString("samlErrorKey") + 1846 "\"/></CENTER></NOSCRIPT>"); 1847 out.println("</FORM></BODY></HTML>"); 1848 out.close(); 1849 return; 1850 } catch (IOException ie) { 1851 debug.error("SAMLUtils.sendError IOException", ie); 1852 } 1853 } 1854 } 1855 } 1856}