Showing posts with label sts. Show all posts
Showing posts with label sts. Show all posts

Monday, November 21, 2011

More on Oracle Secure Token Services (OSTS)

Last week Andre made an excellent post introducing the Oracle Secure Token Services (OSTS) product.

I wanted to follow this up by letting everyone know about a good case study on the OSTS written by Oracle's partner the PathMaker Group.  The study is based on a deployement of the OSTS that they did with a customer a couple months ago.

http://www.oracle.com/us/products/middleware/identity-management/sts-wireless-telco-provider-525434.pdf

Thursday, June 3, 2010

Impersonation and OES

When it rains it pours! I had three separate customers asking me basically the exact same question today - Can OES help me with impersonation?

So, for starters, let's define the use case. A user is logged in and wants to act as another user for some brief period of time - like for a few minutes to troubleshoot an issue. Once the impersonation is established all of the authorization should be calculated against the impersonatee (person being impersonated). In the audit logs, you should see both the impersonator and impersonatee. Optionally, there may be rules that are additionally calculated for the impersonator - example: DENY policies that are enforced even if the user is impersonating, like PII.

Just to clarify, this use case is different than delegation - which is similar except that delegation (in my vocabulary) has a longer duration than impersonation. The classic delegation use case is vacation - example: during the week I'm away, let Bob perform this privilege that I have. OES supports this type of model OOTB.

Impersonation is really more about tokens than it is about authorization, but there are some services that OES exposes that can be pretty helpful in this regard. The CredentialMappingService can be used to generate a token for a given user. You need a custom credential mapper that does two things:

1. Check if the current Subject is authroized to impersonate the other user. Looking at the signature of the getCredentials method, you can see that the actual CredentialMapper is going to get passed the Subject, the alias of the user to create, and then the RuntimeAction and RuntimeResource. This makes it pretty easy from inside of the CM to make a call to the AuthorizationService to check and see if the impersonation is allowed. I'd probably use the action "impersonate" and add the passed in Action to the AppContext.
2. Generate a token that the user can then use to impersonate. This is a little trick but basically you need to create an encrypted token that contains the name of the user - or a reference to a session - that has the name of the user. You also want to - either in the token or by reference - has access to the name of the person that is the impersonator - the Subject passed the CredentialMapperService.

So, now that you have the token, the next step is when the token is presented, to establish a JAAS Subject for the user being impersonated. This is a custom identity asserter. This identity asserter needs to be able to decrypt the token and set up the user name and the impersonator as callbacks. Since impersonator is not a standard callback, you need to pair the identity asserter with a custom login module that will make the callbacks. The user is added as a regular WLSUser. As for the impersonator, I would add them as a custom principal that extends WLSUser...like ImpersonatorUser.

With both of these added to the JAAS Subject, OES can perform authorization, and will use the regular user. If you want to have access to the impersonator, you'll need a custom attribute retriever - getImpersonator that basically pulls the ImpersonatorPrincipal from the JAAS Subject, and returns the name. Since the JAAS subect contains the impersonator name as well as the user, Audit logs will have access to both.

When the user is done impersonating, just have them logout, and log back in as themselves. There are more advanced cases when you can actually stop the impersonation. In that case, you have to extend the solution in a few ways. The first is that you need to persist the original JAAS Subject - either in the token or by reference - so that it can be restored. The second - is to have the identity asserter be able to establish the JAAS Subject of that impersonator. The third is to be able to generate either a token for the impersonator or the impersonatee based on the token type passed in the CredentialMapperService.

This is the mechanics of using the OES services, but these are just API calls. How would you get these APIs called? I think it depends on the context that you're attempting to impersonate. If this is a web-application, then this looks like a good fit for a ServletAuthenticationFilter. The tokens can be passed as HTTP cookies. For a services environment, this looks like a good use case for a custom STS and WS-Trust - wst:onBehalfOf - seems to fit very nicely here.

I left out a lot of the nitty gritty details (read: no sample), but I think this is enough to get people started. You'll have to apologize - my flight out of Atlanta (via Philadelphia) to Boston - is getting ready to board. Let me know if you like this approach and are interested in more details.

P.S.

BEAT LA
BEAT LA
BEAT LA

Take that Wayne!

Wednesday, October 7, 2009

Fat Client and SOA - A case for SAML Sender Vouches and STS

