Obtaining a Security Token - Rampart/C
By Supun Kamburugamuva
- 18 Nov, 2008
Table of Contents
WS-Security provides the foundation for securing Web services. When interactions among different entities get complex, basic security and authentication provided by WS-Security alone is not practical. WS-Trust is a specification developed on top of WS-Security for addressing some of these security requirements.
WS-Security defines the security token. Security tokens can be used for various purposes like authentication, signing, and encrypting messages. This article explains how to use Rampart/C to obtain a security token according to the WS-Trust specification.
A security token is a collection of statements. In security jargon we call these statements Claims.
A claim is a statement made about a subject, i.e., name, age, e-mail, cryptographic key, etc,.
The definition of a security token is generic enough to represent a wide range of things. For example, a security token may carry a cryptographic key, user name/password, personal information about a subject, etc,. A security token can be represented as XML, text, number, binary value -the possibilities are endless. In web services, a security token is always represented by an XML.
Some of the popular examples of security tokens are user name tokens, secure conversation tokens, and SAML tokens. Now let’s look at why we need to obtain security tokens.
Why do we need to obtain security tokens?
Sometimes two parties need to exchange security information via security tokens. One example is a secure conversation, in which two parties establish a shared secret using security tokens to communicate securely.
Another reason is that people trust what others say about us more than what we say about us.
A resource about a requester can be in one of these states.
- The resource knows about the requester, i.e, the username and password
- The resource does not know about the requester, but it knows someone who knows about the requester.
If the resource knows about the requester, the requester can present this information to the resource directly and access the resource.
In most of the real world scenarios where people access the resources providing claims, the resource does not know about the requester. For example, imagine someone presenting his or her identity card to enter a secure building. The person at the gate may not know the person who is presenting the identity card, but he knows and trusts the identity card issuer. If you map this scenario to the web services world, the identity card will be the security token and accessing the building will be equivalent to accessing a resource.
I have used the word trust in my explanation on why we need to obtain security tokens. As you can see, if we want to use an obtained token, trust plays a core role. The resource needs to trust the token issuer. There is a web service specification called WS-Trust, which can be used to establish trust relationships between different parties through the exchange of security tokens.
In its very basic form, WS-Trust is a protocol for issuing, renewing, and validating security tokens. WS-Trust is built on top of the WS-Security specification so that it can be used to perform these operations on the tokens in a secured manner.
This article is written from the perspective of a client. Obtaining a security token is the opposite of issuing a security token. The WS-Trust specification defines a special entity called the Security Token Service (STS). STS provides core functionalities like issuing, renewing, and validating security tokens. An STS is a web service which defines one or more of these operations.
Usually we obtain a token to present to a third party. We call this third party a Resource Provider or Relying Party.
WS-Trust defines a standard message format for requesting a security token. The message is called Request Security Token (RST). WS-Trust also defines a message format for sending a security token called the Request Security Token Response (RSTR).
Since we are going to obtain a security token, we have to talk to an STS. This article does not cover the usage of the obtained security token because each security token has its own unique method.
To obtain a token, you should know the structure of a RST message. I will explain the most important elements of RST and RSTR messages.
<wst:RequestSecurityToken> <wst:TokenType>...</wst:TokenType> <wst:RequestType>...</wst:RequestType> <wsp:AppliesTo>...</wsp:AppliesTo> <wst:Claims Dialect="...">...</wst:Claims> <wst:Entropy> <wst:BinarySecret>...</wst:BinarySecret> </wst:Entropy> <wst:Lifetime> <wsu:Created>...</wsu:Created> <wsu:Expires>...</wsu:Expires> </wst:Lifetime> </wst:RequestSecurityToken>
The TokenType element identifies the token type we are requesting. Usually this is a URI value and is defined by specific tokens. To request a security token, the RequestType element should contain the value http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue.
The AppliesTo element is optional, and it can contain information about the ultimate recipient (relying party) of the token. For example, if we request a token to be presented to a third party, we can give information about the third party in the AppliesTo element.
As I mentioned earlier, a token is a collection of claims. The model provided by WS-Trust is so flexible that we can even request a specific set of claims from the issuer. The format of requesting claims is not defined by WS-Trust. You can define your own claim types or use standard types from other specifications.
When the requester wants to create a shared secret with the issuer, it should send the Entropy element. If the issuer is willing to share a secret key, it will send its entropy. Then a shared secret can be formed and keys can be derived from it. This is called the partial key derivation.
Some tokens have lifetimes and some do not. We can specify the expected lifetime of the token in the LifeTime element. The specification does not say that the issuer should strictly adhere to the lifetime that is requested.
<wst:RequestSecurityTokenResponse> <wst:TokenType>...</wst:TokenType> <wst:RequestedSecurityToken>...</wst:RequestedSecurityToken> <wsp:AppliesTo>...</wsp:AppliesTo> <wst:RequestedAttachedReference> </wst:RequestedAttachedReference> <wst:RequestedUnattachedReference> <wst:RequestedProofToken>...</wst:RequestedProofToken> <wst:Entropy> <wst:BinarySecret>...</wst:BinarySecret> </wst:Entropy> <wst:Lifetime>...</wst:Lifetime> </wst:RequestSecurityTokenResponse>
The TokenType specifies the token that is inside the response. The RequestedSecurityToken contains the actual token that is returned. The AppliesTo element contains the intended party of the containing token.
The common way of referencing a token in WS-Security is to use the ID attribute of the XML element. Some tokens have different referencing mechanisms. The best example is SAML tokens, which do not allow referencing by ID. In these cases, it is the responsibility of the issuer to send the Security token reference along with the actual token. Then the recipient can simply use the token, without requiring any further knowledge of the token.
When some tokens are presented to a third party, the actual token does not have to be in the message. Instead, a reference to the token can be sent. In such a situation, the RequestedUnattachedReferece element should be present with the SecurityTokenReference element.
If the issuer wants to use a specific key with the token, it sends the key inside the RequestedProofToken. If it wants to use a shared secret (client provides entropy) it sends the ComputedKey element inside the RequestedProofToken for the client to create the shared secret, and derive keys using it.
Rampart/C and WS-Trust
WS-Trust is implemented as part of the Rampart/C project. You can find the trust related headers with the Rampart/C header files. WS-Trust only provides an API. Usually Rampart/C is used as a module for Axis2/C, and the security functionalities are provided using the Policy configurations. If you want to use the WS-Trust functionality, you should program the client or service using the WS-Trust API. So if you write a STS or a WS-Trust client, Rampart/C must be linked to the service or client in order to use the API. All the API methods for the WS-Trust implementation can be found in the header files with the prefix trust.
There are two ways of obtaining a security token. The first method is to use the trust_sts_client_t structure and its associated methods. The second method is to use the API methods to create an RST message and send it using the normal Axis2/C client. The second method gives maximum control to the user but requires more work.
First we will look at how to use trust_sts_client_t to obtain a token.
STS Client API
The primary entity that can be used for easily obtaining a security token is to use the trust_sts_client_t structure and its associated functions. The STS client is a wrapper around the normal Axis2/C service client. Since there are a lot of user parameters that need to be set when obtaining a token, use the API provided for building a RST message. As I have mentioned earlier, obtaining a security token is a regular web service request with a special body element called RST. So we need to create the RST and send it to the STS. This request should adhere to the STS security policy requirements. The message delivery and security requirements are handled by the underlying Axis2/C client. We need to create a RST according to the Security token that we are requesting. To simplify creating and processing an RST message, Trust implementation supplies an RST API. You can use this to create an RST message.
First, create a trust_sts_client_t structure.
trust_sts_client_t *sts_client = NULL; AXIS2_EXTERN trust_sts_client_t *AXIS2_CALL trust_sts_client_create( const axutil_env_t * env);
The API methods for the trust_sts_client_t can be found in the header file trust_sts_client.h. Then you need to set the basic information that any web service request requires. For example, the client home directory and the issuer address. You can use the following functions to do this.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_sts_client_set_home_dir( trust_sts_client_t * sts_client, const axutil_env_t * env, axis2_char_t * directory); AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_sts_client_set_issuer_address( trust_sts_client_t * sts_client, const axutil_env_t * env, axis2_char_t * address);
Next, create a trust_context_t. This is the core structure in WS-Trust implementation and acts as a central hub to combine all the information.
trust_context_t* trust_context = NULL; AXIS2_EXTERN trust_context_t *AXIS2_CALL trust_context_create( const axutil_env_t * env);
Since we are requesting a security token, we need to send an RST message. To create and process RST messages, there are set of methods and an associated structure called trust_rst_t. We can use this to create and process RST messages. To use these functions, we need to create the trust_rst_t.
trust_rst_t *rst = NULL; AXIS2_EXTERN trust_rst_t * AXIS2_CALL trust_rst_create( const axutil_env_t *env);
Now let’s see what we need to populate into this structure to request a security token.
There is a method set available for populating this structure. You can find them in the trust_rst.h header file. Obviously we need to set the request type. That is, whether it is an issue request, cancel request, etc. Here we set it to Issue, which is a URI. These URIs are defined in the trust_constants.h header file.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_rst_set_request_type( trust_rst_t *rst, const axutil_env_t *env, axis2_char_t *request_type);
Here are the possible values for the request_type parameter.
TRUST_REQ_TYPE_ISSUE TRUST_REQ_TYPE_VALIDATE TRUST_REQ_TYPE_RENEW TRUST_REQ_TYPE_CANCEL
Now we look at the type of the security token requested. This depends on the token that you are requesting.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_rst_set_token_type( trust_rst_t *rst, const axutil_env_t *env, axis2_char_t *token_type);
Secure conversation token:
It is important thing to set the WS-Trust namespace URI.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_rst_set_wst_ns_uri( trust_rst_t *rst, const axutil_env_t *env, axis2_char_t *wst_ns_uri);
Then we need to set the addressing action.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_rst_set_wsa_action( trust_rst_t *rst, const axutil_env_t *env, axis2_char_t *wsa_action);
At this point we have set all the basic values neccassary for obtaining a token. Then we need to set the RST to the trust context we have created previously.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_context_set_rst( trust_context_t *trust_context, const axutil_env_t * env, trust_rst_t *rst);
Set the policy file location of the issuer. This will ensure that the the request to the STS will adhere to its security policy.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_sts_client_set_issuer_policy_location( trust_sts_client_t * sts_client, const axutil_env_t * env, axis2_char_t * file_path);
This file path is the full path name to the policy file.
Now we have a basic set up for requesting a security token. There are two methods for requesting a security token. The first method just sends an RST message without considering the relying party policy.
AXIS2_EXTERN void AXIS2_CALL trust_sts_client_request_security_token( trust_sts_client_t * sts_client, const axutil_env_t * env, trust_context_t *trust_context);
The above method will request a security token with the infomation in the RST structure, If you want to use the client entropy with this method, you have to manually create it and set it to the RST.
The second method sends an RST adhereing to a security policy of the relying party.
AXIS2_EXTERN oxs_buffer_t* AXIS2_CALL trust_sts_client_request_security_token_using_policy( trust_sts_client_t * sts_client, const axutil_env_t * env, trust_context_t *trust_context, neethi_policy_t *policy, axis2_char_t *address_version, axis2_bool_t is_soap11, rampart_context_t *rampart_context);
If you are using this method from a client, rampart_context should be NULL. You can use Neethi to build the policy object from a policy file or an AXIOM node containing the policy. If the policy needs the client entropy, this method will create it.
When the response comes in, the above methods return with all the information about the response populated in to the trust_context_t. The response is an RSTR message with a secuity token. This implementation provides an API for processing RSTR messages as well. The structure associated with this API is trust_rstr_t. You can find the API methods in the trust_rstr.h header file.
Now we need to access the RSTR and get the Security Token with the other relavent information.
AXIS2_EXTERN trust_rstr_t* AXIS2_CALL trust_context_get_rstr( trust_context_t *trust_context, const axutil_env_t * env);
Now we can access the security token.
axiom_node_t *token_node; AXIS2_EXTERN axiom_node_t * AXIS2_CALL trust_rstr_get_requested_security_token( trust_rstr_t *rstr, const axutil_env_t *env);
This method will return the AXIOM node of the security token.
There are abstractions provided for security tokens like SAML and the Secure Conversation token by Rampart/C. It is up to the obtainer of the security token to apply these models on top of the AXIOM node that is returned. For example, in the case of an SAML token, you can build an instance of an saml_assertion_t by using the Assertion AXIOM node returned.
Similiarly you can access the attached references and unattached references.
AXIS2_EXTERN axiom_node_t * AXIS2_CALL trust_rstr_get_requested_attached_reference( trust_rstr_t *rstr, const axutil_env_t *env); AXIS2_EXTERN axiom_node_t * AXIS2_CALL trust_rstr_get_requested_unattached_reference( trust_rstr_t *rstr, const axutil_env_t *env);
The trust_client_t was developed for easily obtaining a security token.
You can always use the foundation utility functions and the trust_rst_t and trust_rstr_t for obtaining a security token directly using the service client API of Axis2/C.
You can have maximum control by populating the trust_rst_t as I've explained earlier, and then serializing it to an axiom_node_t by using the method
AXIS2_EXTERN axiom_node_t * AXIS2_CALL trust_rst_build_rst( trust_rst_t *rst, const axutil_env_t *env, axiom_node_t *parent);
This will give you the RST node and you can send this node to the STS, as any other web service request. When you get the RSTR message (AXIOM node), you can easily process it by using this method.
AXIS2_EXTERN axis2_status_t AXIS2_CALL trust_rstr_populate_rstr( trust_rstr_t *rstr, const axutil_env_t *env, axiom_node_t *rstr_node);
Now you can use the getter methods with RSTR for accessing various values.
I have explained the basic usage of WS-Trust implementation to acquire a security token. WS-Trust is a very flexible protocol with a lot of options. If the options provided by the trust_sts_client is insufficient for your requirements, you are always encouraged to use the trust_rst and trust_rstr methods for better flexibility. Direct usage of the WS-Trsut implementation is pretty straightforward.
Supun Kamburugamuva, Sofware Engineer, WSO2.Inc, supun AT wso2 DOT com