Showing posts with label kerberos. Show all posts
Showing posts with label kerberos. Show all posts

Friday, June 14, 2013

Part 4: Kerberos Authentication, RBAC and SAML identity propagation in OAG

This post is the fourth and last post 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 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:

KerberosPolicy

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

Provide a security shell around SOA and channel each and every request through OAG. The classic model of perimeter defense. As the applications used by end users are Kerberos enabled, the customer wanted to see OAG authenticating Kerberos tokens generated by Active Directory’s KDC (Key Distribution Center). After authentication, we were asked to authorize the user based on a Security Matrix (a relation of groups and URIs) kept in an Oracle database. Finally, with the user properly authenticated and authorized, we should forget Kerberos and instead propagate a SAML token to the SOA platform. This identity should then be preserved all the way to downstream 3rd-party systems.

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.

KerberosPolicy


Deployment Architecture

All Oracle FMW components (as well as OAG) were deployed for HA on a 4-node 1/8 Exalogic rack, as per the following diagram.

Monday, February 18, 2013

Part 3: OAM11g WNA Identity Store Considerations and Configurations

This post is part of a larger series on Oracle Access Manager 11g called Oracle Access Manager Academy. An index to the entire series with links to each of the separate posts is available.

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 post is part of a larger series on Oracle Access Manager 11g called Oracle Access Manager Academy.  An index to the entire series with links to each of the separate posts is available.

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

This post is part of a larger series on Oracle Access Manager 11g called Oracle Access Manager Academy. An index to the entire series with links to each of the separate posts is available.

This is the first post of a three part series that expands on a great article Matt wrote --- “The (Windows) Natives Are Restless”. Matt’s article covered some configurations, browser settings, and some examples of role mapping, but I want to dive into this whole WNA solution a lot more. So Part 1 will include just what the title eludes to, Under the Covers of the WNA integration with Multiple Active Directory Forests, then Part 2 will cover the details of the WNA configuration to make it work against multiple untrusted or trusted domains, and finally in Part 3) some highlights on leveraging OVD11g to pull it all together and make sure WNA can find the correct user across multiple forests.

Monday, July 18, 2011

5 Minutes or less: Kerberos

Every time I talk to someone about Kerberos I need to take a few minutes to go through the concepts. This post is intended to just write down what I usually say and draw with a white board.

If you want to know more about Kerberos there's a metric ton of info out there on the internet. I'll leave it to you to go read up. For those of you that are experts on Kerberos please note that I'm going to simplify some stuff and gloss over a few important things; don't judge me for that - I'm just trying to make this all easy to understand!

A Kerberos Domain can be thought of as an island of single sign-on. Anything in the domain can authenticate securely to anything else in the domain without ever transmitting a clear text password over the network.

Everything in Kerberos is a Principal - machines, services and even users. A Principal is identified by a simple string called a Service Principal Name or SPN in one of two forms - either PROTOCOL/hostname for services (e.g. HTTP/www.mydomain.com for a web server) or username@DOMAIN for users (e.g. cjohnson@ATEAMDEMO.COM). The case of the string is important; the Protocol and domain name are always in CAPITAL LETTERS and the hostname and username are always in lower case.

There's a service on the network called a Key Distribution Center, abbreviated to KDC. The KDC has a list of every user and every service on the network and has a secret keys for each and every one of those Principals.

When a Service enrolls with the KDS the KDC generates a secret key for the service. That secret key is stored in two places - on the KDC and by the service (usually in a file).

When a user first logs in they use their password to the KDC prove their identity and get something called a Ticket Granting Ticket, abbreviated to TGT. Note that the password is never actually sent over the wire.

When a user wants to authenticate to a Service they go to the KDC and use their TGT to ask for a Service Ticket (ST) to talk to that service. The user asks for a ST for a particular service identified by the service's Service Principal Name (PROTOCOL/hostname). Note that, as usual, the user doesn't actually send the TGT to the KDC. Instead the client uses the TGT to prove its identity to the KDC, just as it did with the password.

In order to construct a Service Ticket the KDC takes the username, the service's SPN, a timestamp and a couple of other bits of information, puts them all together in a particular way and then encrypts that data with the Service's secret key and adds on some unencrypted data describing the ticket. Remember that only two things know that secret key - the KDC and the Service itself.

The KDC then securely returns the ST to the user.