The basic scenario is that users need to "log into" their fat client applications, and then go and access some services (let's assume SOAP based) over the internet.

There are a number of questions that drive the solution in a case like this:


  • What directory/data source will users be authenticating to? A local source or a remote source?
  • Are those same directories/data sources readily available to the consuming services?


In the cases where the directory and the services are in the same security domain, and the directory is "readily available", there is no need for something elaborate. I think using the native authentication of the directory (say LDAP) and then passing the users identity to the services as something simple (HTTP Header or WS-Security UsernameToken (no password)) would probably work. Applications can just take the username (or dn) from the request and callback to the directory to get additional information. One last thing, you need to have some mitigation strategy for avoiding people spoofing DNs (adding a DN that isn't there's to the request). The simplest way to is to do the requests over 2-way SSL. Package the certificate with the application and there you go. BTW, the CSF function of OPSS is a nice approach for this user case - relies on Java Security to ensure that only authorized applications can have access to the credential (password for decrypting the private key).

The harder use case is more of a federated model - example, the user needs to authenticate locally, but the services are in another security domain. In this model, if there is additional information that the services need about the user, they need to be passed in some form. I think that SAML-Sender Vouches works nicely here. So, the application authenticates locally and then gets a SAML Assertion, signed by the issuer. The SAML Assertion could/should contain additional information needed by the service - groups/roles/attributes etc. The SAML Assertion is added to the message and the message is signed.

This is actual a good use case for an STS. Basically, the STS is taking username and password in and returning a SAML Assertion for the service. Think of it as a standards based authentication service, where the standard is WS-Trust. The stand-alone application can just be configured to point to the local STS and the application is done - no need to specify support for LDAP, RDBMS...that's left to the local deployment.

The reality is that you could actually solve the first scenario with SAML/STS it may just be overkill, but starting with this architecture does provide much more flexible business models. For example, some customers of the service want to authenticate locally, while others want to authenticate centrally. Not a problem. Its simply a matter of configuration. In the fully federated case, the centralized service trust the local authentication and can avoid the headache of password management. That issue can be pushed out to local directories - at least that's the vision.

Thursday, October 1, 2009

Calling Oracle Service Bus from MSFT WCF Client Using an STS

I hope that this is the first of two posts. In the second post, I want to able to describe how to do this use case with out an STS. As people know from this blog, I think that an STS has a time and a place. When I first did this integration, there was a real reason for having the STS. We were implementing what was essentially the MSFT claims based authorization model. The STS was calling out to an entitlements system than needed to be invoked using native .net authentication. The alternative was to have OSB generate a Kerberos Ticket for a user that it didn't have the password, and call the entitlements service. Let's just say many people consider this against security best practices. Now that I'm faced with doing this again for another customer, I eager to figure out how to do this without the STS. That aside, here's the approach.

Also, I couldn't have done this without Symon Chang, Anand Kothari, Wil Hopkins - very very smart engineers.

Overview


WCF, by default uses windows authentication. Windows authentication is based on Kerberos, so from the WCF perspective, the most logical way of propagating identity would be to use WS-Security Kerberos Token Profile. This is the standard way of conveying a Kerberos Ticket in a SOAP Message. This is supported in WCF OOTB.


The problem is that OSB 10gR3 does not. OSB10gR3 has no support for Kerberos at the message level. OSB does have support for Kerberos as part of the transport level security provided by SPNEGO. As for message level, OSB 10gR3 has support for Usename/Password, X.509 Certificate, and SAML profiles for WS-Security 1.0. SAML provides the best fit for this use case since it allows for the identity in the windows environment to remain native, only relying on SAML when calling services on the OSB.


WCF also supports WS-Security SAML 1.1 Token Profile for WS-Security 1.0, so this seems like a good profile to use to meet the requirements, and therefore focus on. WCF requires a Security Token Service (STS) to generate the SAML Assertion. Microsoft provides a sample, but the sample needs to be modified to generate a SAML Assertion that OSB understands. Also, WCF favors symmetric bindings for WS-Security. This is probably because WS-Security Kerberos Token profile uses the Kerberos Session Id as the key. OSB 10gR3 only supports an asymmetric binding - X.509 certificates are used to sign the message and bind the SAML assertion to it.


On the OSB side, a pipeline needs to be configured to handle the WS-Security policies. The inbound policy is WS-Security SAML 1.1 Token Profile for WS-Security 1.0 and the outbound policy is that the message is signed by the service. This is because MSFT expects that an endpoint that is protected using WS-Security will secure the response as well. To support this OSB configuration, WLS Security realm needs to be configured to consume and validate SAML Assertions as well as configure Public/Private Key pairs and corresponding trust stores for the message signature operations dictated by the WS-Security policies.


The flow is that a WCF client calls the STS. The STS generates a SAML Assertion signed by the STS that contains the name of the user as the Subject. The SAML Assertion uses the sender-vouches confirmation method. The SAML Assertion is added to the WS-Security Header, and the message is signed by invoking service. The message is sent to OSB where the SAML Assertion is verified along with the message signature. Once the message is processed, the return message is signed by the OSB identity. The signature is validated by the WCF client to ensure that the message has not been tampered and was sent by the OSB.

Customizing the STS Sample to Work with OSB


The sample STS provided by Microsoft needs to be modified to work with OSB in this scenario. The sample STS has the following issues:



  • Sample STS needs to be modified to use X509RawCertificate format. OSB does not support SHA1Thumbprint
  • Sample STS needs to be modified to use Sender-Vouches confirmation method instead of Holder of Key.
  • Sample STS needs to be modified to use sign the assertion with the private key of the issuer, not the encrypted key. OSB does not the use of symmetric encrypted keys, only un-encrypted asymmetric keys.
  • Sample STS needs to be modified to include an AuthenticationStatement in the SAML Assertion. This is where OSB looks for the user's identity.
  • Sample STS needs to be modified to add a wsu:Id to the saml:Assertion, otherwise WCF cannot use it as an IssuedToken with an asymmetric binding


These issues can be addressed mainly by modifying the SamlTokenCreator.class

//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;

using System.Collections.Generic;
using System.Collections.ObjectModel;

using System.IdentityModel.Tokens;

using System.ServiceModel;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.Configuration;
using System.Security.Principal;
using Common;


namespace Microsoft.ServiceModel.Samples.Federation
{
public sealed class SamlTokenCreator
{
#region CreateSamlToken()
/// <summary>
/// Creates a SAML Token with the input parameters
/// </summary>
/// <param name="stsName">Name of the STS issuing the SAML Token</param>
/// <param name="proofToken">Associated Proof Token</param>
/// <param name="issuerToken">Associated Issuer Token</param>
/// <param name="proofKeyEncryptionToken">Token to encrypt the proof key with</param>
/// <param name="samlConditions">The Saml Conditions to be used in the construction of the SAML Token</param>
/// <param name="samlAttributes">The Saml Attributes to be used in the construction of the SAML Token</param>
/// <returns>A SAML Token</returns>
public static SamlSecurityToken CreateSamlToken(string stsName,
BinarySecretSecurityToken proofToken,
SecurityToken issuerToken,
SecurityToken proofKeyEncryptionToken,
SamlConditions samlConditions,
IEnumerable<SamlAttribute> samlAttributes)
{





// Create a security token reference to the issuer certificate
SecurityKeyIdentifierClause skic = issuerToken.CreateKeyIdentifierClause<X509RawDataKeyIdentifierClause>();
SecurityKeyIdentifier issuerKeyIdentifier = new SecurityKeyIdentifier(skic);

//Get the user
WindowsIdentity wi = ServiceSecurityContext.Current.WindowsIdentity;

// Create a SamlSubject
SamlSubject samlSubject = new SamlSubject(SamlConstants.UserNameNamespace,
SamlConstants.UserName,
wi.Name);
//Set the Confirmation method to Sender-Vouches
samlSubject.ConfirmationMethods.Add(SamlConstants.SenderVouches);

//Create the Authentication Statement
SamlAuthenticationStatement samlAuthStatement = new SamlAuthenticationStatement();
samlAuthStatement.SamlSubject = samlSubject;

// Put the SamlAttributeStatement into a list of SamlStatements
List<SamlStatement> samlSubjectStatements = new List<SamlStatement>();
samlSubjectStatements.Add(samlAuthStatement);

// Create a SigningCredentials instance from the key associated with the issuerToken.
SigningCredentials signingCredentials = new SigningCredentials(issuerToken.SecurityKeys[0],
SecurityAlgorithms.RsaSha1Signature,
SecurityAlgorithms.Sha1Digest,
issuerKeyIdentifier);


// Create the SamlAssertion
String assertionId = "_"+Guid.NewGuid().ToString();

SamlAssertion samlAssertion = new SamlAssertion(assertionId,
"uri:"+stsName.Replace(' ','_'),
DateTime.UtcNow,
samlConditions,
new SamlAdvice(),
samlSubjectStatements
);

//Wrap the SamlAssertion so that the wsu:Id can be added
CustomSamlAssertion customAssertion = new CustomSamlAssertion(samlAssertion);

// Set the SigningCredentials for the SamlAssertion
customAssertion.SigningCredentials = signingCredentials;


// Create a SamlSecurityToken from the SamlAssertion and return it
SamlSecurityToken st = new SamlSecurityToken(customAssertion);

return st;
}

#endregion

private SamlTokenCreator() { }

static X509Certificate2 LookupCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint)
{
X509Store store = null;
try
{
store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByThumbprint,
thumbprint, false);
if (certs.Count != 1)
{
throw new Exception(String.Format("FedUtil: Certificate {0} not found or more than one certificate found", thumbprint));
}
return (X509Certificate2)certs[0];
}
finally
{
if (store != null) store.Close();
}
}

}
}


