com.vmware.identity.saml.impl.TokenAuthorityImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.identity.saml.impl.TokenAuthorityImpl.java

Source

/*
 *  Copyright (c) 2012-2015 VMware, Inc.  All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not
 *  use this file except in compliance with the License.  You may obtain a copy
 *  of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, without
 *  warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED.  See the
 *  License for the specific language governing permissions and limitations
 *  under the License.
 */
package com.vmware.identity.saml.impl;

import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;

import org.apache.commons.lang.Validate;
import org.joda.time.DateTime;
import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.AttributeValue;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.KeyInfoConfirmationDataType;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.ProxyRestriction;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.SubjectConfirmation;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.saml2.core.impl.AdviceBuilder;
import org.opensaml.saml2.core.impl.AssertionBuilder;
import org.opensaml.saml2.core.impl.AssertionMarshaller;
import org.opensaml.saml2.core.impl.AttributeStatementBuilder;
import org.opensaml.saml2.core.impl.AudienceBuilder;
import org.opensaml.saml2.core.impl.AudienceRestrictionBuilder;
import org.opensaml.saml2.core.impl.AuthnContextBuilder;
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml2.core.impl.AuthnStatementBuilder;
import org.opensaml.saml2.core.impl.ConditionsBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.KeyInfoConfirmationDataTypeBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.ProxyRestrictionBuilder;
import org.opensaml.saml2.core.impl.SubjectBuilder;
import org.opensaml.saml2.core.impl.SubjectConfirmationBuilder;
import org.opensaml.saml2.core.impl.SubjectConfirmationDataBuilder;
import org.opensaml.samlext.saml2delrestrict.Delegate;
import org.opensaml.samlext.saml2delrestrict.DelegationRestrictionType;
import org.opensaml.samlext.saml2delrestrict.impl.DelegateBuilder;
import org.opensaml.samlext.saml2delrestrict.impl.DelegationRestrictionTypeBuilder;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.Namespace;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.schema.XSString;
import org.opensaml.xml.signature.SignatureConstants;
import org.opensaml.xml.signature.impl.KeyInfoBuilder;
import org.opensaml.xml.signature.impl.X509CertificateBuilder;
import org.opensaml.xml.signature.impl.X509DataBuilder;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLConstants;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.vmware.identity.diagnostics.DiagnosticsLoggerFactory;
import com.vmware.identity.diagnostics.IDiagnosticsLogger;
import com.vmware.identity.idm.PrincipalId;
import com.vmware.identity.saml.Advice;
import com.vmware.identity.saml.PrincipalAttribute;
import com.vmware.identity.saml.PrincipalAttributeDefinition;
import com.vmware.identity.saml.PrincipalAttributesExtractor;
import com.vmware.identity.saml.SamlToken;
import com.vmware.identity.saml.SamlTokenSpec;
import com.vmware.identity.saml.SamlTokenSpec.AuthenticationData;
import com.vmware.identity.saml.SamlTokenSpec.AuthenticationData.AuthnMethod;
import com.vmware.identity.saml.SamlTokenSpec.Confirmation;
import com.vmware.identity.saml.SamlTokenSpec.ConfirmationType;
import com.vmware.identity.saml.SamlTokenSpec.TokenDelegate;
import com.vmware.identity.saml.SignatureAlgorithm;
import com.vmware.identity.saml.TokenAuthority;
import com.vmware.identity.saml.config.Config;
import com.vmware.identity.saml.config.Config.SamlAuthorityConfiguration;
import com.vmware.identity.saml.config.ConfigExtractor;
import com.vmware.identity.saml.config.TokenRestrictions;
import com.vmware.identity.saml.ext.RSAAdvice;
import com.vmware.identity.saml.ext.RenewRestrictionType;
import com.vmware.identity.saml.ext.impl.RSAAdviceBuilder;
import com.vmware.identity.saml.ext.impl.RenewRestrictionTypeBuilder;
import com.vmware.identity.util.PerfConstants;
import com.vmware.identity.util.TimePeriod;

/**
 * Issuer authority of valid SAML tokens.
 */
public final class TokenAuthorityImpl implements TokenAuthority {

    private static final String HTTP_SCHEMAS_XMLSOAP_ORG_CLAIMS_UPN = "http://schemas.xmlsoap.org/claims/UPN";

    private final static IDiagnosticsLogger log = DiagnosticsLoggerFactory.getLogger(TokenAuthorityImpl.class);

    private final static IDiagnosticsLogger perfLog = DiagnosticsLoggerFactory
            .getLogger(PerfConstants.PERF_LOGGER.getClass());

    private final SignatureAlgorithm defaultSignatureAlgorithm;
    private final PrincipalAttributesExtractor principalAttributesExtractor;
    private final ConfigExtractor configExtractor;
    private final Collection<PrincipalAttributeDefinition> supportedAttributes;