Finally, the user sends their service ticket to the Service. To verify the Service Ticket the Service takes the encrypted ticket and tries to decrypt it using the secret key it got when it enrolled with the KDC. If the the ST decrypts correctly with that key then the service knows that the user was authenticated by the KDC; all the that remains is to check the timestamp and other information to make sure that the ticket isn't expired. If all of that checks out then the user is considered authenticated.

You'll note that the Service does not need to go to the KDC to verify the Service Ticket - all it needs is its Secret Key which it already has.

Here's a sequence diagram for you.

Remember when I said I was glossing over or simplifying a few things?

One of those things is how the Service gets its secret key. In my text above I said it gets that key when it "enrolls", but in the diagram I used the word "start" instead. That's one of those hand wavey places.

Another one is the TGT. A TGT is just a ST for the KDC itself. So if you look at a TGT you'll see that it's a special Service Ticket for krbtgt/DOMAIN.COM@DOMAIN.COM. For example here's mine:

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?

I got hit with an IM out of the blue this morning that kicked off a bunch of conversations about how Kerberos works with HTTP. As I was working my way around the org it became clear that lots of people say "and Kerberos happens here" and move on, but most didn't have a good understanding of how it actually worked "on the wire".

If you want my description read on. If you want to see more authoritative info you should check out Wikipedia's page on SPNEGO.

Monday, January 17, 2011

The (Windows) Natives Are Restless

From Brian:  I'm adding this excellent post by Matt to our OAM 11g Academy series. To view the first post in the series which will be updated throughout to contain links to the entire series, click here: http://fusionsecurity.blogspot.com/2011/02/oracle-access-manager-11g-academy.html 
 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.
For the krb5.conf file my example is (showing all the edited parts; there were other pieces that came with my Amazon images that I left alone):
[realms]
IAM.COM = {
kdc = ip-10-116-199-182.ec2.internal
admin_server = ip-10-116-199-182.ec2.internal
default_domain = IAM.COM
}
[domain_realm]
.iam.com = IAM.COM
iam.com = IAM.COM
On the KDC (Active Directory), I created a user named “idam11g”. This is the WebGate host, what you’re going to use in the browser. I ran the following to create the keytab:
C:\>ktpass -princ HTTP/idam11g@IAM.COM -pass P@ssw0rd -mapuser idam11g -out c:\logs\keytab.service
Make sure that your User Login name in the Active Directory looks like “HTTP/idam11g”
Copy the keytab.service file to your OAM server.
Test on your OAM Server box that you can generate the Kerberos token by using the kinit command:
$ kinit HTTP/idam11g@IAM.COM -k -t /oracle/stage/wna/keytab.service
Now make the changes to OAM to tell it how to contact the KDC. Here is the oam.config.xml entry:
<Setting Name="KerberosModules" Type="htf:map">
<Setting Name="6DBSE52C" Type="htf:map">
<Setting Name="principal" Type="xsd:string">HTTP/idam11g@IAM.COM</Setting>
<Setting Name="name" Type="xsd:string">Kerberos</Setting>
<Setting Name="keytabfile" Type="xsd:string">/oracle/stage/wna/keytab.service</Setting>
<Setting Name="krbconfigfile" Type="xsd:string">/etc/krb5.conf</Setting>
NOTE: The docs are off on the contents of oam-config.xml. Also, if you make changes to the oam-config.xml, these changes may get reset if you make other changes in OAM through the console. I’ve heard of similar problems when doing the OAM-OAAM integration. I think you are better off making this edit through the console. You can configure this through the console by going to System Configuration (tab)->Authentication Modules->Kerberos Authentication Modules->Kerberos:






While in the oamconsole, you want to configure AD to be the Primary Identity Store. This is located at System Configuration (tab)->Data Sources->User Identity Stores.

A word about Role Mapping: The “OAM Administrator’s Role” field is looking for an existing group in AD. Users in that group will be able to login to oamconsole once AD becomes the primary identity store. Don’t forget to press the “Set as Primary” button. Once you do this, you should be able to authenticate to your default web page with AD users’ credentials.
I recommend creating a separate Authentication Policy for WNA with a OnAuthFailure redirect so you can see when you are getting an OAM Auth failure vs. other reasons. I used the existing “KerbScheme” for the Authentication Scheme as is, without editing.

I configured an 11g WebGate on 11g OHS via standard means.
For IE7, here is the process for setting Integrated Windows Authentication for the client:
  • 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.

