Friday, June 14, 2013
Part 4: Kerberos Authentication, RBAC and SAML identity propagation in OAG
In the first post we introduced the use case and talked about the Kerberos authentication part.
In the second post we talked about Role Based Access Control.
In the third post, we described how to propagate the authenticated user through a SAML token and also covered OWSM configuration in OSB and SOA.
In this post we talk about the client, how we actually submit a Kerberos token along with our request to OAG.
In the first post, we said (verbatim):
“Perhaps the most challenging part was the test client. Supporting Kerberos was a must. Our first and natural attempt was OAG’s API Explorer (formerly OEG Service Explorer). After numerous attempts, configuration changes everywhere, we came to the the conclusion that it simply does not support the SPNEGO protocol properly. In summary, it seems that it doesn’t honor the second leg of the protocol, i.e., after receiving a “WWW-Authenticate: Negotiate” HTTP header in the response, it does not resubmit the request along with the Kerberos token. And we also did not find a way to bypass SPNEGO and submit the Kerberos token directly. The free version of SOAP UI also failed.
Fortunately, via Prasad’s blog post, we came to know a .Net-based tool called WCFStorm-lite, dead simple, that just works. However, another requirement for this exercise was to show test automation best practices. So the client tool should ideally have scripts capabilities, allowing for the client integration into an ANT build script, for example. WCFStorm-lite has some scripts capabilities, but we did not go any further in looking at them, because almost at the same time we got to know Apache CXF’s support for Kerberos.
That basically put an end to our research. What could be better than simply writing a piece of java code? At the heart of Apache CXF’s framework are what they call interceptors, which are basically web services handlers. An inbound interceptor is attached to a web service and processes the message before service invocation. An outbound interceptor is attached to a web service proxy and process the message as it leaves the client. An out-of-the-box Kerberos interceptor interfaces with the KDC, gets a Kerberos token and attaches it to the request before sending it out to the web service (in our use case, a web service exposed by OAG).”
Very well, let’s talk about how to configure a Kerberos Outbound Interceptor in Apache CXF.
Thursday, June 13, 2013
Part 3: Kerberos Authentication, RBAC and SAML identity propagation in OAG
This post is the third one of a series by Andre Correa and Paulo Pereira on OAG (Oracle API Gateway).
In the first post we introduced the use case and talked about the Kerberos authentication part.
In the second post we talked about Role Based Access Control.
In this one we describe how to build a SAML token out of the authenticated user and sign it so it properly interoperates with an OWSM server-side policy applied to OSB proxy service. We also cover the necessary OWSM configuration in OSB and SOA. As a reminder, a typical request in our exercise flows through OTD -> OAG -> OTD -> OSB -> OTD -> SOA -> Stub server, and we're required to keep the user identity all the way up to the Stub server.
Now we're specifically talking about the third policy in our overall circuit:
'Add SAML token policy' is as simple as:
We first add the SAML token (using the Insert SAML Authentication Assertion filter), then we sign it (using the XML Signature Generation filter). Let's look at each filter in detail.
Wednesday, March 13, 2013
Part 2: Kerberos Authentication, RBAC and SAML identity propagation in OAG
This post is the second one of a series by Andre Correa and Paulo Pereira on OAG (Oracle API Gateway).
The first post is found at http://fusionsecurity.blogspot.com.br/2013/03/part1-kerberos-authentication-rbac-and.html. Check it out for use case background and the Kerberos authentication part.
As mentioned, one of the requirements in our exercise was to authorize the user against a ROLE X URI matrix, called “Authorization Matrix”. In this post we’re looking at the second policy (Call ‘Perform Authorization’) in the overall flow:
Basically, “Perform Authorization” had to:
a. Obtain the authenticated user (authenticated by Kerberos);
b. Lookup the groups memberships in Active Directory;
c. For the requested URI, query a Database for the authorized roles for that URI in particular;
d. Check if any of the user groups (obtained from AD) is in the list returned by the DB query;
e. Authorize the user in case the check on the previous steps passes.
Tuesday, March 12, 2013
Part 1: Kerberos Authentication, RBAC and SAML identity propagation in OAG
This post is the first one of a series by Andre Correa and Paulo Pereira on OAG (Oracle API Gateway).
Throughout the series, we are going to talk about Kerberos authentication, Role Based Access Control (RBAC) and SAML identity propagation in OAG 11g, formerly known as OEG (Oracle Enterprise Gateway). What follows has been implemented as part of a larger exercise involving the SOA suite, OSB, OTD (Oracle Traffic Director) and the Exalogic platform. The kind of architecture presented here can be used as general guidance, but that may not apply to your use case scenarios. We will also briefly touch on OWSM policies that were applied to OSB and SOA composite.
The use case is about enabling end users to place orders. As you might think, there are quite a few 3rd-party systems to interact with in order to have the order fulfilled and the product provisioned to the end user. SOA to the rescue.
Security Requirements
At the end of our exercise, the policy we built in OAG is expressed as the following circuit, where we can clearly see authentication, authorization and token switch. We expand the contents of each filter/policy as we go. In this post, we focus on the Kerberos Service filter and how we enable the policy for the service we want to protect.
Deployment Architecture
Monday, February 18, 2013
Part 3: OAM11g WNA Identity Store Considerations and Configurations
This is the final post of a three part series. In "Part 1: Under the Covers of OAM11g WNA integration with Multiple AD Forests", I covered the flow of how WNA works and what was going on behind the scenes, and in "Part 2: How to Configure OAM11g WNA for Multiple AD Forests", I went into detail on how to configure WNA. In this final post I am going to go over what I think would be two of the most common scenarios that the OAM11g Identity Store would be used for WNA, and how it impacts the Kerberos authentication module configurations.
Thursday, February 14, 2013
Part 2: How to Configure OAM11g WNA for Multiple AD Forests
This is the second post of a three part series. In "Part 1: Under the Covers of OAM11g WNA integration with Multiple AD Forests", I covered the flow of how WNA works and what was going on behind the scenes. This article will cover the technical details on how to implement WNA in a way that will support multiple Active Directory Forests that either have no transient trust between them, or even all trusted; in either case this will work for you.
Before we get into the details on how to setup WNA for multi Active Directory domains I just want to point out that I will use a straw man of three Active Directory KDC servers so you can understand any additional steps needed to support more than one KDC. However, this would also work for as few as one domain, or more than three domains. All that is needed is to simply extrapolate the steps to fit your requirements; I will be sure to comment where necessary.
Tuesday, February 12, 2013
Part 1: Under the Covers of OAM11g WNA integration with Multiple AD Forests
Monday, July 18, 2011
5 Minutes or less: Kerberos
Ticket cache: FILE:cjohnson.cache
Default principal: cjohnson@ATEAMDEMO.COM
Valid starting Expires Service principal
07/18/11 07:51:14 07/18/11 17:51:19 krbtgt/ATEAMDEMO.COM@ATEAMDEMO.COM
renew until 07/19/11 07:51:14, Flags: RIA
Most of our posts here are pretty long and can take a while to digest. This one is intended to be read in 5 minutes or less. If you'd like to see more "5 minutes or less" posts please let us know in the comments below. If you DON'T want to see more of them please let us know that too. Note: we may not publish the comments, but we promise to read them all.
Thursday, January 27, 2011
How does Kerberos actually work in the HTTP world?
Monday, January 17, 2011
The (Windows) Natives Are Restless
OAM 11g has the ability to do Windows Native Authentication to give a Windows client desktop SSO to the OAM-protected application. This was possible in OAM 10g as well, but it required an IIS server to do the heavy lifting of getting the Kerberos ticket and authenticating the user. In 11g, Oracle does not require IIS to accomplish desktop SSO. WebLogic also had this capability by using its SPNEGO Identity Asserter, but this approach gives one SSO to any other OAM-protected application as well. The documentation is in chapter 7 of the Integration Guide.
- Select Tools, Internet Options.
- Select the Security tab.
- Ensure that your WebGate-protected OHS site is in the list of trusted “Sites”
- Select Local intranet and click Custom Level....
- In the Security Settings dialog box, scroll to the User Authentication section.
- Select “Automatic logon only in Intranet zone”.
- Click OK.
- Select the Advanced tab.
- Scroll to the Security section.
- Make sure that Enable Integrated Windows Authentication option is checked and click OK.
- If this option was not checked, restart the client.
Authorization: Negotiate
Tuesday, August 17, 2010
Kerberos and WebLogic Server - decisions, decisions
We have an app deployed in WebLogic and we want users that are in the domain to be able to access that app without being prompted for credentials.
- Use WebLogic's SPNEGO Identity Asserter to have WebLogic verify the Kerberos ticket
- Use IIS to do the Kerberos bits and then use a simple HTTP header Identity Asserter to get the identity into WebLogic
- Use Oracle Access Manager
Wednesday, June 2, 2010
Oracle Access Manager and Kerberos (/Integrated Windows Authentication) with fallback to HTML forms
- a Windows domain
- a server in the domain with an IIS web server and a Web Gate installed/configured
- a workstation in the domain (call this one workstation1)
- workstation NOT in the domain (call this one workstation2)
- using IIS' 401 error page
- using XmlHttpRequest and XDomainRequest
- using Flash
URL url = new URL("http://testmachine.testdomain.com/IWAProtected/");
URLConnection urlConn = url.openConnection();
urlConn.setAllowUserInteraction(false);
urlConn.setUseCaches(false);
DataInputStream dis = new DataInputStream(urlConn.getInputStream());
data = new String();
String s;
while ((s = dis.readLine()) != null) {
System.out.println("Read data" + s);
data += s + "\n";
}
dis.close();Make /IWAProtected/ protected by IIS' Integrated Windows Authentication, put that code into an Applet and add the Applet to an HTML page elsewhere on your site and you'll be able to tell whether the user is logged into the domain or not.Sunday, March 21, 2010
Oracle Service Bus and Kerberos
Friday, February 5, 2010
Testing your WebLogic/Kerberos setup
I have a virtual environment with two machines, one is a KDC and one is just a WebLogic server. I setup WebLogic to do Kerberos, following the directions in the the documentation and could not figure out what I was doing wrong.
Whenever I accessed WebLogic I kept seeing the header "Authorization: Negotiate TlRMTVNTUAAB..." with only a short string as the token. If you base64 decode that string you see that it starts with NTLMSSP. In other words most definitely NOT a Kerberos token.
It looks like IE6 and IE 8 on Windows 2003 figures out that it's talking to the local machine (i.e. localhost) even if you use the fully qualified domain name!
Thursday, February 4, 2010
Kerberos and WebLogic Server on Windows step-by-step
I got a frantic call late last week asking for help getting WebLogic and Kerberos working. WebLogic would be deployed on Windows but, unlike in my previous post, this customer wanted IE to talk directly to WebLogic with no IIS server in between.
Easy enough, right? It is, but there are some nuances.
In this environment the desktop and web server machines are both in the same Windows domain and thus also in the same Kerberos Domain. It's important to note that in my case the machine is part of the domain, but it doesn't actually have to be in the domain to get configuration working.
- You are using WebLogic with JDK 1.6 (either JRockit or the Sun JDK)
- You have the Active Directory domain created
- You have downloaded and installed WebLogic and may have created a domain
- You are frustrated because the docs are confusing and despite working on this for hours you still can't get Kerberos authentication working
dn: CN=WEBSERVER,CN=Computers,DC=kerbtest,DC=com
servicePrincipalName: HOST/WEBSERVER
servicePrincipalName: HOST/webserver.kerbtest.com
servicePrincipalName: HTTP/webserver
servicePrincipalName: HTTP/webserver.kerbtest.com
C:\>setspn -d HTTP/webserver webserver
Unregistering ServicePrincipalNames for CN=WEBSERVER,CN=Computers,DC=kerbtest,DC=com
HTTP/webserver
Updated object
C:\>setspn -d HTTP/webserver.kerbtest.com webserver
Unregistering ServicePrincipalNames for CN=WEBSERVER,CN=Computers,DC=kerbtest,DC=com
HTTP/webserver.kerbtest.com
Updated object
C:\>setspn -a HTTP/webserver webuser
Registering ServicePrincipalNames for CN=web user,CN=Users,DC=kerbtest,DC=com
HTTP/webserver
Updated object
C:\>setspn -a HTTP/webserver.kerbtest.com webuser
Registering ServicePrincipalNames for CN=web user,CN=Users,DC=kerbtest,DC=com
HTTP/webserver.kerbtest.com
Updated object
Install WebLogic as normal.
Create a new WebLogic domain using the Config Wizard.
Make a backup of config/config.xml. Don't ask, just do it!
Open the WebLogic console (http://localhost:7001/console by default)
In the left Domain Structure navigation menu click on Security Realms, then click on myrealm.
the key to look for is WWW-Authenticate: Negotiate. If you see that the Negotiate Identity Asserter is running and you can go ahead and finish up the Kerberos setup.HTTP/1.1 401 Unauthorized
Date: Thu, 04 Feb 2010 21:44:10 GMT
Content-Length: 1518
Content-Type: text/html
WWW-Authenticate: Negotiate
[libdefaults]
default_realm = KERBTEST.COM
ticket_lifetime = 600
[realms]
KERBTEST.COM = {
kdc = 10.99.2.181
admin_server = testmachine
default_domain = KERBTEST.COM
}
[domain_realm]
.kerbtest.com = KERBTEST.COM
[appdefaults]
autologin = true
forward = true
forwardable = true
encrypt = true
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
principal="webuser@KERBTEST.COM"
useKeyTab=true
keyTab=keytab
storeKey=true
debug=true;
};
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required
principal="webuser@KERBTEST.COM"
useKeyTab=true
keyTab=keytab
storeKey=true
debug=true;
};
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule required
principal="webuser@KERBTEST.COM"
useKeyTab=true
keyTab=keytab
storeKey=true
debug=true;
};C:\Oracle\Middleware\user_projects\domains\base_domain>java.exe -Dsun.security.krb5.debug=true sun.security.krb5.internal.tools.Ktab -k keytab -a webuser@KERBTEST.COM
Password for webuser@KERBTEST.COM:abcd1234
Config name: C:\WINDOWS\krb5.ini
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 3 1 23 16 17.
>>> KeyTabEntry: key tab entry size is 46
>>> KeyTabEntry: key tab entry size is 46
>>> KeyTabEntry: key tab entry size is 54
>>> KeyTabEntry: key tab entry size is 62
>>> KeyTabEntry: key tab entry size is 54
Done!
Service key for webuser@KERBTEST.COM is saved in keytab
C:\Oracle\Middleware\user_projects\domains\base_domain>java.exe -Dsun.security.krb5.debug=true sun.security.krb5.internal.tools.Kinit -k -t keytab webuser@KERBTEST.COM
>>>KinitOptions cache name is C:\Documents and Settings\administrator.KERBTEST\krb5cc_administrator
Principal is webuser@KERBTEST.COM
>>> Kinit using keytab
>>> Kinit keytab file name: keytab
>>> KeyTabInputStream, readName(): KERBTEST.COM
>>> KeyTabInputStream, readName(): webuser
>>> KeyTab: load() entry length: 46; type: 3
>>> KeyTabInputStream, readName(): KERBTEST.COM
>>> KeyTabInputStream, readName(): webuser
>>> KeyTab: load() entry length: 46; type: 1
>>> KeyTabInputStream, readName(): KERBTEST.COM
>>> KeyTabInputStream, readName(): webuser
>>> KeyTab: load() entry length: 54; type: 23
>>> KeyTabInputStream, readName(): KERBTEST.COM
>>> KeyTabInputStream, readName(): webuser
>>> KeyTab: load() entry length: 62; type: 16
>>> KeyTabInputStream, readName(): KERBTEST.COM
>>> KeyTabInputStream, readName(): webuser
>>> KeyTab: load() entry length: 54; type: 17
Added key: 17version: 1
Added key: 16version: 1
Added key: 23version: 1
Added key: 1version: 1
Added key: 3version: 1
Ordering keys wrt default_tkt_enctypes list
Config name: C:\WINDOWS\krb5.ini
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 3 1 23 16 17.
>>> Kinit realm name is KERBTEST.COM
>>> Creating KrbAsReq
>>> KrbKdcReq local addresses for webserver are:
webserver/10.99.2.133
IPv4 address
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 3 1 23 16 17.
>>> KrbAsReq calling createMessage
>>> KrbAsReq in createMessage
>>> Kinit: sending as_req to realm KERBTEST.COM
>>> KrbKdcReq send: kdc=10.99.2.181 UDP:88, timeout=30000, number of retries =3,
#bytes=169
>>> KDCCommunication: kdc=10.99.2.181 UDP:88, timeout=30000,Attempt =1, #bytes=1
69
>>> KrbKdcReq send: #bytes read=210
>>> KrbKdcReq send: #bytes read=210
>>> reading response from kdc
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
sTime is Sun Feb 07 14:40:09 EST 2010 1265571609000
suSec is 135477
error code is 25
error Message is Additional pre-authentication required
realm is KERBTEST.COM
sname is krbtgt/KERBTEST.COM
eData provided.
msgType is 30
>>>Pre-Authentication Data:
PA-DATA type = 11
PA-ETYPE-INFO etype = 23
>>>Pre-Authentication Data:
PA-DATA type = 2
PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
PA-DATA type = 15
Kinit: PREAUTH FAILED/REQ, re-send AS-REQ
>>>KrbAsReq salt is KERBTEST.COMwebuser
Pre-Authenticaton: find key for etype = 23
AS-REQ: Add PA_ENC_TIMESTAMP now
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbAsReq calling createMessage
>>> KrbAsReq in createMessage
>>> Kinit: sending as_req to realm KERBTEST.COM
>>> KrbKdcReq send: kdc=10.99.2.181 UDP:88, timeout=30000, number of retries =3,
#bytes=240
>>> KDCCommunication: kdc=10.99.2.181 UDP:88, timeout=30000,Attempt =1, #bytes=2
40
>>> KrbKdcReq send: #bytes read=1243
>>> KrbKdcReq send: #bytes read=1243
>>> reading response from kdc
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbAsRep cons in KrbAsReq.getReply webuser
New ticket is stored in cache file C:\Documents and Settings\administrator.KERBTEST\krb5cc_administrator
echo starting weblogic with Java version:
%JAVA_HOME%\bin\java %JAVA_VM% -version
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dsun.security.krb5.debug=true -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=krb5Login.conf -Djava.security.krb5.realm=KERBTEST.COM -Djava.security.krb5.kdc=testmachine.kerbtest.com
Monday, January 25, 2010
Integrating Oracle Access Manager with Kerberos authentication with fallback to an HTML form
We often see customers wanting authenticate users to Oracle Access Manager (OAM) with Kerberos, and fall back to HTML forms if the Kerberos authentication fails. There's an easy way to set this up, but it's not obvious how to do it.
I'm just going to describe the flow here, but that should be enough to help you figure out how to do the actual knob turning yourself. If you run into trouble feel free to ask a question here.
- user accesses http://somemachine.mydomain.com/protected/
- OAM redirects the user to http://trustedmachine.mydomain.com/oamauthenticator/
- on trustedmachine the virtual directory /oamauthenticator/ is protected by IIS' built in Kerberos functionality, and only Kerberos. In other words you should uncheck the Basic, Digest and Anonymous checkboxes in the IIS console.
If the user's workstation is in the domain and they are signed in then their identity will be passed to IIS via Kerberos. If the user's worksation is not in the domain or something goes wrong they will be presented with the 401 error page (see below) - the OAM WebGate will consume the identity from IIS and pass that identity to the Access Server
- the OAM Access Server will search LDAP for the user
- once the user's OAM session has been established they will be redirected back to http://somemachine.mydomain.com/protected/ along with the ObSSO cookie.
alternative flow for workstations not in a domain or if Kerberos fails:
In step 3 if the Kerberos authentication fails IIS will send the HTML file configured as the 401 page
You can create a plain HTML page that
- contains a conventional OAM login form - prompting the user for their username and password.
- submits to an URL on trustedmachine
- that URL will be associated with a conventional OAM HTML forms login authentication scheme
When the user fills in the form and hits submit:
- the credentials will be passed to the Access Server
- the Access Server will search LDAP for the user and bind to the directory to authenticate them
- if the credentials are correct the user will get an OAM session cookie
- the user will be redirected back to the original resource as in the normal flow above.
The only real trick here is using the custom error page in IIS. Remember that you have to use a plain HTML file for that file - you can't use an ASP/ASPX. That also means that you have to do anything dynamic in JavaScript.
Thursday, October 1, 2009
Calling Oracle Service Bus from MSFT WCF Client Using an STS
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 (
<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