This code above references another class - CustomSAMLAssertion.class. This class fixes the issue of the SAMLAssertion not having a wsu:Id

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Text;
using System.Xml;
using System.IO;

namespace Common
{
class CustomSamlAssertion: SamlAssertion
{


public CustomSamlAssertion(SamlAssertion theAssertion):
base(
theAssertion.AssertionId,
theAssertion.Issuer,theAssertion.IssueInstant,
theAssertion.Conditions,
theAssertion.Advice,
theAssertion.Statements)
{


}

public override void WriteXml(System.Xml.XmlDictionaryWriter writer, SamlSerializer samlSerializer, System.IdentityModel.Selectors.SecurityTokenSerializer keyInfoSerializer)
{
StringBuilder myBuilder = new StringBuilder();
XmlDictionaryWriter myWriter = XmlDictionaryWriter.CreateDictionaryWriter(XmlDictionaryWriter.Create(myBuilder));


base.WriteXml(myWriter, samlSerializer, keyInfoSerializer);

myWriter.Close();

String contents = myBuilder.ToString();

//contents = contents + "";


XmlDictionaryReader reader =
XmlDictionaryReader.CreateDictionaryReader(XmlDictionaryReader.Create(new StringReader(contents)));


StringBuilder myBuilder2 = new StringBuilder();
XmlDictionaryWriter myWriter2 = XmlDictionaryWriter.CreateDictionaryWriter(XmlDictionaryWriter.Create(myBuilder2));

try
{
while (reader.Read())
{

WriteShallowNode(reader, writer);


}
}
catch (Exception e)
{
Console.Out.WriteLine(e);
//writer.Flush();
//String contents2 = myBuilder2.ToString();

//throw e;

return;


}

//writer.Flush();

}

void WriteShallowNode(XmlReader reader, XmlWriter writer)
{

if (reader == null)
{

throw new ArgumentNullException("reader");

}

if (writer == null)
{

throw new ArgumentNullException("writer");

}



switch (reader.NodeType)
{

case XmlNodeType.Element:

//writer.WriteStartElement(reader.LocalName);

writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);

writer.WriteAttributes(reader, true);

if (reader.LocalName.Equals("Assertion")) {


writer.WriteAttributeString(
"wsu",
"Id",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
this.AssertionId);
}



if (reader.IsEmptyElement)
{

writer.WriteEndElement();

}

break;

case XmlNodeType.Text:

writer.WriteString(reader.Value);

break;

case XmlNodeType.Whitespace:

case XmlNodeType.SignificantWhitespace:

writer.WriteWhitespace(reader.Value);

break;

case XmlNodeType.CDATA:

writer.WriteCData(reader.Value);

break;

case XmlNodeType.EntityReference:

writer.WriteEntityRef(reader.Name);

break;

case XmlNodeType.XmlDeclaration:

break;


case XmlNodeType.ProcessingInstruction:

writer.WriteProcessingInstruction(reader.Name, reader.Value);

break;

case XmlNodeType.DocumentType:

writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);

break;

case XmlNodeType.Comment:

writer.WriteComment(reader.Value);

break;

case XmlNodeType.EndElement:

writer.WriteEndElement();

break;

}

}


}
}