Tools like isHTTPHeaders can help you determine whether the Negotiate token is being issued. It should look something like:
GET /oam/CredCollectServlet/WNA?request_id=-3931587206375492112&error_code=OAM-1001&redirect_url=http%3A%2F%2Fip-10-124-122-41%3A7777%2Fwna%2Findex.html HTTP/1.1
OAMRequestContext_idam11g:7777_444a69=DJi+QPvh2J7VEs6bzv0EAQ==
Authorization: Negotiate
<Long encrypted string of about 1600 chars>
For OAM troubleshooting, you want to be able to turn on some tracing via WLST:
cd <MW_HOME>/Oracle_IDM1/common bin
./wlst
connect(‘weblogic’,’<password’)
listLoggers(pattern=”oracle.oam.*”,target=”oam_server1”)
setLogLevel(logger=”oracle.oam”,level=”TRACE:32”, persist=”0”, target=”oam_server1”)
You can then check your diagnostics log at <IDM_DOMAIN>/servers/oam_server1/logs/oam_server1-diagnostic.log.
A successful transaction should look something like:
[2011-01-13T12:34:40.730-05:00] [oam_server1] [TRACE:16] [] [oracle.oam.controller] [tid: [ACTIVE].ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'] [userId: ] [ecid: 0000Iq0SORD1rYspkgg8yZ1DBnSC00000R,0] [SRC_CLASS: oracle.security.am.engines.enginecontroller.AuthnEngineController] [APP: oam_server] [dcid: d03843071ed98d9b:6369d65b:12d806f7606:-8000-0000000000000015] [SRC_METHOD: createSubject] RETURN oracle.security.am.engines.sso.SSOSubject@16748a2c, Subject: [{ Subject: Subject:[[
Principal: username@IAM.COM
Principal: CN=User Name,cn=users,dc=iam,dc=com
Principal: \4f\81\08\d9\90\14\29\43\81\fd\2b\a2\59\c3\21\ab
, GroupsLoaded: false, UpdateSession: false, isAnonymous: false }], Subject Attrs: [String Map: {}], User Id: will.laase@IAM.COM, User DN: CN=Will Laase,cn=users,dc=iam,dc=com, GUID: \4f\81\08\d9\90\14\29\43\81\fd\2b\a2\59\c3\21\ab, Auth Level: 2, Auth Scheme: KerbScheme
]]
The documentation talks about doing the same with Firefox, by browsing to about:config and setting:
network.negotiate-auth.trusted-uris =http://idam11g:7777
I was not able to get firefox to work. If anyone can comment on a success path here, that would be appreciated.

Tuesday, August 17, 2010

Kerberos and WebLogic Server - decisions, decisions

I've been away from the blog for a while - I took some vacation time and I've been working on a customer that I can't really talk about. Have you missed me?

No? I didn't think so.


I got a call yesterday asking me to help a customer trying to get SPNEGO/Kerberos working between Windows desktops and WebLogic server. Yes, Kerberos again. After talking through what they're trying to accomplish today I found that they may have started out on the wrong foot to begin with.

Here's (more or less) what they told me they're trying to accomplish:
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.
That's it.

There are three ways that I can think of right away to address that requirement:
  1. Use WebLogic's SPNEGO Identity Asserter to have WebLogic verify the Kerberos ticket
  2. Use IIS to do the Kerberos bits and then use a simple HTTP header Identity Asserter to get the identity into WebLogic
  3. Use Oracle Access Manager
Each of the solutions have their own plusses and minuses. The point of this post is to try to get them written down to help you (or someone else) make a sensible decision about which one to pick.

Before I get into it let me just say that Kerberos/SPNEGO is one of those technologies that when it works it's awesome, and when it doesn't it's incredibly frustrating to figure out what's gone wrong. Add to the mix the fact that so much of what's happening is automatic and so few people actually understand what's really happening under the covers/on the wire and you have a recipe for pain, frustration, annoyance and lots of hair being pulled out.

With that said...

WLS + SPNEGO Identity Asserter
WebLogic includes an the necessary bits that allow WebLogic to verify a Kerberos ticket if you want. I've blogged about it before and there are official docs as well as oodles of threads on Oracle forums where people are struggling to get it working. It definitely works and it has gotten easier over the years, but it's still harder than flipping a switch and being done.

The requirements basically amount to getting a Kerberos service principal registered, getting the necessary secret key onto the box running WebLogic and then getting WebLogic and IE to agree to do SPNEGO. Each of those steps is manual and it's easy to make a mistake.

