/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * $Id: QuerySignatureUtil.java,v 1.2 2008/06/25 05:47:45 qcheng Exp $ * * Portions Copyrighted 2015 ForgeRock AS. */ package com.sun.identity.saml2.common; import java.security.GeneralSecurityException; import java.util.Set; import java.util.StringTokenizer; import java.security.PrivateKey; import java.security.Signature; import java.security.SignatureException; import java.security.InvalidKeyException; import java.security.cert.X509Certificate; import com.sun.identity.shared.configuration.SystemPropertiesManager; import com.sun.identity.shared.encode.Base64; import com.sun.identity.shared.encode.URLEncDec; import com.sun.identity.saml.common.SAMLConstants; import org.apache.xml.security.Init; import org.apache.xml.security.algorithms.JCEMapper; import org.apache.xml.security.signature.XMLSignature; import org.forgerock.openam.utils.StringUtils; import java.security.NoSuchAlgorithmException; /** * The QuerySignatureUtil provides methods to * sign query string and to verify signature on query string */ public class QuerySignatureUtil { private static final String SIGNATURE = "Signature"; static { Init.init(); } private QuerySignatureUtil() { } /** * Signs the query string. * @param queryString Query String * @param privateKey siging key * @return String signed query string * @exception SAML2Exception if the signing fails */ public static String sign(String queryString, PrivateKey privateKey) throws SAML2Exception { String classMethod = "QuerySignatureUtil.sign: "; if (queryString == null || queryString.length() == 0 || privateKey == null) { SAML2Utils.debug.error( classMethod + "Either input query string or private key is null." ); throw new SAML2Exception( SAML2Utils.bundle.getString("nullInput")); } if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod + "Input query string:\n" + queryString); } final String querySigAlg; final String alg = privateKey.getAlgorithm(); switch (alg) { case "RSA": //Defaulting to RSA-SHA1 for the sake of interoperability querySigAlg = SystemPropertiesManager.get(SAML2Constants.QUERY_SIGNATURE_ALGORITHM_RSA, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1); break; case "DSA": //Defaulting to SHA1WithDSA as JDK7 does not support SHA256WithDSA querySigAlg = SystemPropertiesManager.get(SAML2Constants.QUERY_SIGNATURE_ALGORITHM_DSA, XMLSignature.ALGO_ID_SIGNATURE_DSA); break; case "EC": querySigAlg = SystemPropertiesManager.get(SAML2Constants.QUERY_SIGNATURE_ALGORITHM_EC, XMLSignature.ALGO_ID_SIGNATURE_ECDSA_SHA512); break; default: SAML2Utils.debug.error(classMethod + "Private Key algorithm not supported: " + alg); throw new SAML2Exception(SAML2Utils.bundle.getString("algorithmNotSupported")); } Signature sig; try { sig = Signature.getInstance(JCEMapper.translateURItoJCEID(querySigAlg)); } catch (NoSuchAlgorithmException nsae) { throw new SAML2Exception(nsae); } if(queryString.charAt(queryString.length()-1) != '&'){ queryString = queryString + "&"; } queryString += SAML2Constants.SIG_ALG + "=" + URLEncDec.encode(querySigAlg); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod + "Final string to be signed:\n" + queryString); } byte[] sigBytes; try { sig.initSign(privateKey); sig.update(queryString.getBytes()); sigBytes = sig.sign(); } catch (GeneralSecurityException gse) { throw new SAML2Exception(gse); } if (sigBytes == null || sigBytes.length == 0) { SAML2Utils.debug.error( classMethod + "Generated signature is null"); throw new SAML2Exception( SAML2Utils.bundle.getString( "nullSigGenerated" ) ); } Base64 encoder = new Base64(); String encodedSig = encoder.encode(sigBytes); queryString += "&" + SAML2Constants.SIGNATURE + "=" + URLEncDec.encode(encodedSig); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod + "Signed query string:\n" + queryString); } return queryString; } /** * Verifies the query string signature. * * @param queryString Signed query String. * @param verificationCerts Verification certificates. * @return boolean whether the verification is successful or not. * @throws SAML2Exception if there is an error during verification. */ public static boolean verify( String queryString, Set verificationCerts ) throws SAML2Exception { String classMethod = "QuerySignatureUtil.verify: "; if (queryString == null || queryString.length() == 0 || verificationCerts.isEmpty()) { SAML2Utils.debug.error( classMethod + "Input query string or certificate is null"); throw new SAML2Exception( SAML2Utils.bundle.getString("nullInput")); } if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod + "Query string to be verifed:\n" + queryString); } StringTokenizer st = new StringTokenizer(queryString, "&"); String token = null; String samlReq = null; String samlRes = null; String relay = null; String sigAlg = null; String encSig = null; while (st.hasMoreTokens()) { token = st.nextToken(); if (token.startsWith(SAML2Constants.SAML_REQUEST)) { samlReq=token; } else if (token.startsWith(SAML2Constants.SAML_RESPONSE)) { samlRes=token; } else if (token.startsWith(SAML2Constants.RELAY_STATE)) { relay=token; } else if (token.startsWith(SAML2Constants.SIG_ALG)) { sigAlg = token; } else if (token.startsWith(SAML2Constants.SIGNATURE)) { encSig = token; } } if (sigAlg == null || sigAlg.equals("")) { SAML2Utils.debug.error( classMethod + "Null SigAlg query parameter."); throw new SAML2Exception( SAML2Utils.bundle.getString("nullSigAlg")); } if (encSig == null || encSig.equals("")) { SAML2Utils.debug.error( classMethod + "Null Signature query parameter."); throw new SAML2Exception( SAML2Utils.bundle.getString("nullSig")); } // The following manipulation is necessary because // other implementations could send the query // parameters out of order, i.e., not in the same // order when signature is produced String newQueryString = null; if (samlReq != null) { newQueryString = samlReq; } else { newQueryString = samlRes; } if (relay != null) { newQueryString += "&"+relay; } newQueryString += "&"+sigAlg; if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod+ "Query string to be verifed (re-arranged):\n" + newQueryString); } int sigAlgValueIndex = sigAlg.indexOf('='); String sigAlgValue = sigAlg.substring(sigAlgValueIndex+1); if (sigAlgValue == null || sigAlgValue.equals("")) { SAML2Utils.debug.error( classMethod + "Null SigAlg query parameter value."); throw new SAML2Exception( SAML2Utils.bundle.getString("nullSigAlg")); } sigAlgValue = URLEncDec.decode(sigAlgValue); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod + "SigAlg query parameter value: " + sigAlgValue); } int encSigValueIndex = encSig.indexOf('='); String encSigValue = encSig.substring(encSigValueIndex+1); if (encSigValue == null || encSigValue.equals("")) { SAML2Utils.debug.message( classMethod + "Null Signature query parameter value."); throw new SAML2Exception( SAML2Utils.bundle.getString("nullSig")); } encSigValue = URLEncDec.decode(encSigValue); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( classMethod + "Signature query parameter value:\n" + encSigValue); } // base-64 decode the signature value byte[] signature = null; Base64 decoder = new Base64(); signature = decoder.decode(encSigValue); // get Signature instance based on algorithm if (!SIGNATURE.equals(JCEMapper.getAlgorithmClassFromURI(sigAlgValue))) { SAML2Utils.debug.error(classMethod + "Signature algorithm " + sigAlgValue + " is not supported."); throw new SAML2Exception(SAML2Utils.bundle.getString("algNotSupported")); } Signature sig; try { sig = Signature.getInstance(JCEMapper.translateURItoJCEID(sigAlgValue)); } catch (NoSuchAlgorithmException nsae) { throw new SAML2Exception(nsae); } return isValidSignature(sig, verificationCerts, newQueryString.getBytes(), signature); } private static boolean isValidSignature(Signature sig, Set certificates, byte[] queryString, byte[] signature) throws SAML2Exception { final String classMethod = "QuerySignatureUtil.isValidSignature: "; Exception firstException = null; for (X509Certificate certificate : certificates) { try { sig.initVerify(certificate); sig.update(queryString); if (sig.verify(signature)) { return true; } } catch (InvalidKeyException | SignatureException ex) { SAML2Utils.debug.warning(classMethod + "Signature validation failed due to " + ex); if (firstException == null) { firstException = ex; } } } if (firstException != null) { throw new SAML2Exception(firstException); } return false; } }