    /**
     * @param defaultSignatureAlgorithm
     *           the default algorithm which will be used for signing the token
     *           if there is no such specified in either the {@link SamlTokenSpec}, or {@link SamlAuthorityConfiguration}.
     *           Cannot be null.
     * @param principalAttributesExtractor
     *           to be used for extracting attributes which should be included in
     *           the issued tokens per each principal. Cannot be null.
     * @param configExtractor
     *           extractor of configuration data for issuing SAML tokens
     *           Must not be {@code null}.
     */
    public TokenAuthorityImpl(SignatureAlgorithm defaultSignatureAlgorithm,
            PrincipalAttributesExtractor principalAttributesExtractor, ConfigExtractor configExtractor) {
        Validate.notNull(defaultSignatureAlgorithm);
        Validate.notNull(principalAttributesExtractor);
        Validate.notNull(configExtractor);

        this.defaultSignatureAlgorithm = defaultSignatureAlgorithm;
        this.principalAttributesExtractor = principalAttributesExtractor;
        this.configExtractor = configExtractor;

        this.supportedAttributes = principalAttributesExtractor.getAllAttributeDefinitions();
    }

    @Override
    public SamlToken issueToken(SamlTokenSpec spec) {
        log.debug("Started issuing token for spec:{}", spec);
        Validate.notNull(spec);

        final Config config = this.configExtractor.getConfig();
        final SamlAuthorityConfiguration samlAuthorityConfig = config.getSamlAuthorityConfig();
        final TokenRestrictions tokenRestrictions = config.getTokenRestrictions();
        final DelegationHandler delegationHandler = new DelegationHandler(tokenRestrictions.getDelegationCount(),
                principalAttributesExtractor);
        final SignInfo signInfo = getSignInfo(samlAuthorityConfig);
        final TokenLifetimeRemediator lifetimeRemediator = new TokenLifetimeRemediator(signInfo, tokenRestrictions,
                config.getClockTolerance());

        final boolean requesterIsTokenOwner = spec.requesterIsTokenOwner();
        final DelegationInfo delegationInfo = delegationHandler.getDelegationInfo(requesterIsTokenOwner,
                spec.getDelegationSpec());
        final int renewCount = new RenewRestrictor(requesterIsTokenOwner, tokenRestrictions.getRenewCount())
                .processRequest(spec.getRenewSpec());
        final SignatureAlgorithm signatureAlgorithm = getSignatureAlgorithm(spec.getSignatureAlgorithm(),
                samlAuthorityConfig);
        final List<Advice> advice = AdviceFilter.processRequest(requesterIsTokenOwner, spec.getRequestedAdvice(),
                spec.getPresentAdvice());

        // move token issue time right before create assertion, PR 1550654.
        final Date issueInstantTime = new Date();
        log.debug("Issue instant time: {}", issueInstantTime);

        final TimePeriod validity = lifetimeRemediator.remediateTokenValidity(spec, issueInstantTime);

        final Assertion assertion = createAssertion(spec, issueInstantTime, validity, delegationInfo, renewCount,
                advice, samlAuthorityConfig);

        final Element assertionElement = createSignatureAndSignAssertion(assertion, signatureAlgorithm, signInfo);
        final Document token = assertionElement.getOwnerDocument();

        log.debug("Created token(issuance time: {}):\n{}", issueInstantTime,
                XMLHelper.nodeToString(token.getDocumentElement()));

        // we can use confirmation type from the spec because it is directly used
        // for the issued token without remediation.
        final ConfirmationType confirmationType = spec.getConfirmationData().getType();

        return new SamlToken(token, signatureAlgorithm, validity, confirmationType,
                delegationInfo.isDelegableToken(), renewCount > 0);
    }

    /**
     * @param spec
     *           object containing SAML artifacts used during token creation.
     *           Cannot be null.
     * @param issueInstantDateTime
     *           the time at which the assertion is issued. Cannot be null.
     * @param tokenValidity
     *           validated and remediated token validity. Cannot be null,
     * @param delegationInfo
     *           all the delegation information needed to be included into this
     *           assertion. Cannot be null.
     * @param renewCount
     *           number of renew count which will be included into this
     *           assertion. Non-negative.
     * @param advice
     *           advice to put in the token. Cannot be null.
     * @return an assertion object representing the token
     */
    private Assertion createAssertion(SamlTokenSpec spec, Date issueInstantDateTime, TimePeriod tokenValidity,
            DelegationInfo delegationInfo, int renewCount, List<Advice> advice,
            SamlAuthorityConfiguration samlAuthorityConfig) {
        assert spec != null;
        assert issueInstantDateTime != null;
        assert tokenValidity != null;
        assert tokenValidity.getStartTime() != null;
        assert tokenValidity.getEndTime() != null;
        assert delegationInfo != null;
        assert renewCount >= 0;
        assert advice != null;

        AuthenticationData authnData = spec.getAuthenticationData();
        Set<PrincipalAttribute> principalAttributes = getTokenAttributesAndIdentityAttribute(spec);
        PrincipalAttribute identityAttribute = extractIdentityAttribute(authnData.getIdentityAttrName(),
                principalAttributes);
        Set<PrincipalAttribute> tokenAttributes = extractTokenAttributes(identityAttribute, principalAttributes,
                spec);

        Assertion assertion = new AssertionBuilder().buildObject();
        assertion.setID(generateAssertionId());
        assertion.setVersion(SAMLVersion.VERSION_20);
        assertion.setIssueInstant(new DateTime(issueInstantDateTime));
        assertion.setIssuer(createIssuer(samlAuthorityConfig));
        assertion.setConditions(createConditions(tokenValidity, delegationInfo, renewCount, spec.getAudience()));
        if (!advice.isEmpty()) {
            assertion.setAdvice(createAdvice(advice));
        }
        assertion.getAuthnStatements().add(createAuthnStatement(authnData));
        assertion.getAttributeStatements().add(createAttributeStatement(tokenAttributes));
        assertion.setSubject(createSubject(identityAttribute, tokenValidity.getEndTime(),
                spec.getConfirmationData(), delegationInfo.lastDelegate()));

        registerNS(assertion, XMLConstants.XSD_NS, XMLConstants.XSD_PREFIX);
        registerNS(assertion, XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX);

        return assertion;
    }