Plusses: baked into WebLogic so there are no dependencies on any other software, Kerberos ticket is verified inside WebLogic itself and is thus about as secure as possible.
Minuses: can be difficult to setup & troubleshoot

Use IIS to do the hard stuff

One of the great things about Microsoft embracing Kerberos is that some very difficult, very complicated things have been reduced to a checkbox. To enable Kerberos in IIS you add the web server to the domain, then you go into the IIS console, edit the Security properties of a directory or of the server and check one box - "Integrated Windows Authentication". Windows and IIS do all the hard stuff for you including setting up the Kerberos principal.

Yes, I'm glossing over a few things, but not many.

Once you've got IE and IIS doing the Kerberos/SPNEGO dance two things remain: (1) getting IIS to proxy requests over to WebLogic and (2) getting WebLogic to trust IIS' say so on the user's identity. The former is address by the IIS WebLogic Plug-in. The latter is a simple Identity Asserter like the one I discussed back in January (I really need to put that code up somewhere on the Internets!).

Plusses: One checkbox to enable Kerberos in IIS
Minuses: You have to deploy Windows & IIS in front of every WebLogic server. The security issues I mention in my older post

Use OAM

OAM provides two relevant things - SSO across web sites and access control to those web sites. The former is all we're interested in here; when a user accesses the web server OAM will detect that they haven't authenticated yet and kick them into a process to do that. The latter allows you to decide who gets access to what parts of the protected web sites; it's not something we care about in this case but it can be very useful.

The upside to using OAM in this case is that you only need one or two IIS web servers somewhere in your environment to get Kerberos working. Each of the OAM WebGates plugged into your protected web servers will redirect users to one of those machines to figure out who the user is and then back to the protected web server. This works regardless of which flavor of web server you use - OHS, Apache, IIS, or Sun One. I've seen customers do this sort of thing with as little as one IIS server, but for redundancy you want a pair of IIS boxes somewhere in your environment and probably a second pair in a disaster recovery environment as well.

Plusses: Few Windows/IIS servers needed.
Minuses: Requires additional software (OAM WebGate) to be installed


In this particular customer's case they'd tried the first option already and had run into some issues. Because of those issues and because their requirements were still in flux I suggested that they try the second option (IIS doing Kerberos along with a username Identity Asserter). Your mileage may vary and you should probably ask a knowledgable person for advice before picking one of these options, but that's (part of) what we're here for.

I'm interested in what you think. Let me know in the comments!

Wednesday, June 2, 2010

Oracle Access Manager and Kerberos (/Integrated Windows Authentication) with fallback to HTML forms

Back in January I posted an answer to the question:


In that post, as in the past I've always pointed people in the Right Direction and let them fill in the blanks themselves. This week I had to actually go get it working and given all the effort it took me I have a sneaking suspicion that most people listen to my suggested solution, nodded, said they understood and then skipped the Kerberos bit altogether.

First the requirements:
  • 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)
What we want is for a user logged into workstation1 with their domain credentials to be able get into OAM-protected resources by going right to them via a bookmark, typing in a URL, clicking on an emailed or IM'd link or any other way they want. A user on workstation2 going to the same URL on the same web server should see OAM's HTML login form.

Seems simple, right? It's not.

The way IWA works is through the HTTP authentication method called Negotiate documented in RFC 4559. Basically your client says "GET /foo" and the server says "401. Gimme a Kerberos token" and optionally some text or HTML. Your browser repeats the first request and includes a Keberos token. The spec includes details of what the flow looks like on the wire.

In the olden days if your browser didn't know how to handle the Negotiate authentication method it would just show the HTML page that the web server sent back. Things have changed though - browsers tend to hide the real HTTP error pages and instead show "friendly" error pages. I also discovered that IE, at least in my test environment, pops a login box up on the screen to collect the credentials.

So what we need is a way to silently detect whether the web browser can authenticate to the web server or not. Once we have that putting together the OAM bits is just a matter of following the normal steps.

What didn't work...

I tried a bunch of ways to do that detection before finding one that worked. I am listing them here not to torture you, but to describe why they didn't work to save you the time and trouble of testing them out on your own.

Ways that didn't work:
  1. using IIS' 401 error page
  2. using XmlHttpRequest and XDomainRequest
  3. using Flash
As I mentioned above the 401 error page didn't work because IE pops up a basic-style box and hides the error page sent by the server. In fact that's the same solution I posted back in January... and it's wrong. Or at least it's wrong for IE 8.

