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