Configuring the WCF Client


WCF supports a large number of authentication methods and profile bindings simply and easily. This is typically done by modifying the configuration file through the WCF Service Configuration Editor. Unfortunately, there is no way through configuration to set-up the client. This needs to be done programmatically. The WS-Policy that OSB uses is essentially as follows:

<?xml version="1.0"?>
<wsp:Policy
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
>
<sp:AsymmetricBinding>
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssX509V3Token10/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never">
<wsp:Policy>
<sp:WssX509V3Token10/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic256/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Lax/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:ProtectTokens/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedSupportingTokens>
<wsp:Policy>
<sp:SamlToken
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssSamlV11Token10/>
</wsp:Policy>
</sp:SamlToken>
</wsp:Policy>
</sp:SignedSupportingTokens>
<sp:Wss10>
<wsp:Policy>
<sp:MustSupportRefKeyIdentifier/>
<sp:MustSupportRefIssuerSerial/>
</wsp:Policy>
</sp:Wss10>
</wsp:Policy>


The translation between this policy and the WCF APIs is pretty straight forward with one exception - the SAML Token itself. In WCF, the SAML Token is retrieved from the STS, so the WCF client needs to be configured to communicate to it. In WCF, authentication from a token retrieved from an STS is called IssuedToken. All of this can be done programmatically through the WCF APIs. For simplicity sake, the creation of the custom AsymmetricSecurity binding can be encapsulated as a WCF Binding Element Extension. This allows for the inclusion of custom binding elements () inside of a custombinding.


<extensions>
<bindingElementExtensions>
<add name="osbsecurity" type="OSBWCFExtensions.OSBSecurityElement, OSBWCFExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=63fc46aa660659ca" />
</bindingElementExtensions>
</extensions>