XmlHttpRequest is the standard way that AJAX web apps make HTTP requests to the server via JavaScript. XDomainRequest is an IE 8+ proprietary extension that's basically the same thing as XmlHttpRequest but has different security restrictions; more info can be found on MS' site. Both of these exhibited the same behavior as the browser itself, exactly as the W3C spec says they should.

I tried Flash because it's almost always available on platforms that would support Kerberos and because it plugs into the browser and is initialized on browser startup. Unfortunately the URLRequest object in Flash doesn't provide low enough level control to avoid the pop-up box.

What DID work?
The only thing I could get to actually work without breaking my requirements was a Java Applet.

If you let it Java's URLConnection class handles the Kerberos authentication for you. All you need to do is wrap a try/catch block around the call and fire it off. If you get back content then the HTTP transaction succeeded. This is what the Applet's code looks like:
 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. tag on your page and

Pulling the bits together
Once you have an applet that can detect that the user is logged into the domain all that remains is to put the pieces together. There are a bunch of ways to get across the finish line depending on how you want it to work, the simplest of which is to make an HTML login page your "default". You add the applet to that page, let the applet detect that IWA is available to the user. If the applet determines that IWA isn't going to work for the user they just fill out the normal HTML form and log in. If the applet determines that IWA will work for the user you use JavaScript to kick the user into the Kerberos authentication process.

I'll leave some of the details up to you, but those are the broad strokes.

Hope this helps!

Sunday, March 21, 2010

Oracle Service Bus and Kerberos

This topic has come up in a number of discussions at a number of customers in the past few weeks. The basic question is "How does OSB work with Kerberos (WCF) clients?". The short answer is "The simplest thing to do is just use OSB as a pass-through." I've given this answer a few times, and people seem disappointed. I think they expect that there is "more". I wanted to use this post to help people understand some of the details of OSB and Kerberos that make this the most sensible approach.

Friday, February 5, 2010

Testing your WebLogic/Kerberos setup

I spent several hours today beating my head against a wall and thought I'd post this in case anyone else runs into the same problem.

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!

So if you're trying to get Kerberos working make sure you use a web browser on a different machine. Or just use Firefox instead.

Thursday, February 4, 2010

Kerberos and WebLogic Server on Windows step-by-step

The month of Kerberos continues

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.

There are official docs available from download.oracle.com, but some people find them confusing. These are a bit simplified and are intended for this configuration only.

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.

Prerequisites:
  • 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
Setup the Kerberos KDC bits...

Before you do anything else we need to make sure that you're starting in a "good" state.

Delete any keytab files you've already created. Delete any cached keys (del "%USERPROFILE%\krb5cc*"). Clear out any config files you've created. And log out and back into the account in which you're running Internet Explorer. Also make keep my previous post in mind while you're testing.

If you have installed IIS on the machine uninstall it. IIS registers the kerberos service principal HTTP/machine and HTTP/machine.domain.com and if you leave IIS installed you'll never manage to get Kerberos on WebLogic working correctly.

Install the setspn utility. Info on the utility is available on MS TechNet, and it is installed as part of the Windows Server 2003 Support Tools from the Windows product CD.

Run the command "ldifde -f c:\export.txt" on your Windows server. This will export the entire contents of your Active Directory to a flat file so we can search it. What we need to do is make sure that no user or machine has already registered the Kerberos Service Principal we need for WebLogic. If you don't know what that means don't worry about it, just search the export.txt file for HTTP/machine (where machine is the name of the machine). If you find it in the file you'll need to use the setspn utility to remove the mapping. Here's what my export contained when things were broken:
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
Use the setspn utility to remove the extraneous mappings. In my example above I had a mapping from the machine named "WEBSERVER" to the two principals HTTP/webserver and HTTP/webserver.kerbtest.com. I needed to remove those mappings before continuing on to the next step. You have to use the setspn utility with the -D flag to remove the mappings like so:
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
Finally we need to create a new user in the domain so that WebLogic can get the Kerberos secret associated with the Kerberos SPN. The official docs and basically everything else you'll find on the Internet says to make the username the same as the machine name; I disagree strongly. If you give the machine and user the same name you'll just confuse yourself later trying to figure out which is which plus the setspn tool assumes you're talking about the machine and there's no way to tell it to operate on the user instead. So when you create the username make it generic, for example "wlsuser", "webuser" or "webserveruser".