    private Set<PrincipalAttribute> getTokenAttributesAndIdentityAttribute(SamlTokenSpec spec) {
        assert spec != null;
        final PrincipalId tokenSubject = spec.requesterIsTokenOwner()
                ? spec.getAuthenticationData().getPrincipalId()
                : spec.getDelegationSpec().getDelegationHistory().getTokenSubject();
        assert tokenSubject != null;

        Collection<PrincipalAttributeDefinition> attributeDefinitions = new HashSet<PrincipalAttributeDefinition>();

        PrincipalAttributeDefinition resolvedIdentityAttribute = resolveToAttributeDefinitions(
                spec.getAuthenticationData().getIdentityAttrName());
        if (resolvedIdentityAttribute == null) {
            throw new IllegalStateException("Identity attribute is not supported");
        }
        attributeDefinitions.add(resolvedIdentityAttribute);

        attributeDefinitions.addAll(resolveToAttributeDefinitions(spec.getAttributeNames()));

        return principalAttributesExtractor.getAttributes(tokenSubject, attributeDefinitions);
    }

    private PrincipalAttributeDefinition resolveToAttributeDefinitions(String identityAttributeName) {
        assert identityAttributeName != null;

        PrincipalAttributeDefinition result = null;
        for (PrincipalAttributeDefinition attrDef : supportedAttributes) {
            if (identityAttributeName.equals(attrDef.getName())) {
                result = attrDef;
                break;
            }
        }

        return result;
    }

    private Collection<PrincipalAttributeDefinition> resolveToAttributeDefinitions(
            Collection<String> attributeNames) {
        assert attributeNames != null;

        Set<PrincipalAttributeDefinition> result = new HashSet<PrincipalAttributeDefinition>();
        for (String attrName : attributeNames) {
            PrincipalAttributeDefinition resolvedAttr = resolveToAttributeDefinitions(attrName);
            if (resolvedAttr != null) {
                result.add(resolvedAttr);
            }
        }

        return result;
    }

    private PrincipalAttribute extractIdentityAttribute(String identityAttributeName,
            Set<PrincipalAttribute> principalAttributes) {
        assert identityAttributeName != null;
        assert principalAttributes != null;

        log.debug("Identity attribute name for token subject: {}", identityAttributeName);

        PrincipalAttribute identityAttribute = null;
        for (PrincipalAttribute principalAttribute : principalAttributes) {
            if (principalAttribute.getName().equals(identityAttributeName)) {
                identityAttribute = principalAttribute;
                break;
            }
        }
        if (identityAttribute == null || identityAttribute.getValues() == null
                || identityAttribute.getValues().length != 1) {
            // this should not be illegal state, but request failed exception
            throw new IllegalStateException("The desired identity attribute " + identityAttributeName
                    + " is not supported(not found in the schema or found but does not have value or have multiple values).");
        }

        return identityAttribute;
    }

    private Set<PrincipalAttribute> extractTokenAttributes(PrincipalAttribute identityAttribute,
            Set<PrincipalAttribute> principalAttributes, SamlTokenSpec spec) {

        assert principalAttributes != null;
        assert identityAttribute != null;
        assert spec != null;

        Set<PrincipalAttribute> tokenAttributes = principalAttributes;

        if (!spec.getAttributeNames().contains(spec.getAuthenticationData().getIdentityAttrName())) {
            tokenAttributes.remove(identityAttribute);
        }

        return tokenAttributes;
    }