<bindings>
<customBinding>
<binding name="HelloWorldServiceServiceSoapBinding">
<textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
messageVersion="Soap12" writeEncoding="utf-8">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
</textMessageEncoding>
<osbsecurity STSAddress="http://fedtest/FederationSample/HomeRealmSTS/STS.svc"/>
<httpsTransport manualAddressing="false" maxBufferPoolSize="524288"
maxReceivedMessageSize="165536" allowCookies="false" authenticationScheme="Anonymous"
bypassProxyOnLocal="false" hostNameComparisonMode="WeakWildcard"
keepAliveEnabled="true" maxBufferSize="165536" proxyAuthenticationScheme="Anonymous"
realm="" transferMode="Buffered" unsafeConnectionNtlmAuthentication="false"
useDefaultWebProxy="true" requireClientCertificate="true"/>



</binding>
</customBinding>

Inside of the OSBSecurityElement, the WCF API calls are made that create the proper binding for sending a SAML Assertion to OSB.

protected override System.ServiceModel.Channels.BindingElement CreateBindingElement()
{

//Retrieve the STS Address from the config
ConfigurationProperty stsConfig = this.Properties["STSAddress"];


//Set-up the Asymmetric binding with the recipient and initiator's parameters
//Keys are identified by the issuersSerial as required by the policy
//OSB does not support derived keys, so they are disabled
X509SecurityTokenParameters initiatorParams = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.AlwaysToRecipient);
initiatorParams.RequireDerivedKeys = false;


X509SecurityTokenParameters recipientParams = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.Never);
recipientParams.RequireDerivedKeys = false;

AsymmetricSecurityBindingElement security = new AsymmetricSecurityBindingElement(recipientParams, initiatorParams);


security.SecurityHeaderLayout = SecurityHeaderLayout.Lax;
security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
security.SetKeyDerivation(false);

//Configure the STS and the resulting SAML Assertion as a signed supporting token
WSHttpBinding stsBinding = new WSHttpBinding();

//This credential type is how the caller identifies themself to the STS
stsBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

IssuedSecurityTokenParameters issuedTokenParamters =
new IssuedSecurityTokenParameters("", new EndpointAddress((String)base["STSAddress"]), stsBinding);

issuedTokenParamters.RequireDerivedKeys = false;
issuedTokenParamters.ReferenceStyle = SecurityTokenReferenceStyle.Internal;
issuedTokenParamters.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
security.EndpointSupportingTokenParameters.Signed.Add(issuedTokenParamters);

//Set this to process the signature of the response
security.AllowSerializedSigningTokenOnReply = true;

return security;
}
}

By using the custom binding element extension, the client code remains unchanged:

HelloWorldClient client = new HelloWorldClient();
Console.Out.WriteLine(client.test1("WCF Client"));

Configuring OSB Domain's Security Domian


The inbound SAML processing requires the creation and configuration of a SAML Identity Asserter. For this scenario, the SAML V2 Identity Asserter should be used. It supports SAML 1.1 sender-vouches subject confirmation method. It needs to be configured with an asserting party that corresponds to the STS. Since the SAML Assertion is signed, OSB needs to be configured to trust the signer of the assertion. This can be done my adding the certificate authorities (CAs) that make up the STS's certificate chain to the list of trusted CAs. Which keystore to add them to depends of the trust mode that the OSB domain is running, but by default, these can be added to the cacerts keystore found in JRE_HOME/jre/lib/security.

In some scenarios, the identity being asserted by the SAML assertion can be trusted, and in others, the identity needs to be validated against some other authentication source - mainly Active Directory. OSB domain can be configured to support both. To trust the identity, a SAML Authentication Provider needs to be added to the realm. Make sure to configure it with an appropriate JAAS Control Flag. The simplest way to avoid any conflicts is to mark all of the authentication providers as OPTIONAL. Also, the asserting party configuration in the SAML Identity Asserter needs to be configured to Allow Virtual Users. Otherwise, the SAML Authentication Provider will not work. If "Allow Virtual Users" is not checked for the asserting party, then the security realm will try to validate the user against the authentication providers configured for the realm. The name that the STS above generates is of the form domain/username. In most cases, a custom username mapper will need to be written and configured on the SAML Identity Asserter to split off the domain portion of the name.

A PKI CredMapper needs to be configured so that OSB can generate digital signatures for outbound requests. The PKI CredMapper is configured to point to a Java Keystore. The identity of the OSB should be available in this keystore, and should be the same identity as the ServiceCert configured in the WCF client.

Configuring the OSB Pipeline


