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