    private SignatureAlgorithm getSignatureAlgorithm(SignatureAlgorithm desiredSignatureAlgorithmInRequest,
            SamlAuthorityConfiguration samlAuthorityConfig) {

        SignatureAlgorithm signatureAlgorithm = desiredSignatureAlgorithmInRequest;
        if (signatureAlgorithm == null) {
            final String signatureAlgorithmInConfig = samlAuthorityConfig.getSignatureAlgorithm();

            if (signatureAlgorithmInConfig != null) {
                SignatureAlgorithm signatureAlgorithmFromConfig = SignatureAlgorithm
                        .getSignatureAlgorithmForURI(signatureAlgorithmInConfig);
                if (signatureAlgorithmFromConfig != null) {
                    signatureAlgorithm = signatureAlgorithmFromConfig;
                } else {
                    log.warn("{} algorithm is not supported.", signatureAlgorithmInConfig);
                }
            }

            if (signatureAlgorithm == null) {
                signatureAlgorithm = this.defaultSignatureAlgorithm;
            }
        }

        log.debug("{} signature algorithm will be used for signing the token.", signatureAlgorithm);

        return signatureAlgorithm;
    }

    /**
     * Registers NS in assertion.
     */
    private void registerNS(Assertion assertion, String uri, String prefix) {
        assert assertion != null;
        assert uri != null;
        assert prefix != null;

        assertion.getNamespaceManager().registerNamespace(new Namespace(uri, prefix));
    }

    /**
     * @return generated assertion Id
     */
    private String generateAssertionId() {
        String assertionId = "_" + UUID.randomUUID().toString();
        log.debug("Generated assertion id: {}", assertionId);
        return assertionId;
    }

    /**
     * Creates conditions section of the token. Inserts in it only NotBefore and
     * NotOnOrAfter attributes.
     *
     * @param tokenValidity
     *           time period of the token validity. Cannot be null.
     * @param delegationInfo
     *           Cannot be null.
     * @param renewSpec
     *           renew specification. Cannot be null.
     * @param audience
     *           audience. Cannot be null.
     * @return initialized Conditions object representing subject part of the
     *         token.
     */
    private Conditions createConditions(TimePeriod tokenValidity, DelegationInfo delegationInfo, int renewCount,
            Set<String> audience) {
        assert tokenValidity != null;
        assert tokenValidity.getStartTime() != null;
        assert tokenValidity.getEndTime() != null;
        assert delegationInfo != null;
        assert renewCount >= 0;
        assert audience != null;

        Conditions conditions = new ConditionsBuilder().buildObject();
        conditions.setNotBefore(new DateTime(tokenValidity.getStartTime()));
        conditions.setNotOnOrAfter(new DateTime(tokenValidity.getEndTime()));

        log.debug("Created conditions - notBefore: {}, notOnOrAfter: {}", tokenValidity.getStartTime(),
                tokenValidity.getEndTime());

        conditions.getConditions().add(createProxyRestriction(delegationInfo.getDelegationCount()));

        List<TokenDelegate> delegationChain = delegationInfo.getDelegationChain();
        if (delegationChain != null && delegationChain.size() > 0) {
            conditions.getConditions().add(createDelegationChain(delegationChain));
        }

        addRenewRestriction(conditions, renewCount);

        final AudienceRestriction audienceCondition = createAudienceRestriction(audience);
        if (audienceCondition != null) {
            conditions.getAudienceRestrictions().add(audienceCondition);
        }
        return conditions;
    }

    /**
     * @param proxyCount
     * @return
     */
    private ProxyRestriction createProxyRestriction(int proxyCount) {

        ProxyRestriction proxy = new ProxyRestrictionBuilder().buildObject();
        proxy.setProxyCount(proxyCount);

        log.debug("Token delegation count is {}", proxyCount);

        return proxy;
    }

    /**
     * @param delegationChain
     *           required
     * @return
     */
    private DelegationRestrictionType createDelegationChain(List<TokenDelegate> delegationChain) {
        assert delegationChain != null;

        DelegationRestrictionType delegationRestriction = new DelegationRestrictionTypeBuilder().buildObject();
        List<Delegate> delegateList = delegationRestriction.getDelegates();
        for (TokenDelegate tokenDelegate : delegationChain) {
            Delegate delegate = new DelegateBuilder().buildObject();
            delegate.setDelegationInstant(new DateTime(tokenDelegate.getDelegationDate()));
            NameID delegateName = createUPNNameId(tokenDelegate.getSubject());
            delegate.setNameID(delegateName);
            delegateList.add(delegate);
        }

        log.debug("Created delegation chain");

        return delegationRestriction;
    }

    private void addRenewRestriction(Conditions conditions, int renewCount) {
        assert conditions != null;
        assert renewCount >= 0;

        if (renewCount > 0) {
            conditions.getConditions().add(createRenewRestriction(renewCount));
        } else {
            log.debug("Token renew count is 0 hence no renew restriction is added.");
        }
    }

    private RenewRestrictionType createRenewRestriction(int count) {

        RenewRestrictionType proxy = new RenewRestrictionTypeBuilder().buildObject();
        proxy.setCount(count);

        log.debug("Token renew count is {}", count);
        return proxy;
    }