The OSB service needs to be configured to process the WS-Security header sent by WCF. The inbound request message needs to be configured with the SAML Token Profile 1.0 - Sender Vouches policy.
<wsp:Policy
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wssp="http://www.bea.com/wls90/security/policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wls="http://www.bea.com/wls90/security/policy/wsee#part"
wsu:Id="samlPolicy-sender-vouches-simple">
<wssp:Identity>
<wssp:SupportedTokens>
<wssp:SecurityToken TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-2004-01-saml-token-profile-1.0#SAMLAssertionID">
<wssp:Claims>
<wssp:ConfirmationMethod>sender-vouches</wssp:ConfirmationMethod>
</wssp:Claims>
</wssp:SecurityToken>
</wssp:SupportedTokens>
</wssp:Identity>
</wsp:Policy>

The OSB response needs be signed. This can be done by creating a Service Key Provider that points to the identity of the OSB, and then adding the predefined Sign.xml policy to the response operation.

References


The mapping of WCF configuration to WS-Policy and the security capabilities are nicely described

A description of why wsu:Id needs to be added to the SAML Assertion

A good discussion of a variation of this use case

The sample STS from Microsoft, which has extended to integrate with OSB

Saturday, August 8, 2009

When all you have is an STS, everything looks like a...

What is a reasonable use of a Security Token Service (STS)? Standards are very useful and powerful tools in enterprise architecture, but they have to be used to solve the right problems. WS-Trust, the standard that STS relies on is very flexible. Basically, you request a token and get at token back - you have a UsernameToken (username + password) and you get a SAML Assertion back.

So, this is useful when crossing security domains in federated models. For example, you need to call a 3rd party web-service and it requires a SAML assertion - call the STS, get the SAML Assertion and send it to the service. Simple enough. We can agree that for this type of use case, most web services clients can just generate the SAML assertion themselves, and sign the request - SAML sender-vouches. If the SAML assertion itself has to be signed then this can create complexity - requiring each service to have the private key of the issuer - so maybe, depending on the number of client applications that are required to federate, having a central service like an STS is preferred to having each client generate the SAML.

Another common use case for the STS generating a SAML assertion is attributued based authorization. The STS generates a SAML assertion containing the attributes required to access the service. This sounds good in practice, but how does the STS know what attributes are required? Are they published in the WSDL? Assuming that there was a standard way to do this, would services advertise what attributes are required to gain access? Not likely.

Instead, as in most federations, there needs to be some prior arrangement made between the service producer and consumer - you'll send me a SAML Assertion like this with these attributes. This means that the STS has to manage all of the meta-data for all of the partners. Is this practical? It might make more sense to just generate a SAML assertion with no attributes, and then have the service call-back to the "issuer" for more attributes as needed. The SAML protocol - SAML Attribute Query, with out WS-Trust or an STS, can be used to expose additional information to relying parties. There are definately scenarios where the relying party is not authorized to callback to the asserting domain, so in that case it might make sense to have the SAML Assertion contain a fixed set of common attributes. This generation could also be simplified by an STS.

As to not be accused of being an STS "hater", here's a scenario I've come across for a POC I'm working on that I actually like for an STS. In an online banking scenario, how the user authenticates (business card + PIN or personal card +PIN) determines which accounts they have access. Make a call to the STS - authenticate the user, and based on which authentication method they used, filter the accounts they access. Return the list of accounts in the SAML assertion. Use the accounts contained in the SAML assertion for personalization - I would also go to the system of record you authorizing transactions.

I guess the point is that WS-Trust/STS solves some good use cases, but it is not the only or best solution - neither is SAML or even WS-Security for that matter. In selecting standards for a project or an organization, consider the likely use cases and understand that simpler is almost always better.

Friday, July 24, 2009

Binding WS-SecureConversation Bootstrap Identity to WebLogic Server Identity

In WebLogic Server, when you configure a web service to use WS-SecureConversation, you have a number of choices for how to "bootstrap" the conversation. Besides calling a Security Token Service (STS), you can also send a WS-Trust RequestSecurityToken (RST) message to the endpoint, and receive a SecureConversationToken in return. This token is then typically used to derive keys to sign and encrypt subsequent messages. But, how should the initial exchange where the context is created be secured?

WebLogic Server gives a number of options including 1 and 2 way SSL, Basic Authentication, and UsernameToken. With the exception of 1 way SSL, all of the other policies require the web service consumer to provide an identity. This identity is validated by WebLogic Server in the normal way. The web service can then also have a policy that requires another identity when invoking the service. For example, use 2 way SSL to bootstrap the secure conversation and then SAML to provide identity for the actual service. This makes a lot of sense if you want a separate bootstrap identity, but what if you don't? What if you want to use a single identity to both bootstrap the conversation and identify the consumer? Is there a way to do it without simply sending the same token in both the bootstrap request and the subsequent messages?

