Click here to Skip to main content
Click here to Skip to main content

An Introduction To Web Service Security - Part II

, 1 Jun 2004
Rate this:
Please Sign up or sign in to vote.
Part two of this series introduces signing and encryption principals. It explains the steps to digitally sign and encrypt a message using the WSE and an X.509 certificate.

TOC

Introduction

The first article of these series explained how to use custom security tokens, adding username identification / password validation to ensure authentication. Now let's handle the next possibility to authenticate one's messages: using certificates by adding binary security tokens. This article extends the example built during the previous article.

Before we go into implementation details let's have a look at some fundamental security concepts. Using certificates and thus the technique of public/private keys can be used for different purposes, but most probably for signing messages or encrypting (parts of) them. And these topics, signing and encryption of messages, build the content of this article.
The next few paragraphs give a short introduction to encryption, signatures and certificates.

Encryption

Encryption technologies can be used to assure data confidentiality. Passing data through a network is very delicate regarding security issues. So encrypting the data before it passes the network and decrypting it afterwards can prevent eavesdroppers from reading the data during its transmittal.

The process of plaintext being coded to create ciphertext is called encryption, and the process of decoding ciphertext to restore the plaintext is called decryption.

There are two types of encryption: symmetric key encryption and asymmetric (public) key encryption, which can be even used in conjunction.

Symmetric Key Encryption

means the same key is used to both encrypt and decrypt a message. The key is a secret key because it is kept as a shared secret between the two involved entities (sender and receiver). Not keeping it secret would lead to the confidentiality of the encrypted data being compromised. Symmetric key encryption is often much faster then public encryption, up to 1000 times, because public encryption needs higher computational efforts.

Asymmetric (Public) Key Encryption

is based on two different keys for encryption and decryption: a private key (known only to its owner) and a public key (available and known to other entities on the network). A public key is typically used to encrypt a message, guaranteeing that only the person with the corresponding private key can decrypt the message.

The other way, the message can be encrypted using the private key and then decrypted with the public key.

Encryption is very important to Web Service security since SOAP messages are plain text by default, so being readable by any recipient and eavesdropper. The WSE supports both symmetric and asymmetric encryption. Using the former, the Web Service and the client share a secret key outside the SOAP message communication, the client encrypts the message using this key and the Web Service decrypts the message using the same shared key. Using asymmetric encryption, a client uses the public key (of an X.509 certificate) to encrypt his message and only the owner of the private key (of that X.509 certificate) can decrypt the message.

Signatures

Public keys can also be used to create and validate digital signatures. The goal is to achieve message integrity, that is ensuring that the data an recipient receives has not been altered during transit.

The basis here is that the data to be signed is combined with the private key and then transformed by some algorithm. Taken this, only someone possessing the private key could have created the digital signature, but anyone that has access to the corresponding public key can verify the digital signature. The important part: any changes made on the signed data invalidate the whole signature. So digital signatures allow a recipient to cryptographically verify that a message has not been altered since it was signed. To ease the use of the signature mechanism, the signatures themselves can be sent together with the data signed, so that the receiver can easily verify the message' origin.

The WSE provides mechanism to digitally sign SOAP messages using a UsernameToken, X.509 certificates or custom binary security tokens. When the WSE is configured on the SOAP message recipient's computer, the WSE automatically verifies the signature.

Certificates

So how to pass public or symmetric keys? Passing one's public key (the way it is most commonly needed) face-to-face is a possibility, but obviously not very practicable in every situation. Often it will be necessary to obtain a public key using some public directory, a less secure mechanism than face-to-face. So it's important for a user accessing a key from such a directory to have the certainty that this key really is the key it claims to be from. A standardized way to do so is to use certificates. A certificate is a digitally signed statement that contains information about the owner and its public key, binding these two pieces of information together.

Certificates are issued by entities or services called certificate authority (CA). A CA guarantees the validity of the binding between the certificate owner and its public key. Trusting a CA means trusting that any certificate created and issued for its owner by that CA identifies the owner of the certificate. Therefore the private key that corresponds to the public key in the certificate is deemed to be known only by the specified owner. Certificates can contain different type of data. A standard format representing certificates is X.509. Below, the members of this certificate format are listed.

Field Description
Version of the certificate format, f.i. 3
Certificate Serial Number unique serial number assigned by the issuing CA
Certificate Algorithm Identifier and Parameters public key cryptography and message digest algorithms used by the issuing CA to digitally sign the certificate
Issuer name of issuing CA
Validity Period start and expiration date
Subject name of person or entity that is owner of the certificate
Subject Public-Key Information incl. Algorithm Identifier & Public-Key Value