    private AudienceRestriction createAudienceRestriction(Set<String> audience) {
        assert audience != null;

        final AudienceRestriction result = audience.isEmpty() ? null
                : new AudienceRestrictionBuilder().buildObject();
        for (String audienceParty : audience) {
            Audience samlAudience = new AudienceBuilder().buildObject();
            samlAudience.setAudienceURI(audienceParty);
            result.getAudiences().add(samlAudience);
            log.trace("Audience restriction added for  {}", audienceParty);
        }

        return result;
    }

    private org.opensaml.saml2.core.Advice createAdvice(List<Advice> advice) {
        assert advice != null && !advice.isEmpty();
        final org.opensaml.saml2.core.Advice result = new AdviceBuilder().buildObject();
        for (Advice adviceMember : advice) {
            final RSAAdvice rsaAdvice = toOpenSamlAdvice(adviceMember);
            result.getChildren().add(rsaAdvice);
        }
        return result;
    }

    private RSAAdvice toOpenSamlAdvice(Advice advice) {
        final RSAAdvice openSamlAdvice = new RSAAdviceBuilder().buildObject();
        openSamlAdvice.setSource(advice.sourceURI());
        for (Advice.Attribute adviceAttr : advice.attributes()) {
            // TODO unify PrincipalAttribute and Advice.Attribute
            final PrincipalAttribute principalAttribute = new PrincipalAttribute(adviceAttr.nameURI(),
                    NameIDType.UNSPECIFIED, adviceAttr.friendlyName(), adviceAttr.values().toArray(new String[0]));
            final Attribute openSamlAttribute = convertToOpenSamlAttribute(principalAttribute);
            openSamlAdvice.getAttributes().add(openSamlAttribute);
        }
        return openSamlAdvice;
    }

    /**
     * Create subject section of the token
     *
     * @return initialized Subject object representing Subject part of the token.
     */
    private Subject createSubject(PrincipalAttribute identityAttribute, Date tokenEndTime,
            Confirmation confirmation, TokenDelegate lastDelegate) {
        assert identityAttribute != null;
        assert tokenEndTime != null;
        assert confirmation != null;

        String[] tokenIdentityValue = identityAttribute.getValues();
        assert tokenIdentityValue.length == 1;

        String tokenSubject = tokenIdentityValue[0];
        String tokenSubjectFormat = identityAttribute.getName();

        Subject subject = new SubjectBuilder().buildObject();
        subject.setNameID(createNameId(tokenSubject, tokenSubjectFormat));
        subject.getSubjectConfirmations().add(createSubjectConfirmation(tokenEndTime, confirmation, lastDelegate));

        log.debug("Subject created : {}.", subject);
        return subject;
    }

    /**
     * Creates confirmation part of the token subject section.
     *
     * @param endTime
     * @param confirmation
     * @param lastDelegate
     * @return initialized subject confirmation object. representing
     *         SubjectConfirmation in a Subject part of the token
     */
    private SubjectConfirmation createSubjectConfirmation(Date endTime, Confirmation confirmation,
            TokenDelegate lastDelegate) {
        assert endTime != null;
        assert confirmation != null;

        SubjectConfirmation conf = new SubjectConfirmationBuilder().buildObject();
        SubjectConfirmationData confData;
        if (confirmation.getType().equals(ConfirmationType.BEARER)) {
            conf.setMethod(SubjectConfirmation.METHOD_BEARER);
            confData = createSubjectConfirmationData(confirmation, endTime);
            log.debug("Created subject confirmation - method: BEARER");
        } else {
            conf.setMethod(SubjectConfirmation.METHOD_HOLDER_OF_KEY);
            confData = createHoKSubjectConfData(confirmation);
            log.debug("Created subject confirmation - method: HoK");
        }

        conf.setSubjectConfirmationData(confData);
        if (lastDelegate != null) {
            conf.setNameID(createUPNNameId(lastDelegate.getSubject()));
        }

        return conf;
    }

    private KeyInfoConfirmationDataType createHoKSubjectConfData(Confirmation confirmation) {
        assert confirmation != null;
        assert confirmation.getCertificate() != null;

        KeyInfoConfirmationDataType data = new KeyInfoConfirmationDataTypeBuilder().buildObject();

        org.opensaml.xml.signature.X509Data x509Data = new X509DataBuilder().buildObject();
        org.opensaml.xml.signature.KeyInfo ki = new KeyInfoBuilder().buildObject();
        org.opensaml.xml.signature.X509Certificate cert = new X509CertificateBuilder().buildObject();
        try {
            cert.setValue(Base64.encodeBytes(confirmation.getCertificate().getEncoded()));
        } catch (CertificateEncodingException e) {
            // TODO check [848537] for details
            throw new IllegalStateException("Cannot encode X509Certificate", e);
        }
        x509Data.getX509Certificates().add(cert);
        ki.getX509Datas().add(x509Data);
        data.getKeyInfos().add(ki);

        log.debug("Created SubjectConfirmation data, Certificate with Subject: {}",
                confirmation.getCertificate().getSubjectX500Principal().getName());
        return data;
    }