This was the question posed to me recently by a customer. There is nothing in the WS-SecureConversation standard that says the bootstrap identity should be preserved, but the requirement does seem pretty reasonable. Also, in discussing this issue with a colleague he made the observation that WS-SecureConversation is like "SSL for Messages". The SSL standard does not require that the original client certificate is passed for identity on every request, but many, if not all implementations do. So, following that analogy, I set off this week to try to get this type of functionality working inside of WLS.

So, after trying what felt like every conceivable approach, and a lot of late nights, this is how you can do it. There are a couple things that you have to know about the bootstrapping process and the WLS web services stack. The first is that during the bootstrapping process a session is created, but that its not associated with the bootstrap user. The second is that the WLS web services client will send the JSESSIONID cookie on subsequent requests. The third is that a HTTP based web service is really two different resources inside of WLS - one of type <url> and one of type <webservices> . The idea is to capture the session, get it associated with Subject created by the authentication of the bootstrap identity, and then push that Subject onto the Servlet stack, making it available for the web-service. As long as the client sends the same JSESSIONID cookie, the bootstrap identity will be preserved.

The solution leverages a SessionEventListener to capture the HttpSession that is created during the bootstrapping process. The session is stored in a ThreadLocal.

public class SessionListener implements HttpSessionListener {
private HttpSession session = null;

public void sessionCreated(HttpSessionEvent event) {
session = event.getSession();
WSCSubjectThreadLocal.getWSCSubjectThreadLocal().set(session);
}

public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
}
}


With the session stored in the ThreadLocal, the next thing to happen is to capture the bootstrap identity in the form of the Subject, and add it to the session. This can be done through a custom AuthenticationProvider, but this a very unusual provider. Its required, its configured to be the last provider in the realm, and its only purpose is in the commit method to capture the subject.

public boolean commit() {

HttpSession session =
WSCSubjectThreadLocal.getWSCSubjectThreadLocal().get();

if (session!=null) {
session.setAttribute("wsscSubject",this.subject);
WSCSubjectThreadLocal.getWSCSubjectThreadLocal().remove();
}
return true;
}


So, at this point the bootstrapping is complete. The Subject is stored as an attribute in the session and the client is passed the JSESSIONID cookie. Assuming that the client sends the cookie in the next request, all that is left to do is to push the Subject on to the stack. To accomplish this, I used a custom AuthorizationProvider. This provider is only looking for requests, and always returns a PEMIT. Its only purpose if to push the Subject onto the stack.

public Result isAccessAllowed(
Subject subject, Map map, Resource resource,
ContextHandler contextHandler,Direction direction) {

if (resource.getType().equals("")) {

HttpServletRequest request = (HttpServletRequest)contextHandler.getValue("HttpServletRequest");

if (subject.getPrincipals().size()==0) {

HttpSession session = request.getSession();

Subject theWSCSubject = (Subject)session.getAttribute("wsscSubject");

if (theWSCSubject!=null) { ServletAuthentication.runAs(theWSCSubject,request);
}
}
}
return Result.PERMIT;
}


The application needs to be deployed with the Custom Roles and Policies. If the URL is protected in the deployment descriptor, the bootstrapping process will fail - the user is not authorized. Its also worth noting the limitation that the identity is tied to the session and the client is responsible for sending the session in the cookie in the transport. This means that a single client won't be able to maintain two conversations concurrently with the same server. Also, since the identity is not included in the message, this solution is best suited for single party operations - client calls WebLogic Server, and WLS processes the message. Although, since there is a real identity inside of WLS, the identity can be pretty easily propagated using the CredentialMappers (PKI/SAML) of WLS.


Saturday, July 18, 2009

WS-SecureConversation

WebLogic 11g (10.3.1) has support for WS-SecureConversation. What is WS-SecureConversation? According to Wikipedia, not much more than a specification from IBM, MSFT and others. Recently, I've had a couple of customers asking about WS-SecureConversation and how WLS can support it so I wanted to take a little time to discuss what WS-SecureConversation is and explain how to configure WS-SecureConversation on WLS 11g.

WS-SecureConversation enables the creation of a SecurityContext between a web-service producer and consumer. The SecurityContext is essentially a shared key. The SecurityContext is created first, and then the message exchange begins. WS-SecureConversation uses the WS-Trust specification to establish the SecurityContext. One common approach is to use WS-Trust to communicate with a SecurityTokenService (STS). Another is for the consumer and the producer to negotiate the SecurityContext directly. In WebLogic Server, this process is called "bootstrapping". The difference among the policies is just how the two parties establish trust so that they can securely exchange the shared key. Often, the shared key for the security context is used to calculate DerivedKey. Using the concept of DerivedKeys it is common to have one key used for signing the message and another to encrypt the message.

The sample client illustrates how to configure a client application to use WS-SecureConversation. The details on the setting up the server are not obvious, so I'll cover them here.

