views:

353

answers:

4

We have a service that handles authorization based on a User Name and Password. Instead of making the username and password part of the call, we place it in the SOAP header.

In a typical scenario, a Web Service calls the Authorization service at the start of execution to check that the caller is allowed to call it. The problem is though that some of these Web services call each other, and it would mean that on every sub-call the user's permissions are checked, and that can be very expensive.

What I thought of doing was to have the Authorization service return a Security Token after the first call. Then, instead of having to call the Authorization service each time, the Web Service can validate the Security Header locally.

The Security Header looks something like this (C# code - trimmed to illustrate the essential concept):

public sealed class SecurityHeader : SoapHeader
{
    public string UserId;     // Encrypted   
    public string Password;   // Encrypted; Just realized this field isn't necessary [thanks CJP]

    public DateTime TimeStamp;   // Used for calculating header Expiry
    public string SecurityToken;
}

The general idea is that the SecurityHeader gets checked with every call. If it exists, hasn't expired, and the SecurityToken is valid, then the Web Method proceeds as normal. Otherwise it will either return an error, or it will attempt to re-authorize and generate a new SecurityHeader

The SecurityToken is based on a salted hash of the UserId, Password, and TimeStamp. The salt is changed every day to prevent replays.

The one problem I do see is that a user might have permission to access Web Service A, but not Web Service B. If he calls A and receives a security token, as it stands now it means that B will let him through if he uses that same token. I have to change it so that the security token is only valid from Web Service to Web Service, rather than User to Web Service ie. It should be OK if the user calls A which calls B, but not OK if the user calls Service A and then Service D. A way around that is to assign a common key (or a set of keys) to logically related services. (ie. if client can do A then logically he can do B as well).

Alternatively, I'd have to encode the user's entire permission set as part of the security header. I'll have to investigate what the overhead will be.

Edit:

Several people have mentioned looking at other security schemes like WS-Security and SAML etc. I already have. In fact, I got the idea from WS-Security. The problem is that other schemes don't provide the functionality I need (caching authorization information and protecting against replays without an intemediary database). If someone knows of a scheme that does then I will glady use it instead. Also, this is not about authentication. That is handled by another mechanism beyond my control.

If it turns out that there is no way to cache authorization data, then it means that I'll just have to incur the overhead of authorization at each level.

+1  A: 

Personally I don't see you having any issues there as long as you have a centralized underlying framework to support the validation of the SecurityToken values.

Mitchel Sellers
+2  A: 

Ok, replacing my older answers with hopefully a better one.

What you describe should work if you have a way to securely share data between your services. For example, if your services share a secret key with the Authorization Service, you can use this key to get the salt.

BTW, I don't know enough cryptography to say whether it's safe enough to add secret salt + hash (although seems fine); I'm pretty sure it's safe to HMAC with a secret or private key. Rotating keys is a good idea, so you would still have a master key and propagate a new signing key.

Other issues with your approach are that (a) you're hardcoding the hashing logic in every service, and (b) the services might want to get more detailed data from the Authorization Service than just a yes/no answer. For example, you may want the Authorization Service to insert into the header that this user belongs to roles A and B but not C.

As an alternative, you can let the Authorization Service create a new header with whatever interesting information it has, and sign that block.

At this point, we're discussing a Single Sign-On implementation. You already know about WS-Security specs. This header I described sounds a lot like a SAML assertion.

Here's an article about using WS-Security and SAML for Single Sign-On.

Now, I don't know whether you need all this... there are in-between solutions too. For example, the Authorization Service could sign the original Username block; if you worry about public/private crypto performance, and you're ok sharing secret keys, you could also use a secret key to sign instead of public/private keys.

ykaganovich
+3  A: 

The fundamental problem with your scheme is that you're not using a standard framework, or implementation. This is regardless of any particular merits of your scheme itself.

The reason is simple, security (cryptography in particular) is very, very complicated and pretty much impossible to get right. Use a common tool that is robust, well understood and proven.

See: http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=wss for more information on WS-Security.

Most frameworks (.NET/JavaEE etc) will have in-built support (to some degree) for WS-Security.

If you beleive your scheme to be better in some way than the standards, I suggest you write it up as a paper and submit it for peer-review (along with a reference implementation), but DO NOT use it to secure an application.

EDIT to respond to OP Edit: I think you're confusing the roles of Authentication and Authorization a little, which is easy to do...

The roll of the Security Token (or similar) in schemes is to Authenticate the sender of the message - basically, is the sender who they should be. As you rightly pointed out, Authentication does not imply anything about which underlying resources the sender is to be granted access to.

Authorization is the process whereby you take an authenticated sender and apply some set of permissions so that you can restrict scope of access. Generally the frameworks won't do authorization by default, you either have to enable it by creating some form of ACL, or by extending some kind of "Security Manager" type interface.

In effect, the idea is that the Authentication layer tells you who is trying to access Page A, and leaves it up to you to decide if that person is authorized to access Page A.

You should never store information about the rights and permissions in the message itself - the receiver should verify rights against its ACL (or database or whatever) for each message. This limits your exposure should someone figure out how to modify the message.

Do you see any potential problems with it?
ilitirit
The problems will likely be in the implementation, e.g. if you're open to replay, padding, collision etc attacks.Honestly it's not worth the drama when you can use stuff that other people have already done their heads in over :)
I see what you mean - I don't need to store the password after the user has been authenticated. That just makes things a bit simpler. However, it still doesn't help with the initial problem - caching the permissions so that I don't *have* to check it with each message (it's an expensive operation)
ilitirit
caching is the easy bit, simply load the permissions into an object, then store that object in a global cache object (such as a hashtable). You then write an accessor method that looks for the object in cache, if it doesn't find it, it then populates one and adds it.
How would WebService B read the permissions stored by WebService A? That would mean I've had to pass it along with each WebService call. I can't do that, unless I pass it along as part of the Security Header.
ilitirit
A: 

First of all, read @CJP 's post, he makes an excellent and valid point.
If you want to go ahead and roll it yourself anyway (maybe you do have a good reason for it), I would make the following points:

  • You're talking about an Authentication Service, NOT an authorization service. Just to make sure you know what you're talking about...?
  • Second, you need to separate between the salt (which is not secret) and the key (which is). You should have a keyed hash (e.g. HMAC), together with salt (or a keyed salted hash. Sounds like a sandwich.). The salt, as noted, is not secret, but should be changed FOR EACH TOKEN, and can be included in the header; the key MUST be secret and being changed every day is good. Of course, make sure you're using strong hash (e.g. SHA-256), proper key management techniques, etc etc.

Again, I urge you to reconsider rolling your own, but if you have to go out on your own...

AviD
I'm NOT talking about an Authentication service,I'm talking about authorization (permissions).Salt values can be used as keys (I do have a secret key as well).I haven't come across an existing scheme that handles 3rdparty authorization and replay attacks without an intermediary database.
ilitirit
Ok, after you edit I see your point - but it's still not really authorization unless you do include the "permission set". At most, its authentication+access.
AviD
Moreover, simply salting the hash will not prevent spoofing, you need a keyed hash for that - the salt is to prevent rainbow table attacks, hence the need for it to be unique for each token.
AviD