    private SubjectConfirmationData createSubjectConfirmationData(Confirmation confirmation, Date endTime) {
        assert confirmation != null;
        assert endTime != null;

        SubjectConfirmationData data = new SubjectConfirmationDataBuilder().buildObject();
        data.setNotOnOrAfter(new DateTime(endTime));
        assert confirmation != null;
        assert data != null;

        if (confirmation.getInResponseTo() != null) {
            data.setInResponseTo(confirmation.getInResponseTo());
        }
        if (confirmation.getRecipient() != null) {
            data.setRecipient(confirmation.getRecipient());
        }

        log.debug("Created SubjectConfirmation data, NotOnOrAfter: {}, InResponseTo: {}, Recipient: {}",
                new Object[] { endTime, confirmation.getInResponseTo(), confirmation.getRecipient() });
        return data;
    }

    private NameID createUPNNameId(PrincipalId principalId) {
        return createNameId(toUPN(principalId), HTTP_SCHEMAS_XMLSOAP_ORG_CLAIMS_UPN);
    }

    /**
     * Creates name id for the sake of token subject, delegates, subject
     * confirmation, etc.
     *
     * @param subject
     *           Cannot be null.
     * @param format
     *           Cannot be null.
     * @return initialized NameId object representing NameId in a Subject part of
     *         the token.
     */
    private NameID createNameId(String subject, String format) {
        assert subject != null;
        assert format != null;

        NameID nameId = new NameIDBuilder().buildObject();
        nameId.setValue(subject);
        nameId.setFormat(format);

        log.debug("Created nameId: {}", subject);
        return nameId;
    }

    /**
     * Creates issuer part of the token.
     *
     * @return initialized issuer object. representing SubjectConfirmation in a
     *         Subject part of the token.
     */
    private Issuer createIssuer(SamlAuthorityConfiguration samlAuthorityConfig) {
        IssuerBuilder b = new IssuerBuilder();
        Issuer issuerSamlObj = b.buildObject();
        String issuer = samlAuthorityConfig.getIssuer();

        issuerSamlObj.setFormat(NameIDType.ENTITY);
        issuerSamlObj.setValue(issuer);

        log.debug("Created Issuer: {}", issuer);
        return issuerSamlObj;
    }

    /**
     * Creates AuthenticationStatement section of the token.
     *
     * @param authnData
     *           the entire authentication data for current request, containing
     *           how authentication is done (by pass, kerberos, etc.), time of
     *           authentication, authenticated principal, etc. Cannot be null.
     * @return initialized AuthnStatement part of the token.
     */
    private AuthnStatement createAuthnStatement(AuthenticationData authnData) {
        assert authnData != null;

        DateTime authnInstantTime = new DateTime(authnData.getAuthnTime());
        assert authnInstantTime != null;

        AuthnStatement authnStatement = new AuthnStatementBuilder().buildObject();
        authnStatement.setAuthnInstant(authnInstantTime);
        if (authnData.getSessionIndex() != null) {
            authnStatement.setSessionIndex(authnData.getSessionIndex());
        }

        AuthnContext authnContext = new AuthnContextBuilder().buildObject();
        AuthnContextClassRef authnContextClassRef = new AuthnContextClassRefBuilder().buildObject();
        authnContextClassRef.setAuthnContextClassRef(getAuthnMethod(authnData));

        authnContext.setAuthnContextClassRef(authnContextClassRef);
        authnStatement.setAuthnContext(authnContext);

        Date sessionExpiredate = authnData.getSessionExpireDate();
        if (sessionExpiredate != null) {
            DateTime expireDateTime = new DateTime(sessionExpiredate);
            authnStatement.setSessionNotOnOrAfter(expireDateTime);
        }

        return authnStatement;
    }

    /**
     * @param authnData
     *           not null
     * @return the authentication method that should be inserted into the authn
     *         statement of the SAML token
     */
    private String getAuthnMethod(AuthenticationData authnData) {
        assert authnData != null;
        AuthnMethod authnMethod = authnData.getAuthnMethod();
        assert authnMethod != null;

        String authnMethodValue;
        switch (authnMethod) {
        case PASSWORD:
            authnMethodValue = AuthnContext.PPT_AUTHN_CTX;
            break;
        case KERBEROS:
            authnMethodValue = AuthnContext.KERBEROS_AUTHN_CTX;
            break;
        case XMLDSIG:
            authnMethodValue = AuthnContext.XML_DSIG_AUTHN_CTX;
            break;
        case ASSERTION:
            authnMethodValue = AuthnContext.PREVIOUS_SESSION_AUTHN_CTX;
            break;
        case NTLM:
            authnMethodValue = AuthnContext.KERBEROS_AUTHN_CTX;
            break;
        case TLSCLIENT:
            authnMethodValue = AuthnContext.TLS_CLIENT_AUTHN_CTX;
            break;
        case TIMESYNCTOKEN:
            authnMethodValue = AuthnContext.TIME_SYNC_TOKEN_AUTHN_CTX;
            break;
        default:
            throw new IllegalStateException("Unknown authentication method");
        }
        return authnMethodValue;
    }