You'll need a client public/private key-pair and a server public/private key-pair. For demos, I'll just use utils.CertGen and utils.ImportPrivateKey. You'll need both of them in their own java keystore, as well as the certificate in PEM format. Once the SecurityContext is established, messages will be secured with the DerivedKeys, but to establish the SecurityContext, X.509 certificates and either WS-Security or SSL is used to exchange the keys. This is why you need the keys.

In order to configure the server to use DerivedKeys and the SecurityContext, you need to configure a domain level webservice configuration, and set-up the appropriate certificates and credential-providers (DerivedKeys and SecureConversation). This can be very tedious manual process. Fortunately, there is a sample that has a WLST script that does it for you.

WL_HOME\samples\server\examples\src\examples\webservices\wsrm_security\configWss_Service.py

Run the script as follows:

java weblogic.WLST weblogic welcome1 localhost 7001 serverkeystore.jks serverkeystorepass serveralias serverkeypassword

You'll also need to create a CertPath provider, mark it as the default builder, and then configure both the client certificate and server certificates as trusted.

Finally, deploy a web-service protected by the bootstrapping policy, for example:

@Policy(uri = "policy:Wssp1.2-2007-Wssc1.3-Bootstrap-Wss1.1.xml")

This will sign the message with the DerivedKeys. If you want the body encrypted as well, use the following policies:

@Policies(
{@Policy(uri = "policy:Wssp1.2-2007-Wssc1.3-Bootstrap-Wss1.1.xml"),
@Policy(uri = "policy:Wssp1.2-2007-EncryptBody.xml")}
)

You'll need to modify the sample to use your generated stubs. Make sure that you use JAX-RPC web-service stub.

This is probably the first of a few posts of WS-SecureConversation. I'll definitely need to cover the topics of WS-SecureConversation and WS-Trust, WS-SecureConversation and WS-ReliableMessaging. If there are more topics of interest, let me know.

Tuesday, June 2, 2009

SAML and OSB 10gR3 - Federated Authorization

I recently spent a lot of time with a customer on this use case, and it also appeared on the Oracle Forums, so I thought it would be worth posting some of the details.

The use case is basically processing a SAML assertion in Oracle Service Bus for the purpose of not only authentication, but authorization as well.

In most of these cases, the client first makes a call to an STS (Secure Token Service) and gets a SAML assertion that contains some claims. These claims could be SAML Authorization statements or attributes of the user's profile or actions that the user can perform on the service. Regardless, they show up at the OSB as Attribute Statements in the SAML Assertion.

The question for OSB, is how to process this information and use it for authorization?

In this post, I'll describe what can be done simply using OOTB capabilities. The SAML Identity Asserter is the component inside of OSB that processes the SAML Assertion. You can configure the SAML Identity Asserter to process the group attributes. What are the group attributes? WLS and therefore OSB has the ability to get groups for the user from a SAML Assertion.

So, a SAML Assertion like this:

<Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion" AssertionID="de7790da1578ae713b9fbe9399a87e3d" IssueInstant="2009-05-27T23:54:44.219Z" Issuer="http://fedtest" MajorVersion="1" MinorVersion="1"><Conditions NotBefore="2009-05-27T23:54:44.219Z" NotOnOrAfter="2009-05-27T23:56:44.219Z"></Conditions><AuthenticationStatement AuthenticationInstant="2009-05-27T23:54:44.219Z" AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified"><Subject>
<NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">josh</NameIdentifier>
<SubjectConfirmation><ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:sender-vouches</ConfirmationMethod></SubjectConfirmation></Subject>
<Attribute AttributeName="Groups"AttributeNamespace="urn:bea:security:saml:groups">
<AttributeValue>group1</AttributeValue><AttributeValue>group2</AttributeValue>
</Attribute></AttributeStatement></Assertion>


Then you'll wind-up with a Subject named josh who has two WLSGroup principals group1 and group2.

At this point, you just need to figure out how to use these groups in authorization. You could use the role mapper to map the groups to roles, and then assign those roles policies granting them access to OSB Proxy services. As an alternative, you could just skip roles, and grant access to the web-services to those groups directly.

The groups themselves don't have to exist in any directory. The values above of group1 and group2 could easily replaced with priv#trade or role#foo.
In fact the user doesn't have to exist either - its a virtual user. Since the SAML Assertion is valid, OSB will just trust what is in the assertion.

This is not the only federated authorization model that you can use with OSB, but hopefully this post shows what is simply there OOTB with OSB. There are obviously more elaborate scenarios like one-time-identifiers or more elaborate claims that would require some custom work. One of the strong points of OSB is that its built on top of WLS Security, and I've used that framework to solve a ton of use-cases. I'm sure there is a way through configuration and/or custom providers that these use cases could be met. If there's interest, I'd be happy to explore the specifics here.