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