    /**
     * @param principalAttributes
     *           Cannot be null.
     * @return object representation of AttributeStatement part of token.
     */
    private AttributeStatement createAttributeStatement(Set<PrincipalAttribute> principalAttributes) {
        assert principalAttributes != null;

        AttributeStatement attributeStatement = null;
        if (!principalAttributes.isEmpty()) {
            attributeStatement = new AttributeStatementBuilder().buildObject();
            List<Attribute> attributes = attributeStatement.getAttributes();

            for (PrincipalAttribute principalAttribute : principalAttributes) {
                Attribute currentAttribute = convertToOpenSamlAttribute(principalAttribute);
                attributes.add(currentAttribute);

                log.debug("Added Attribute to statement.");
            }
        } else {
            log.trace("No attributes found so no attribute statement is added!");
        }
        return attributeStatement;
    }

    /**
     * Creates attribute in attribute statement part of the token.
     *
     * @param principalAttribute
     *           cannot be null.
     * @return attribute object
     */
    private Attribute convertToOpenSamlAttribute(PrincipalAttribute principalAttribute) {
        assert principalAttribute != null;

        XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
        @SuppressWarnings("unchecked")
        XMLObjectBuilder<Attribute> builder = builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME);
        Attribute attribute = builder.buildObject(Attribute.DEFAULT_ELEMENT_NAME);

        attribute.setName(principalAttribute.getName());
        attribute.setNameFormat(principalAttribute.getNameFormat());

        String friendlyName = principalAttribute.getFriendlyName();
        if (friendlyName != null) {
            attribute.setFriendlyName(friendlyName);
        }