Once you've created that user use the setspn utility to associate the HTTP/machine and HTTP/machine.domain.com principals with the user you just created. Yes, you are going to associate the SPN with the user and NOT with the machine. I'll explain why later, but this is really important! When you do this you want HTTP to be in all caps and the machine name to be all lower case. The capitalization is important because the format has to match what Internet Explorer uses when it talks to the KDC. Here's what my output looks like:
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
You can use ldifde to export the contents of AD again just to be sure you've got it right.

Configure the WebLogic bits...

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.
Click the Providers tab and you should see the list of Authentication Providers.
Click the New button to create a new authentication provider and select Negotiate from the drop down.
Go into the Identity Asserter's configuration and click on the Provider Specific tab. We don't want the NegotiateIdentityAsserter to run when you deploy an application with the authentication method set to Forms or Basic, so uncheck the "Form Based Negotiation Enabled" box.

Note that once you hit save and restart WebLogic you'll probably be locked out of the console until we do the next bunch of steps. If you wind up in trouble just back out the above changes by swapping your backup of config.xml in, restarting WebLogic and your web browser.

Copy a test app that requires Certificate authentication into your autodeploy directory. Then restart your server. If you don't have such app you'll have to create one.

When you access the app you should get a 401 error page from WebLogic. If you do an HTTP trace you should see an HTTP transaction that looks something like this
HTTP/1.1 401 Unauthorized
Date: Thu, 04 Feb 2010 21:44:10 GMT
Content-Length: 1518
Content-Type: text/html
WWW-Authenticate: Negotiate
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.

Back to more Kerberos stuff...

We need to create the kerberos ini and keytab files:

Create a c:\windows\krb5.ini file that looks like the following. Update the domain and other information to reflect your environment:
[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
In the official docs and all over the Internet you will find similar files with lines that start with default_tkt_enctypes and default_tgs_enctypes. As long as you are using JDK 1.6 you should be able to leave them out. The "kdc" setting is the IP address of your KDC and the "admin_server" setting is the host name of your KDC.

Run the command "c:\Oracle\Middleware\wlserver_10.3\common\bin\commEnv.cmd" to set your path and other environment variables so that you can run the Kerberos-related tools like ktab and kinit. Then cd into your domain's directory. In my case that's c:\Oracle\Middleware\user_projects\domains\base_domain.

The Kerberos related classes included with the JDK require a config file to run properly. Basically this file tells the GSS layer which classes are used to do the actual work and provides configuration information to those classes. Again, don't worry about what any of this means, just do the same thing I do and you'll be fine. Use notepad to create a krb5login.conf file with these contents:
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;
};
We need to create the keytab file I specified in the krb5login.conf file above. To create the file you use the ktab tool, and to verify its contents you use the kinit command line tool. Both of these tools come with the JDK so you want to make sure that you're using the same JDK as WebLogic is going to use. The instructions you find in the docs talk about using the ktab and kinit command line utilities directly, and if you want you can use them. I on the other hand like lots and lots of debugging information, so I prefer to invoke the same classes by running java.exe and passing a few extra command line options. Do whichever you feel more comfortable with and as long as kinit works everything is fine.

First create the keytab with kinit:
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
If you don't see ktab create a keytab file, or if the file winds up being just a few bytes then don't proceed any further since it won't work; instead go back and check the first bunch of steps - exporting the AD contents, checking the user you created, etc.

Next run kinit to insure that the keytab file is OK and that your configuration files are all setup correctly.
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
Notice that line "New ticket is stored". If you get that line it means that the Kerberos configuration is correct and that the Java code was able to acquire the secret key.

Last configuration step...

Edit the bin\startWebLogic.cmd to add the line set JAVA_OPTIONS below in the place I've shown it:
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
Restart WebLogic, access the site with IE and revel in your success.

Problems? Issues?

Hit me up in the comments!

Monday, January 25, 2010

Integrating Oracle Access Manager with Kerberos authentication with fallback to an HTML form

Seems like it's the month of Kerberos.

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.


  1. user accesses http://somemachine.mydomain.com/protected/
  2. OAM redirects the user to http://trustedmachine.mydomain.com/oamauthenticator/
  3. 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)
  4. the OAM WebGate will consume the identity from IIS and pass that identity to the Access Server
  5. the OAM Access Server will search LDAP for the user
  6. 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:

  1. the credentials will be passed to the Access Server
  2. the Access Server will search LDAP for the user and bind to the directory to authenticate them
  3. if the credentials are correct the user will get an OAM session cookie
  4. 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.

Update: I fleshed this out some more in a newer post.

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