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