        String[] values = principalAttribute.getValues();
        if (values != null) {
            @SuppressWarnings("unchecked")
            final XMLObjectBuilder<XSString> stringBuilder = builderFactory.getBuilder(XSString.TYPE_NAME);
            for (String value : values) {
                XSString attributeVal = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
                        XSString.TYPE_NAME);

                attributeVal.setValue(value);
                attribute.getAttributeValues().add(attributeVal);
            }
        }

        log.debug("Created Attribute - name: {}, nameFormat: {}", principalAttribute.getName(),
                principalAttribute.getNameFormat());
        return attribute;
    }

    /**
     * Creates signature part of assertion. Uses digest method algorithm
     * corresponding to the signature algorithm used.
     *
     * @param assertion
     * @param signatureAlgorithm
     * @return
     */
    private Element createSignatureAndSignAssertion(Assertion assertion, SignatureAlgorithm signatureAlgorithm,
            SignInfo signInfo) {
        assert assertion != null;
        assert signatureAlgorithm != null;

        XMLSignatureFactory factory = XMLSignatureFactory.getInstance();
        Element assertionElement = marshallAssertion(assertion);
        List<Transform> transforms = createTransforms();
        Reference ref = createReference(transforms, assertionElement.getAttribute(Assertion.ID_ATTRIB_NAME),
                // here we use the digest method which is corresponding to the
                // signature algorithm used
                signatureAlgorithm.getDigestMethod().toString());
        SignedInfo signedInfo = createSignedInfo(Collections.singletonList(ref), signatureAlgorithm);

        DOMSignContext signingContext = new DOMSignContext(signInfo.getPrivateKey(), assertionElement);
        signingContext.putNamespacePrefix(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, "ec");
        signingContext.putNamespacePrefix(XMLSignature.XMLNS, "ds");

        // signature should be the second section in the assertion - after issuer
        // here we are sure that the structure of assertion is as follows:
        // 1) issuer 2) subject
        // we get subject node and enter signature before it and the result is:
        // 1) issuer 2) signature 3) subject
        Node subjectNode = assertionElement.getChildNodes().item(1);
        signingContext.setNextSibling(subjectNode);
        log.debug("Set SigningContext into assertion (after Issuer or as a first child in the assertion DOM).");

        final KeyInfo keyInfo = createKeyInfo(signInfo);
        XMLSignature xmlSignature = factory.newXMLSignature(signedInfo, keyInfo);

        try {
            final long start = System.nanoTime();
            xmlSignature.sign(signingContext);
            perfLog.trace("'signature.sign' took {} ms.", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
        } catch (MarshalException e) {
            throw new IllegalStateException(e);
        } catch (XMLSignatureException e) {
            throw new IllegalStateException(e);
        }
        log.debug("Created Signature and sign it.");

        return assertionElement;
    }

    /**
     * Create a SignInfo instance with signCertificateChain from samlAuthorityConfig.
     * @param samlAuthorityConfig
     * @return created SignInfo.
     */
    private static SignInfo getSignInfo(SamlAuthorityConfiguration samlAuthorityConfig) {
        List<Certificate> signingCertificateChain = samlAuthorityConfig.getSigningCertificateChain();

        final CertPath certPath;
        try {
            final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            certPath = certificateFactory.generateCertPath(signingCertificateChain);
        } catch (CertificateException e) {
            throw new RuntimeException(e);
        }

        SignInfo signInfo = new SignInfo(samlAuthorityConfig.getAuthorityKey(), certPath, null); // TODO provider

        return signInfo;
    }

    /**
     * Create KeyInfo section representation.
     *
     * @return KeyInfo
     */
    private KeyInfo createKeyInfo(SignInfo signInfo) {
        List<? extends Certificate> stsCertificates = signInfo.getCertificationPath().getCertificates();

        XMLSignatureFactory factory = XMLSignatureFactory.getInstance();
        KeyInfoFactory keyInfoFactory = factory.getKeyInfoFactory();
        X509Data certificatesData = keyInfoFactory.newX509Data(stsCertificates);

        log.debug("Created KeyInfo section from certificates: {}", stsCertificates);
        return keyInfoFactory.newKeyInfo(Collections.singletonList(certificatesData));
    }

    /**
     * Creates SignedInfo section part of Signature.
     *
     * @param references
     *           references to be included in SignedInfo. Cannot be null.
     * @param signatureAlgorithm
     * @return returns SignedInfo object representing SignedInfo section
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    private SignedInfo createSignedInfo(List<Reference> references, SignatureAlgorithm signatureAlgorithm) {
        assert references != null;
        assert signatureAlgorithm != null;

        XMLSignatureFactory factory = XMLSignatureFactory.getInstance();

        CanonicalizationMethod canonicalizationMethod;
        try {
            canonicalizationMethod = factory.newCanonicalizationMethod(
                    SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, (C14NMethodParameterSpec) null);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create canonicalization object.", e);
        }

        SignatureMethod signatureMethod;
        try {
            signatureMethod = factory.newSignatureMethod(signatureAlgorithm.toString(), null);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create signature algorithm object.", e);
        }

        SignedInfo signedInfo = factory.newSignedInfo(canonicalizationMethod, signatureMethod, references);

        log.debug("Created SignedInfo section with signatureAlgorithm: {}", signatureAlgorithm);
        return signedInfo;
    }

    /**
     * Creates a Reference part of Signature section
     *
     * @param transforms
     * @param id
     * @param digestMethod
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    private Reference createReference(List<Transform> transforms, String id, String digestMethod) {
        assert transforms != null;
        assert id != null;
        assert digestMethod != null;

        XMLSignatureFactory factory = XMLSignatureFactory.getInstance();

        javax.xml.crypto.dsig.DigestMethod digestAlgorithm;
        try {
            digestAlgorithm = factory.newDigestMethod(digestMethod, null);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create digest method object.", e);
        }

        log.debug("Created reference with id: {} and digestMethod: {}", id, digestMethod);
        return factory.newReference("#" + id, digestAlgorithm, transforms, null, null);
    }

    /**
     * Creates a list of transform part of Reference section in Signature
     *
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    private List<Transform> createTransforms() {
        XMLSignatureFactory factory = XMLSignatureFactory.getInstance();

        List<Transform> transforms = new ArrayList<Transform>(2);

        List<String> prefixList = new ArrayList<String>(2);
        prefixList.add(XMLConstants.XSD_PREFIX);
        prefixList.add(XMLConstants.XSI_PREFIX);

        try {
            transforms.add(factory.newTransform(CanonicalizationMethod.ENVELOPED, (TransformParameterSpec) null));
            transforms.add(
                    factory.newTransform(CanonicalizationMethod.EXCLUSIVE, new ExcC14NParameterSpec(prefixList)));
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create enveloped or exclusive transform objects.", e);
        }

        log.debug("Created transforms: {} and {}", CanonicalizationMethod.ENVELOPED,
                CanonicalizationMethod.EXCLUSIVE);
        return transforms;
    }

    /**
     * Marshalls created assertion object.
     *
     * @param assertion
     *           object
     * @return
     */
    private Element marshallAssertion(Assertion assertion) {
        assert assertion != null;

        AssertionMarshaller marshaller = new AssertionMarshaller();
        Element assertionElement;
        try {
            assertionElement = marshaller.marshall(assertion);
        } catch (MarshallingException e) {
            throw new IllegalStateException(e);
        }

        log.debug("Successfully marshalled assertion: {}", assertionElement);
        return assertionElement;
    }

    /**
     * @param principalId
     *           principal id. Cannot be null.
     * @return Universal Principal Name(UPN) of the principal id.
     */
    private String toUPN(PrincipalId principalId) {
        assert principalId != null;
        return principalId.getName() + "@" + principalId.getDomain();
    }

}