- the public key and a list of public key cryptography algorithms
- information specifying the cryptography operations for which the public and private key can be used

Extensional Optional Fields
Certification Authority's Digital Signature CA's digital signature

Certificate Stores

All certificates are stored in certificate stores, where several default stores are available:

CURRENT_USER\MY\
Personal certificates store for current logged-in user, not visible to other users logged in

LOCAL_MACHINE\MY\
Personal certificates store common for all users

CURRENT_USER\Root\
Trusted Root Certification Authorities and contains certificates for user-trusted root CAs. Certificates with a certification path to a root CA certificate are trusted by the current user for all valid purposes of the certificate.

LOCAL_MACHINE\Root\
as above, but trusted by all users

There are root CAs that are trusted by default, such as Verisign. Though our examples use a trial Verisign certificate, these are issued by the Verisign Test Authority, which is not trusted by default.

Certificate Management and Creation

It's time to start the actual implementation of our example using X.509 certificates. In fact, the step we take from the example of the last article to X.509 is not very big - it's only some lines of code we have to add to enable X.509 certificate authentication. But additional, some efforts will have to be made for the actual certificate management. Creation and Management of certificates can be done using two tools provided with your Visual Studio installation.

These tools are Certmgr.exe and Makecert.exe.

Certmgr

Certmgr.exe is the ECM Certificate Manager and handles tasks as certificate import/export and management. In detail, it has the following functions (see MSDN)

  • Displays certificates, CTLs, and CRLs to the console.
  • Adds certificates, CTLs, and CRLs to a certificate store.
  • Deletes certificates, CTLs, and CRLs from a certificate store.
  • Saves an X.509 certificate, CTL, or CRL from a certificate store to a file.

MSDN describes Certmgr as follows:

"Certmgr.exe works with two types of certificate stores: StoreFile and system store. It is not necessary to specify the type of certificate store; Certmgr.exe can identify the store type and perform the appropriate operations. Running Certmgr.exe without specifying any options launches a GUI that helps with the certificate management tasks that are also available from the command line. The GUI provides an import wizard, which copies certificates, CTLs, and CRLs from your disk to a certificate store."

Summarized, it's just a tool enabling us to handle installation, deletion and property management of certificates on our local system.

Makecert

The second tool, Makecert, can generate X.509 certificates for testing purposes only.

Let's again quote MSDN:

"[…] creates a public and private key pair for digital signatures and stores it in a certificate file. This tool also associates the key pair with a specified publisher's name and creates an X.509 certificate that binds a user-specified name to the public part of the key pair. "

For a detailed explanation of the supported commands have a look at the documentation.

For public distribution, one needs to purchase a certificate from a Certification Authority. So don't use the test certificates we create here for real world scenarios.

For our purposes it's sufficient to create a simple test certificate.

Typing the command

makecert -sk myNewKey -r -n "CN=Test" -ss root myNew.cer

creates such a test certificate, with a publisher's name "Test", and saved to a file called myNew.cer.

The syntax is as follows (taken from MSDN again - "Digital Code Signing Step-by-Step Guide"):

  • -sk subject key
    The location of the subject's key container which holds the private key. If a key container does not exist, one is created. If neither of the -sk or -sv options is used, a key container called JoeSoft will be created by default.
  • -r
    Creates a self-signed certificate.
  • -n name
    Name for the publisher's certificate. This name must conform to the X.500 standard. The simplest method is to use "CN=MyName" format. For example: -n "CN=Test".
  • -ss subjectCertStoreName
    Name of the subject's certificate store where the generated certificate will be stored.
  • myNew.cer
    Save the certificate to a file and name the file as saveCertificate.cer.

The certificate is created using the default test root. A key store named myNewKey is created, and the file is output both to a store and a file.

Adding X.509 Certificates to a SOAP Message

Now that we have created our own certificated we can start to do the coding. What we need for our application is a way to open certificate stores and access the certificates included. Luckily WSE provides us with the means to do that without any problems.
But before we start the practical part, let's have just a short look at ASP .NET security.

May I ... Permissions

In our example we want to access the current users root store. To do so, the application demanding access to this store must have the according rights. Typically, only the owner and the SYSTEM account are allowed to access the according files. Since your application could run under any user specific to your system, it's possible that you need to adapt the access right of the certificate-related file. If the sample application will be run as SYSTEM or the files owner, you can skip the next steps.
But otherwise we have some steps to do.

Typically, a process run by APS.NET will be done so using the ASPNET account, a low privilege user account. Due to security reasons, it will lack the rights to access the local machine's root store.

