/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.cxf.sts.token.provider; import java.security.Principal; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.x500.X500Principal; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.sts.STSConstants; import org.apache.cxf.sts.STSPropertiesMBean; import org.apache.cxf.sts.request.KeyRequirements; import org.apache.cxf.sts.request.ReceivedKey; import org.apache.cxf.sts.request.ReceivedToken; import org.apache.cxf.sts.request.ReceivedToken.STATE; import org.apache.cxf.sts.request.TokenRequirements; import org.apache.cxf.sts.service.EncryptionProperties; import org.apache.cxf.ws.security.sts.provider.STSException; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.CryptoType; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.principal.UsernameTokenPrincipal; import org.apache.wss4j.common.saml.bean.KeyInfoBean; import org.apache.wss4j.common.saml.bean.KeyInfoBean.CERT_IDENTIFIER; import org.apache.wss4j.common.saml.bean.SubjectBean; import org.apache.wss4j.common.saml.builder.SAML1Constants; import org.apache.wss4j.common.saml.builder.SAML2Constants; import org.apache.wss4j.dom.WSConstants; import org.apache.wss4j.dom.message.WSSecEncryptedKey; /** * A default implementation of SubjectProvider to create a SAML Assertion. The Subject name is the name * of the current principal, the subject name qualifier is a default URL that can be configured, and the * subject confirmation method is created according to the token type and key type. If the Subject * Confirmation Method is SymmetricKey or PublicKey, the appropriate KeyInfoBean object is created and * attached to the Subject. */ public class DefaultSubjectProvider implements SubjectProvider { private static final Logger LOG = LogUtils.getL7dLogger(DefaultSubjectProvider.class); private String subjectNameQualifier = "http://cxf.apache.org/sts"; private String subjectNameIDFormat; /** * Set the SubjectNameQualifier. */ public void setSubjectNameQualifier(String subjectNameQualifier) { this.subjectNameQualifier = subjectNameQualifier; if (LOG.isLoggable(Level.FINE)) { LOG.fine("Setting Subject Name Qualifier: " + subjectNameQualifier); } } /** * Set the SubjectNameIDFormat. */ public void setSubjectNameIDFormat(String subjectNameIDFormat) { this.subjectNameIDFormat = subjectNameIDFormat; if (LOG.isLoggable(Level.FINE)) { LOG.fine("Setting Subject Name format: " + subjectNameIDFormat); } } /** * Get a SubjectBean object. */ public SubjectBean getSubject(SubjectProviderParameters subjectProviderParameters) { // 1. Get the principal Principal principal = getPrincipal(subjectProviderParameters); if (principal == null) { LOG.fine("Error in getting principal"); throw new STSException("Error in getting principal", STSException.REQUEST_FAILED); } // 2. Create the SubjectBean using the principal SubjectBean subjectBean = createSubjectBean(principal, subjectProviderParameters); // 3. Create the KeyInfoBean and set it on the SubjectBean KeyInfoBean keyInfo = createKeyInfo(subjectProviderParameters); subjectBean.setKeyInfo(keyInfo); return subjectBean; } /** * Get the Principal (which is used as the Subject). By default, we check the following (in order): * - A valid OnBehalfOf principal * - A valid principal associated with a token received as ValidateTarget * - The principal associated with the request. We don't need to check to see if it is "valid" here, as it * is not parsed by the STS (but rather the WS-Security layer). */ protected Principal getPrincipal(SubjectProviderParameters subjectProviderParameters) { TokenProviderParameters providerParameters = subjectProviderParameters.getProviderParameters(); Principal principal = null; //TokenValidator in IssueOperation has validated the ReceivedToken //if validation was successful, the principal was set in ReceivedToken if (providerParameters.getTokenRequirements().getOnBehalfOf() != null) { ReceivedToken receivedToken = providerParameters.getTokenRequirements().getOnBehalfOf(); if (receivedToken.getState().equals(STATE.VALID)) { principal = receivedToken.getPrincipal(); } } else if (providerParameters.getTokenRequirements().getValidateTarget() != null) { ReceivedToken receivedToken = providerParameters.getTokenRequirements().getValidateTarget(); if (receivedToken.getState().equals(STATE.VALID)) { principal = receivedToken.getPrincipal(); } } else { principal = providerParameters.getPrincipal(); } return principal; } /** * Create the SubjectBean using the specified principal. */ protected SubjectBean createSubjectBean( Principal principal, SubjectProviderParameters subjectProviderParameters ) { TokenProviderParameters providerParameters = subjectProviderParameters.getProviderParameters(); TokenRequirements tokenRequirements = providerParameters.getTokenRequirements(); KeyRequirements keyRequirements = providerParameters.getKeyRequirements(); String tokenType = tokenRequirements.getTokenType(); String keyType = keyRequirements.getKeyType(); String confirmationMethod = getSubjectConfirmationMethod(tokenType, keyType); String subjectName = principal.getName(); String localSubjectNameIDFormat = subjectNameIDFormat; if (SAML2Constants.NAMEID_FORMAT_UNSPECIFIED.equals(localSubjectNameIDFormat) && principal instanceof X500Principal) { // Just use the "cn" instead of the entire DN try { LdapName ln = new LdapName(principal.getName()); for (Rdn rdn : ln.getRdns()) { if ("CN".equalsIgnoreCase(rdn.getType()) && (rdn.getValue() instanceof String)) { subjectName = (String)rdn.getValue(); break; } } } catch (Throwable ex) { subjectName = principal.getName(); //Ignore, not X500 compliant thus use the whole string as the value } } else if (!SAML2Constants.NAMEID_FORMAT_UNSPECIFIED.equals(localSubjectNameIDFormat)) { /* Set subjectNameIDFormat correctly based on type of principal unless already set to some value other than unspecified */ if (principal instanceof UsernameTokenPrincipal) { localSubjectNameIDFormat = SAML2Constants.NAMEID_FORMAT_PERSISTENT; } else if (principal instanceof X500Principal) { localSubjectNameIDFormat = SAML2Constants.NAMEID_FORMAT_X509_SUBJECT_NAME; } else if (principal instanceof KerberosPrincipal) { localSubjectNameIDFormat = SAML2Constants.NAMEID_FORMAT_KERBEROS; } else if (localSubjectNameIDFormat == null) { localSubjectNameIDFormat = SAML2Constants.NAMEID_FORMAT_UNSPECIFIED; } } SubjectBean subjectBean = new SubjectBean(subjectName, subjectNameQualifier, confirmationMethod); if (LOG.isLoggable(Level.FINE)) { LOG.fine("Creating new subject with principal name: " + principal.getName()); } subjectBean.setSubjectNameIDFormat(localSubjectNameIDFormat); return subjectBean; } /** * Get the SubjectConfirmation method given a tokenType and keyType */ protected String getSubjectConfirmationMethod(String tokenType, String keyType) { if (WSConstants.WSS_SAML_TOKEN_TYPE.equals(tokenType) || WSConstants.SAML_NS.equals(tokenType)) { if (STSConstants.SYMMETRIC_KEY_KEYTYPE.equals(keyType) || STSConstants.PUBLIC_KEY_KEYTYPE.equals(keyType)) { return SAML1Constants.CONF_HOLDER_KEY; } else { return SAML1Constants.CONF_BEARER; } } else { if (STSConstants.SYMMETRIC_KEY_KEYTYPE.equals(keyType) || STSConstants.PUBLIC_KEY_KEYTYPE.equals(keyType)) { return SAML2Constants.CONF_HOLDER_KEY; } else { return SAML2Constants.CONF_BEARER; } } } /** * Create and return the KeyInfoBean to be inserted into the SubjectBean */ protected KeyInfoBean createKeyInfo(SubjectProviderParameters subjectProviderParameters) { TokenProviderParameters providerParameters = subjectProviderParameters.getProviderParameters(); KeyRequirements keyRequirements = providerParameters.getKeyRequirements(); STSPropertiesMBean stsProperties = providerParameters.getStsProperties(); String keyType = keyRequirements.getKeyType(); if (STSConstants.SYMMETRIC_KEY_KEYTYPE.equals(keyType)) { Crypto crypto = stsProperties.getEncryptionCrypto(); EncryptionProperties encryptionProperties = providerParameters.getEncryptionProperties(); String encryptionName = encryptionProperties.getEncryptionName(); if (encryptionName == null) { // Fall back on the STS encryption name encryptionName = stsProperties.getEncryptionUsername(); } if (encryptionName == null) { LOG.fine("No encryption Name is configured for Symmetric KeyType"); throw new STSException("No Encryption Name is configured", STSException.REQUEST_FAILED); } CryptoType cryptoType = null; // Check for using of service endpoint (AppliesTo) as certificate identifier if (STSConstants.USE_ENDPOINT_AS_CERT_ALIAS.equals(encryptionName)) { if (providerParameters.getAppliesToAddress() == null) { throw new STSException("AppliesTo is not initilaized for encryption name " + STSConstants.USE_ENDPOINT_AS_CERT_ALIAS); } cryptoType = new CryptoType(CryptoType.TYPE.ENDPOINT); cryptoType.setEndpoint(providerParameters.getAppliesToAddress()); } else { cryptoType = new CryptoType(CryptoType.TYPE.ALIAS); cryptoType.setAlias(encryptionName); } try { X509Certificate[] certs = crypto.getX509Certificates(cryptoType); if ((certs == null) || (certs.length == 0)) { throw new STSException("Encryption certificate is not found for alias: " + encryptionName); } Document doc = subjectProviderParameters.getDoc(); byte[] secret = subjectProviderParameters.getSecret(); return createEncryptedKeyKeyInfo(certs[0], secret, doc, encryptionProperties, crypto); } catch (WSSecurityException ex) { LOG.log(Level.WARNING, "", ex); throw new STSException(ex.getMessage(), ex); } } else if (STSConstants.PUBLIC_KEY_KEYTYPE.equals(keyType)) { ReceivedKey receivedKey = keyRequirements.getReceivedKey(); // Validate UseKey trust if (stsProperties.isValidateUseKey() && stsProperties.getSignatureCrypto() != null) { if (receivedKey.getX509Cert() != null) { try { Collection<Pattern> constraints = Collections.emptyList(); stsProperties.getSignatureCrypto().verifyTrust( new X509Certificate[]{receivedKey.getX509Cert()}, false, constraints, null); } catch (WSSecurityException e) { LOG.log(Level.FINE, "Error in trust validation of UseKey: ", e); throw new STSException("Error in trust validation of UseKey", STSException.REQUEST_FAILED); } } if (receivedKey.getPublicKey() != null) { try { stsProperties.getSignatureCrypto().verifyTrust(receivedKey.getPublicKey()); } catch (WSSecurityException e) { LOG.log(Level.FINE, "Error in trust validation of UseKey: ", e); throw new STSException("Error in trust validation of UseKey", STSException.REQUEST_FAILED); } } } return createPublicKeyKeyInfo(receivedKey.getX509Cert(), receivedKey.getPublicKey()); } return null; } /** * Create a KeyInfoBean that contains an X.509 certificate or Public Key */ protected static KeyInfoBean createPublicKeyKeyInfo(X509Certificate certificate, PublicKey publicKey) { KeyInfoBean keyInfo = new KeyInfoBean(); if (certificate != null) { keyInfo.setCertificate(certificate); keyInfo.setCertIdentifer(CERT_IDENTIFIER.X509_CERT); } else if (publicKey != null) { keyInfo.setPublicKey(publicKey); keyInfo.setCertIdentifer(CERT_IDENTIFIER.KEY_VALUE); } return keyInfo; } /** * Create an EncryptedKey KeyInfo. */ protected static KeyInfoBean createEncryptedKeyKeyInfo( X509Certificate certificate, byte[] secret, Document doc, EncryptionProperties encryptionProperties, Crypto encryptionCrypto ) throws WSSecurityException { KeyInfoBean keyInfo = new KeyInfoBean(); // Create an EncryptedKey WSSecEncryptedKey encrKey = new WSSecEncryptedKey(doc); encrKey.setKeyIdentifierType(encryptionProperties.getKeyIdentifierType()); encrKey.setEphemeralKey(secret); encrKey.setSymmetricEncAlgorithm(encryptionProperties.getEncryptionAlgorithm()); encrKey.setUseThisCert(certificate); encrKey.setKeyEncAlgo(encryptionProperties.getKeyWrapAlgorithm()); encrKey.prepare(encryptionCrypto); Element encryptedKeyElement = encrKey.getEncryptedKeyElement(); // Append the EncryptedKey to a KeyInfo element Element keyInfoElement = doc.createElementNS( WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":" + WSConstants.KEYINFO_LN ); keyInfoElement.setAttributeNS( WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS ); keyInfoElement.appendChild(encryptedKeyElement); keyInfo.setElement(keyInfoElement); return keyInfo; } }