Now you have two choices: either specify the account used by ASP .NET in the machine.config file, at the key /configuration/system.web/processModel, changing the attributes userName and password. The default values are machine for userName and AutoGenerate for password. Machine means you are running as ASPNET. Change these values to a user with more the required rights - but note that this could open some doors to security-relevant parts of your system. So be aware of what you do…

The second option is my favourite, taken that it also works for applications not run as an ASP.NET process. This option involves adapting the rights of the files needed to be accessed by your application. These files can be found in the folder C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys. So perform the following steps to grant the user your app is executed as the required rights:

  • Open Windows Explorer.
  • Navigate to the C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys folder.
  • Select the needed files, and right-click them to open a menu from where to select Properties.
  • Open the Security tab, add the ASPNET account (or the account you need) and select the Full Control option.

Now you should be able to go on.
For more information on ASP.NET security read on at An Introductory Guide to Building and Deploying More Secure Sites with ASP.NET and IIS.

The new XML Header Element

How is an X.509 certificate added to and transmitted with a SOAP message? As we already know, all security information related to the WSE are added to the <security> header, introduced in my previous article. While we there added a <UsernameToken> element to enable username/password validation, this time we add a <BinarySecurityToken> element. This element will contain the public version of the certificate, with the certificate itself sent along as base64 encoded data.

The <BinarySecurityToken> attributes we need to use are:

  • ValueType
    defines what type of value is included, where three values are defined:
    • X509v3
      An X.509 version 3 certificate
    • Kerberosv5ST
      A ticket granting ticket as defined by section 5.3.1 of the Kerberos specification
    • Kerberosv5TGT
      A service ticket as defined by section 5.3.1 of the Kerberos specification

  • EncodingTpye
    how the value is encoded
    • wsse:Base64Binary
    • wsse:HexBinary

  • Id
    a unique identifier

Taken this, a <BinarySecurityToken> element passing an X.509 certificate could look like this:

<wsse:BinarySecurityToken
  ValueType="wsse:X509v3"
  EncodingType="wsse:Base64Binary"
  Id="SecurityToken-c7ff1a4e-276d-4af3-8837-1264ab9f69b3"
>
  MIIBtzCCAWGgAwIBAgIQz4ySuWmd/opBywS0LK...
</wsse:BinarySecurityToken>

So addding a security token using the WSE ends up having a <BinarySecurityToken> element added to the WSE security header.

Sending a Certificate

First action to do is to retrieve a certificate that will be added to our SOAP message.

We start with creating a new store object representing the store we want to open - in our case the CURRENT_USER\Root store. Typically, a client accesses the current user store, while the Web Service refers to the local machine store.

X509CertificateStore *store = X509CertificateStore::CurrentUserStore(
  X509CertificateStore::RootStore->

The function CurrentUserStore(String* storeName) creates a new X509CertificateStore using the specified store name, here represented by RootStore->ToString(). RootStore is a constant field representing the predefined system certificate store "Root". ToString() should simply return "Root". I encountered some problems doing it this way, because RootStore contained a string different from "Root", thus leading the call CurrentUserStore(…) to fail. So try simply typing "Root" if you have any problems.
Now open the store for reading purposes

store->OpenRead();

and find the certificate you'd like to use. Take one out of the following functions that fits best to your needs:
FindCertificateBySubjectString(String* subjectsubstring)
FindCertificateByHash(unsigned char certHash __gc[])
FindCertificateByKeyIdentifier(unsigned char keyIdentifier __gc[])
FindCertificateBySubjectName(String* subjectstring).

I think the function names are self-explaining. I chose the first method to find my certificate.

X509CertificateCollection *col = (X509CertificateCollection*)store
  ->FindCertificateBySubjectString("Hendrik");

All functions return an X509CertificateCollection object, so we simply take the first certificate inside the collection for our further steps.

X509Certificate *cert = col->get_Item(0);

Now having a certificate object, it may be added to our SOAP message and be used for all security purposes. So simply create a new X509SecurityToken object, representing the binary security token,

X509SecurityToken *x509st = new X509SecurityToken(cert);

and add it to the Tokens member of the requests SOAP security header - the same collection we added our UsernameToken in the previous article to.

ws->RequestSoapContext->Security->Tokens->Add(x509st);

Here the whole method:

void TestX509Certification()
{
  SimpleWebService *ws = new SimpleWebService();


  X509CertificateCollection *col = 0;
  try {
   X509CertificateStore *store = X509CertificateStore::
    LocalMachineStore( X509CertificateStore::RootStore ); 


    bool bRead = store->OpenRead();
    col = (X509CertificateCollection*)store->
      FindCertificateBySubjectString(S"Hendrik");
    // or use FindCertificateByHash or FindCertificateByKeyIdentifier or
    // FindCertificateBySubjectName
  }
  catch( System::InvalidOperationException *e) {
    throw new ApplicationException(S"Could not read certificate.");
  }

  if( col->Count == 0 )
        return;

  X509Certificate* cert = col->get_Item(0);

  // to be usable for signing, the certificate must support
  // digital signatures and a private key must be available
  if( cert->SupportsDigitalSignature && cert->Key != 0 ) {
    X509SecurityToken *x509st = new X509SecurityToken(cert);
    ws->RequestSoapContext->Security->Tokens->Add(x509st);
    ws->RequestSoapContext->Security->Elements->Add(new Signature(x509st));   
  }

  // set time-to-live to try to prevent some 
  // interceptor from replaying the message
  ws->RequestSoapContext->Timestamp->Ttl = 60000;

  Console::WriteLine(ws->HelloWorldX509());
}

Now all steps needed to add a X.509 certificate to a SOAP message are done. But this alone isn't much a way of authenticating anything. We currently just have a SOAP message containing a certificate that can be received and verified by the Web Service. But before we come to actually using that certificate, we implement the Web Service part of the sample.

Validating received Certificates

The entry point to the service' security part is just the same as in the last article - retrieving the message's SOAP context and looking for a security token, now being of type X509SecurityToken instead of UsernameToken. We keep the principal function of our sample Web Service, returning a simple string. I just called it HelloWorldX509 to clarify its purpose.

// get access to the SOAP context of the request
SoapContext* sc = HttpSoapContext::RequestContext;
if( sc == 0 )
  throw new ApplicationException(S"Only SOAP-requests allowed!");
  
bool valid = false;
SecurityToken *st = 0;

// look for a X509 security token
Enumerator *ie = sc->Security->Tokens->GetEnumerator();
while( ie->MoveNext() ) {
  st = (SecurityToken *)ie->get_Current();
  
  // verify that the token represents an X.509 certificate
  if( st != 0 && st->GetType()->Equals(__typeof(X509SecurityToken)) )
  {
  // ... do further security processing ...
  }
}

Signing a message using an X.509 Certificate

So now we have all prerequisites to finally secure our message, first using digital signing. Using the WSE, adding signatures to a SOAP message is fairly easy. You already know the SoapContext::Security::Tokens element, and now we make use of the SoapContext::Security::Elements property. This is a collection of security elements to be added to the message, like signatures and encryption keys. And here adding a signature is what we're going to do.

Adding a signature ends up with a new element added to the <Security> header. It is called <Signature>, and may appear zero or multiple times. The related namespace is http://www.w3.org/2000/09/xmldsig#.

On the server site, the WSE automatically validates signatures received. The validation fails if one of the following circumstances occur:

  • the syntax of the signature entry content does not conform to the XML signature specification
  • the core validation of the specification fails

These two possibilities are only cryptographic verification related. So another reason for the validation to fail is if the application, applying its own trust policy, rejects the message. A cause could be the signature created by an untrusted key.

The Client Part

On the client side, signing a message just means adding one new line after the part where we added our binary security token. Just create a new Signature object, passing our security token as an argument, and add this object to the Elements collection.

...
X509SecurityToken *x509st = new X509SecurityToken(cert);
ws->RequestSoapContext->Security->Tokens->Add(x509st);
ws->RequestSoapContext->Security->Elements->Add(new Signature(x509st));
...

Adding a signature is now already done.

Now let's have a look at the snippet of a SOAP message that has been signed as shown above.

<soap:Envelope>
  <soap:Header>
    <wsse:Security>
      <wsse:BinarySecurityToken ValueType="wsse:X509v3" 
        EncodingType="wsse:Base64Binary" 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" 
        wsu:Id="SecurityToken-f7997210-3be2-4588-9606-de24cba035ee"
      >
        MIIBtzCCAWGgAwIBAgIQz4ySuWmd/opByw...
      </wsse:BinarySecurityToken>

      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
          <CanonicalizationMethod Algorithm=
             "http://www.w3.org/2001/10/xml-exc-c14n#" />
          <SignatureMethod Algorithm=
               "http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
          <Reference URI="#Id-abf496e3-37e8-4fbb-9e62-b3b456b19d0b">
            <Transforms>
              <Transform Algorithm=
                "http://www.w3.org/2001/10/xml-exc-c14n#" />
            </Transforms>
            <DigestMethod Algorithm=
               "http://www.w3.org/2000/09/xmldsig#sha1" />
            <DigestValue>LIBpNIQph1yxuRuWmGXXNy9lEKA=</DigestValue>
          </Reference>
          <Reference>
            ...
          </Reference>
          ...
        </SignedInfo>
        <SignatureValue>ZqEf473ru9847a2TNbbInx1D...</SignatureValue>
        <KeyInfo>
          <wsse:SecurityTokenReference>
            <wsse:Reference 
         URI="#SecurityToken-f7997210-3be2-4588-9606-de24cba035ee" />
          </wsse:SecurityTokenReference>
        </KeyInfo>
      </Signature>
    </wsse:Security>
  </soap:Header>
  <soap:Body 
    wsu:Id="Id-abf496e3-37e8-4fbb-9e62-b3b456b19d0b" 
    xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
  >
    <HelloWorldX509 xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>

Notice the new element part of the security header, called <Signature>. It contains three important sub-elements: SignedInfo, SignatureValue and KeyInfo. SignedInfo defines the data that is being signed in this message, how it is canonicalized and what algorithm is used for computing the signature. Inside SignedInfo you'll see a list of reference elements, which actually define the elements of the message that are signed. The URI attribute now corresponds to the ID attribute of the element that is signed. In above snippet, the URI of the reference is in fact similar to the body element's ID, thus the body is included in the signature.
What you can't see in this snippet is that most elements of the header are signed by default. In detail these are (namespaces not included):

  • the Body element
  • the Created and Expires elements of the WS-Timestamp SOAP header
  • the Action, To, Id and From elements of the WS-Routing SOAP header

The Web Service

On the server side, it's some more lines of code involved. Remember that we left our HelloWorldX509 having found a valid X.509 certificate. Now we have to find a digital signature and verify its conformance with this certificate. So first gain access to the Elements property of the Security element and start iterating over it.

Object *obj = 0;
IEnumerator *iter = sc->Security->Elements->GetEnumerator();

while( iter->MoveNext() ) {
  obj = iter->get_Current();

For every element check whether it's of type Signature, what we are of course looking for.

if( obj && obj->GetType()->Equals(__typeof(Signature)) ) {

If successful, we only want to accept the message if the body is signed. To determine which parts of a message are signed check the SignatureOptions property of the Signature class. The SignatureOptions enumeration can be a combination of the following values:

IncludeNone Specifies that no part of the message is signed
IncludePath Specifies that the path element in the routing header should be signed
IncludePathAction Specifies that the action sub-element of the path element in the routing header should be signed
IncludePathFrom Specifies that the from sub-element of the path element in the routing header should be signed
IncludePathId Specifies that the id sub-element of the path element in the routing header should be signed.
IncludePathTo Specifies that the to sub-element of the path element in the routing header should be signed
IncludeSoapBody Specifies that the SOAP body should be signed
IncludeTimestamp Specifies that the timestamp should be signed.
IncludeTimestampCreated Specifies that the Created element of a timestamp should be signed.
IncludeTimestampExpires Specifies that the Expires element of a timestamp should be signed.

Since we want the body to be signed, check for the IncludeSoapBody value:

Signature *sig = (Signature*)obj;
if( (sig->SignatureOptions & SignatureOptions::IncludeSoapBody) != 0 ) {

Finally ensure that the message body was signed using a X.509 certificate. Here, you can also check for other security tokens like UsernameTokens. If all conditions are met, we know who sent the message and can be sure that the message arrived unaltered.

if( sig->SecurityToken->GetType()->Equals(__typeof(X509SecurityToken)) )
  valid = true;

The complete function body:

String __gc* SimpleWebService::HelloWorldX509()
{
  SoapContext* sc = HttpSoapContext::RequestContext;
  if( sc == 0 )
    throw new ApplicationException(S"Only SOAP-requests allowed!");

  // look for a X509 security token
  bool valid = false;
  SecurityToken *st = 0;

  IEnumerator *ie = sc->Security->Tokens->GetEnumerator();
  while( ie->MoveNext() ) {
    st = (SecurityToken *)ie->get_Current();

    // chech for an X509SecurityToken
    if( st != 0 && st->GetType()->Equals(__typeof(X509SecurityToken)) ) {
      // search signature
      Object *obj = 0;

      IEnumerator *iter = sc->Security->Elements->GetEnumerator();
      while( iter->MoveNext() ) {
        obj = iter->get_Current();

        if( obj && obj->GetType()->Equals(__typeof(Signature)) ) {
          Signature *sig = (Signature*)obj;

          if( (sig->SignatureOptions & 
              SignatureOptions::IncludeSoapBody) != 0 ) {

            if( sig->SecurityToken->GetType()->Equals(
                  __typeof(X509SecurityToken)) ) {
              valid = true;
              break;
            }
          }
        }
      }
      if( valid )
        break;
    }
  }

  if( valid == false )
    throw new ApplicationException(S"Invalid or missing security token");
   
  return S"Hello! X.509 certificate received.";
}

Now we had a look at a first use of X.509 certificates, signing messages. But note that you don't need a certificate to sign a message - it can be even done using user name and password. This means that we can simply extend our previous article sample, using UsernameTokens for authentication, to sign the message.
Having an instance of UsernameToken called ut, simply type

ws->RequestSoapContext->Security->Elements->Add(new Signature(ut));

instead of

ws->RequestSoapContext->Security->Elements->Add(new Signature(x509st));

And viola, the message is signed using a user name. On the server side, validation is done similar to our validation process - only difference is that you'd now check for the signature's security token being of type UsernameToken instead of X509SecurityToken.

Signing Parts of a Message

In the previous scenario, we signed the whole body of the SOAP message. But in some cases, this may be unnecessary or even unwanted. Just remember what I mentioned at the beginning: "any changes made on the signed data invalidate the whole signature.". So taken the case an intermediary wants to modify parts of the body, our whole signature would become invalid. The solution is to sign just the parts of the message required to be signed.

To sign only a part of a message, specify an Id attribute for every element that you want to sign and add a reference, as automatically done for the header elements, to those elements. This is the way WSE creates partial signatures for the various options that the SignatureOptions enumeration offers. Beside that you can create the Id attribute yourself and manually add a reference to the SignedInfo part of the signature. This allows you to sign only certain parts of the body or the SOAP header.

The Web Service again ...

To have our example use partial signing of messages, we add a custom SOAP header that will be signed to our Web Service. The header is just simple, representing kind of a timestamp. Below the class implementing our header:

public __gc class MyHeader : 
  public System::Web::Services::Protocols::SoapHeader
{
public:
  [System::Xml::Serialization::XmlAttributeAttribute("Id", 
    Namespace="http://schemas.xmlsoap.org/ws/2002/07/utility")]
  String *Id;

  DateTime Created;
  DateTime Expires;
};

As you can see, the header contains just 3 members. Created and Expires are DateTime values indicating when the header was created and when it will be expired. That's similar to what the Timestamp header does. The third property is a String called Id. Its value is the Id of the header element which will be referenced by the SignedInfo/Reference element. The Id must be a message-wide unique identifier that follows the rules of the xsd:Id type defined at http://www.w3.org/2001/XMLSchema. If the rules are not followed for the xsd:Id type, the WSE throws an exception with a message text of "Malformed reference". The namespace of the attribute must be http://schemas.xmlsoap.org/ws/2002/07/utility, and this we pass to the XmlAttributeAttribute constructor.

Now add the custom header to the Web Service by inserting a member of type MyHeader.

MyHeader* myHeader;

To be accessible during a function call, you must specify which functions are using that header. This can be easily done by adding the following line just above the function declaration:

...
[SoapHeaderAttribute("myHeader", Direction=SoapHeaderDirection::In)]
String __gc* HelloWorld_UsingMyHeader();
...

myHeader is the attribute representing our header, and SoapHeaderDirection::In means that the header is to be used only for incoming messages. Other possible values would be SoapHeaderDirection::InOut and SoapHeaderDirection::Out. Now, our new header will be accessible inside the corresponding functions through the myHeader member variable.

To ensure that our header is being signed by the person we expect, we have to check it by examining all Signature elements of the security header again. Having found a Signature object as before, we now aren't interested in whether the body is signed. Instead, we retrieve all references the Signature contains and check if one of these references to our header element.

IEnumerator* refIter = sig->SignedInfo->References->GetEnumerator();
while( refIter->MoveNext() ) {
  ref = (Reference*)refIter->get_Current();

Finding our header to be signed is a simple comparison of the header Id, accessible through myHeader->Id, and the reference's Uri member, which actually points to this Id.

if( ref->Uri->Equals(String::Format("#{0}", myHeader->Id)) ) {
  ...
}

If this line is successful, we know that our header has been signed using the senders certificate, and that it has not been altered. To make the changes visible to our client, rebuild the project and include the new Dll in your IIS' Web Service folder.

Client Changes

The client part is not more difficult. To add the new header to our message, we first create a MyHeader object.

MyHeader* header = new MyHeader();

Having created a unique id by help of the Guid class, we can assign our Id member of the header.

Guid refId = Guid::NewGuid();
header->Id = String::Format(S"Id-{0}", refId.ToString());

Then just initialize the Created and Expires values and add the header to our Web Service. If you followed my naming conventions, wsdl.exe will have created a member called MyHeaderValue to your Web Service proxy that then will carry our header object.

header->Created = DateTime::UtcNow;
header->Expires = header->Created.AddMinutes(5);
ws->MyHeaderValue = header; 

Last thing to do is to manually add a reference to our header to the signature we use.

Signature* sig = new Signature(x509st);
Reference* ref = new Reference(String::Format(S"#{0}", header->Id));
sig->AddReference(ref);

As you can see, the reference is initialized with a string equal to the header's id, only with a # prefix, indicating the reference.

Now any call to the Web Service method expecting the MyHeader element and lacking it will be rejected by WSE. Additionally, we can reject the message if the header is not signed or if the signature is due to whatever reason invalid.

Using Certificates to encrypt Messages

To get the sample work, we again need at least a test X.509 certificate. The WSE encrypts a message using the public key of the receiver's certificate, and decrypts the message using the receiver's private key. So since our client will encrypt the message, a vaild public key of the receiver's certificate needs to reside inside the Personal certificate store for the current user account. Additionally, for the server side, the private key for this certificate must be present in the Personal key store for the local machine account. And in addition, one of the certificates in the CA certificate chain must be present in the server's Trusted store so that the WSE knows to trust the incoming certificate. For test certificate, this will be typically the Root Agency certificate.

Having handled the mechanisms of signing messages, encryption is a small step to go. Encrypting messages (or parts of) and the process of decryption are very similar to the steps we took to sign a message. It's again based on an X.509 certificate which is used to actually encrypt the content. To be used for encryption, the Key Usage property of the certificate must include Data Encipherment. Whether a certificate is suitable can be easily found out by checking the SupportsDataEncryption property of the X509Certificate class.

So let's first handle the client side again.

Sending an encrypted Message

Taking our method from the Signing chapter, all we have to add are the following lines:

// check whether the certificate can be used for encryption purposes
if( cert->SupportsDataEncryption ) {
  EncryptedData* ed = new EncryptedData(x509st);
  ws->RequestSoapContext->Security->Elements->Add(ed);
} 

It's obvious that the process of encrypting a message is completely similar to signing it. Just create a new instance of EncryptedData, passing the security token to use, and add it to the security header elements.

So how do these lines change our SOAP message? First, an <EncryptedKey> element will be added to the security header. Second, an <EncryptedData> element replaces the original data contained in the body tag. In detail, every content to be encrypted has to be replaced by an <EncryptedData> element. The <EncryptedKey> should in turn contain a reference to every data encrypted according to the information provided there.

With these new elements, an encrypted SOAP message's security header includes the following part:

<xenc:EncryptedKey 
  Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey" 
  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
>
  <xenc:EncryptionMethod Algorithm=
          "http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
  <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
    <wsse:SecurityTokenReference>
      <wsse:KeyIdentifier ValueType="wsse:X509v3">
           xnps3T2iiIgwG7adOhG9liCyxI4=</wsse:KeyIdentifier>
    </wsse:SecurityTokenReference>
  </KeyInfo>
  <xenc:CipherData>
    <xenc:CipherValue>usAVvHo+pm2cL...</xenc:CipherValue>
  </xenc:CipherData>
  <xenc:ReferenceList>
    <xenc:DataReference 
URI="#EncryptedContent-163b4068-5237-47ee-b1cb-a3fb655149e9"  />
  </xenc:ReferenceList>
</xenc:EncryptedKey>

We recognize the new element, <EncryptedKey>, and the sub-elements <EncryptionMethod>, <KeyInfo>, <CipherData> and <ReferenceList>. <KeyInfo is> used for carrying the key information, allows different key types and provides extensibility. The <ReferenceList> again contains references to the elements encrypted using that key.

The only encrypted element in this example is the body, originally containing just one element:

<HelloWorldX509_Extended xmlns="http://tempuri.org/" />

Note that whenever you don't specify anything else, the body is the only element encrypted.

Thats the call to the Web Service Method. Now being encrypted, it is replaced by an EncryptedData element, as follows:

<xenc:EncryptedData 
  Id="EncryptedContent-163b4068-5237-47ee-b1cb-a3fb655149e9" 
  Type="http://www.w3.org/2001/04/xmlenc#Content" 
  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
>
  <xenc:EncryptionMethod Algorithm=
        "http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
  <xenc:CipherData>
    <xenc:CipherValue>EVb5U61VrXSawk...</xenc:CipherValue>
  </xenc:CipherData>
</xenc:EncryptedData> 

Receiving encrypted Messages

The server side doesn't require any coding action of us. The WSE, if installed and configured properly, automatically recieves the certificate given and checks its validity. If successful, the message is decrypted and proceeded. Otherwise, it is rejected and a failure code will be sent back to the sender of the message.

The first thing needed for decryption to work is the following entry in the Web.config file:

<configuration>
  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="Microsoft.Web.Services.WebServicesExtension, 
          Microsoft.Web.Services,Version=1.0.0.0, Culture=neutral, 
          PublicKeyToken=31bf3856ad364e35" 
          priority="1" group="0"/>
      </soapExtensionTypes>
    </webServices>
  </system.web>
</configuration>

It enables the Web Service to use the WSE. But since we already did that as part of the previous article, there is nothing to do for us here.

Now we only need to tell the WSE where to look for the private key of the certificate, which is needed for the decryption. Since we put it in the Personal store for the local machine account, this is where we point to. This is done by adding an <x509> element to /configuration/microsoft.web.services/security in Web.config. The associated value is storeLocation, which we will assign the value LocalMachine for the local machine account. The other possible value would be CurrentUser, but this is more practible for clients receiving encrypted messages.

Second value to set is verifyTrust, which must be set to false to allow test certificates to be trusted. Never allow this in a productive environment, but for test purposes only.

<configuration>
  <microsoft.web.services>
    <security>
      <x509 storeLocation="LocalMachine" verifyTrust="false" 
        allowTestRoot="true" />
    </security>
  </microsoft.web.services>
</configuration>

Specifying Parts to be encrypted

Encrypting only parts of a SOAP messages is similar to partial signing of messages. We again need the id of the element to be encrypted. In case we want to encrypt our custom header, the id has to be created the way we already did. Then, we create a EncryptedData object, with the security token as first parameter. So the only difference to encrypting the whole body only is to add a second parameter, the reference to the Id.

String *ref = String::Format(S"#{0}", header->Id);
EncryptedData* ed = new EncryptedData(x509st, ref);

Then add the element to the security elements:

ws->RequestSoapContext->Security->Elements->Add(ed);

Regarding the client, determining the encrypted parts is again similar to partial signatures. You need to iterate over the Elements collection of the Security property, and to determine whether the current object is of type EncryptedData. For each element in the SOAP message that is encrypted, there will be such an EncryptedData element in the Elements collection.

A code snippet would look like follows.

Object *obj = 0;
IEnumerator *iter = sc->Security->Elements->GetEnumerator();
while( iter->MoveNext() ) {
  obj = iter->get_Current();
  if( obj && obj->GetType()->Equals(__typeof(EncryptedData)) ) { 
    // do security related processing
  }
}

I didn't implement these things in my example since the technique doesn't differ from signing parts of a message. But I'm sure you won't have any problems implementing such a scenario.

Testing the Example

Installing the example is similar to the previous article, so please have a look there regarding any questions.

Now the client contains three different functions:

  • TestAuthentication()
    • uses username/password authentication, and is just the function of the previous article
    • calls the Web Service's HelloWorld() function
  • TestX509Certification()
    • uses an X.509 certificate to authenticate, and the message is signed and encrypted with this certificate
    • calls HelloWorldX509() on the server side
  • TestX509Certification_Extended()
    • encrypts the messge using an X.509 certificate, additionaly sending and encryptin a custom message header
    • the Web Service's counterpart is HelloWorldX509_Extended()

For testing just call one of the functions above.

Read on ...

... at

Perspective

Now having the first insight on encryption and signatures, the next article will continue handling encryption using a shared secret. Also, I'm going to introduce custom binary security tokens.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

HENDRIK R
Web Developer
Germany Germany
Gained first computer game experiences on Atari and Commodore C64. Improved the gaming skills for about 10 years, then had my first Windows95 installed and wondered how to develop own applications.
 
Since that time playing around with C++, JAVA etc., from time to time trying to broaden my horizon with some new stuff. Currently I'm a student in computer science, spending more time in working as a freelancer than visiting any courses. Still confident of managing to finish my studies in 2005.

Comments and Discussions

 
Generalwaiting for next artical Pinmembercoolvcguy12-Jun-04 1:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 2 Jun 2004
Article Copyright 2004 by HENDRIK R
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid