WS-Security

La sécurité dans les services web est rendue plus difficile parce qu'ils sont distribués et sans état.

Le terme WS-Security est habituellement utilisé pour référence un groupe de spécifications qui traite du chiffrage (encryption) des données et des signatures digitales, permettant ainsi de créer une application sécurisée.

La spécification de base SOAP, ne définit rien pour sécuriser les messages. Elle laisse cette tâche à une autre spécification. Le transport des messages SOAP se fait normalement sur HTTP, ce qui signifie que chaque message transite par un ou plusieurs intermédiaires, chacun pouvant lire et altérer le message.

Il faut donc un moyen pour éviter qu'une personne non autorisée puisse lire / altérer le message. De même, il ne faut pas qu'une personne non-autorisée puisse envoyer un message.

La spécification SOAP prévoit d'ajouter des informations de sécurité dans l'en-tête de l'enveloppe mais ne spécifie pas sous quelle forme. C'est là qu'entre en jeu WS-Security.

Trois problèmes majeurs sont impliqués dans la sécurisation des échanges de messages SOAP et WS-Security fournis des réponses pour chacun d'eux, mais pas directement. En fait, il s'agit d'une spécification qui n'explique pas comment protéger le message, mais comment avertir le destinataire que le message est protégé. Pour la protection réelle, WS-Security se base sur des spécifications additionnelles.

1) Identifier et authentifier le client

Plusieurs possibilités existent pour créer un token de sécurité. WS-Security ne définit pas une manière particulière, mais plutôt comment ces différents token doivent être transféré dans les messages SOAP. En d'autres mots, il permet au destinataire de savoir comment extraire les token du message pour traitement.

2) Intégrité du message

WS-Security utilise la signature digitale pour celà, en utilisant la spécification XML Signature. XML Signature est une recommandation W3C qui fournit un mécanisme pour signer digitalement des documents XML.

3) Eviter l'écoute clandestine

Une fois encore, WS-Security utilise un autre standard W3C: XML Encryption qui fournit un mécanisme pour encrypter un document XML.

Pour sécuriser un service il n'y a pas besoin de toucher les classes java actuelles, mais celà nécessite pas mal de traitement annexes.

A) Création du répertoire de clés

Le répertoire de clé stocke toutes les paires privées de clés publiques. Pour générer ces clés, il faut utiliser l'application 'keytool' disponible avec le JDK.

Une clé peut (doit) être créée pour chaque utilisateur autorisé comme ci-dessous.

>cd %JAVA_HOME%\bin
>keytool -genkey -keystore mykeys.jks -alias gene
Enter keystore password:  mykeystorepassword
What is your first and last name?
  [Unknown]:  Gene Telluride
What is the name of your organizational unit?
  [Unknown]:  Information Technologies
What is the name of your organization?
  [Unknown]:  The Daily Moon
What is the name of your City or Locality?
  [Unknown]:  New York
What is the name of your State or Province?
  [Unknown]:  NY
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=Gene Telluride, OU=Information Technologies, O=The Daily Moon, 
L=New York, ST=NY, C=US correct?
  [no]:  yes
 
Enter key password for 
  (RETURN if same as keystore password):  mypassword

La ligne de commande ci-dessus génère une paire de clé et la stocke dans le fichier mykeys.jks. Elle possède un alias 'gene' qui permet de facilement la référencer. Un autre utilisateur peut créer sa clé et la stocker dans le même fichier.

B) Activer la sécurité côté serveur

La première étape pour l'ajout au service de WS-Security consiste à l'activer sur le serveur. Par exemple en ajoutant un timestamp pour les messages entrants et sortants. Maintenant, lors de l'appel du service par un client sans timestamp, le serveur retourne une erreur au lieu de l'information attendue.

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope 
     xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
     xmlns:wsa="http://www.w3.org/2005/08/addressing">
 <soapenv:Header>
  <wsa:ReplyTo>
   <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
  </wsa:ReplyTo>
  <wsa:MessageID>urn:uuid:1CA9E94A9C7FE9D5B311507328796251</wsa:MessageID>
  <wsa:Action>http://www.w3.org/2005/08/addressing/fault</wsa:Action>
 </soapenv:Header>

 <soapenv:Body>
  <soapenv:Fault>
   <faultcode>soapenv:Client</faultcode>
   <faultstring>
    WSDoAllReceiver: Request does not contain required Security header
   </faultstring> 
   <detail>
    <Exception>
     org.apache.axis2.AxisFault: WSDoAllReceiver: Request does not contain 
     required Security header at ...
    </Exception>
   </detail>
  </soapenv:Fault>
 </soapenv:Body>
</soapenv:Envelope>

C) Activer la sécurité côté client

En quelque sorte, le client est une sorte de serveur du fait qu'il envoye et reçoit lui aussi des messages SOAP. Il faut aussi lui indiquer de rajouter un timestamp au message qu'il envoye au serveurs.

La requête

Une fois le client configuré, pour la première fois, apparaissent dans l'en-tête, des informations de sécurité. Il ne s'agit que d'un timestamp qui indique quand le message a été créé. L'attribut 'mustUnderstand' signifie que si le serveur ne sait pas quoi faire avec ce timestamp, il doit rejeter le message.
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Header>
  <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/
                                oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">
   <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/
                                oasis-200401-wss-wssecurity-utility-1.0.xsd"
                  wsu:Id="Timestamp-29987161">
    <wsu:Created>2006-06-19T16:22:28.578Z</wsu:Created>
    <wsu:Expires>2006-06-19T16:27:28.578Z</wsu:Expires>
   </wsu:Timestamp>
  </wsse:Security> 
 </soapenv:Header>

 <soapenv:Body>
  <cms:getNumberOfArticles xmlns:cms="http://daily-moon.com/cms">
   <cms:category>classifieds</cms:category>
  </cms:getNumberOfArticles>
 </soapenv:Body>
</soapenv:Envelope>

Les timestamps sont certainement les éléments les plus simples de WS-Security. Ils fournissent une solution pour limiter la durée de vie des messages, prévenant ainsi l'interception des messages par des malfaiteurs. Dans notre exemple, le timestampe expire 5 secondes après la création du message. Un malfaiteur peut toujours modifier le timestamp mais celà peut être réglé avec les signatures.

La réponse

La réponse inclus le timestamp, mais informe aussi que les informations de sécurité ont été traitées et vérifiées.

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Header>
  <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/
                                oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">
   <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/
                                oasis-200401-wss-wssecurity-utility-1.0.xsd"
                 wsu:Id="Timestamp-22347273">
    <wsu:Created>2006-06-19T16:22:29.281Z</wsu:Created>
    <wsu:Expires>2006-06-19T16:27:29.281Z</wsu:Expires>
   </wsu:Timestamp>
   <wsse11:SignatureConfirmation
              xmlns:wsse11="http://docs.oasis-open.org/wss/2005/xx/
              oasis-2005xx-wss-wssecurity-secext-1.1.xsd"
              xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/
              oasis-200401-wss-wssecurity-utility-1.0.xsd" 
              wsu:Id="SigConf-5759024" />
  </wsse:Security> 
 </soapenv:Header>

 <soapenv:Body>
  <resp:numberOfArcticles 
           xmlns:resp="http://daily-moon.com/cms/"
           xmlns:tns="http://ws.apache.org/axis2">
   42
  </resp:numberOfArcticles>
 </soapenv:Body>
</soapenv:Envelope>

Partie 4: Signer les messages

L'ajout d'un timestamp ne garantit pas que quelqu'un de mal attentionné le change et renvoye le message. Afin de résoudre ce problème (et tout les autres problèmes pouvant découler d'une modification du message d'origine), il faut signer le message.

Le processus de signature à mettre en place se déroule en plusieurs étapes. Première étape: signer le message ou une portion du message. En général celà se passe en envoyant l'alias de l'utilisateur vers une classe 'callback' qui retourne le mot de passe pour cet utilisateur. A partir de ce mot de passe, on peut récupérer la clé privée de cet utilisateur.

A partir de cette clé privée, le framework de sécurité encrypte, ou signe, la partie approprié du message et ajoute cette signature au message qui peut ensuite être envoyé. Lorsque le service reçoit le message, il fait presque la même chose; il récupère la clé public de cet utilisateur, et ensuite vérifie la signature.

La classe de callback

Elle implémente CallbackHandler et doit donc implémenter la méthode handle(). Dans l'exemple ci-dessous, le mot de passe de l'utilisateur reçu en paramètre est setté au WSPasswordCallback

import org.apache.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

public class PWCallback implements CallbackHandler {

 public void handle(Callback[] callbacks)throws IOException, 
                                                UnsupportedCallbackException {

  for (int i = 0; i < callbacks.length; i++) {
   if (callbacks[i] instanceof WSPasswordCallback) {
    WSPasswordCallback pc=(WSPasswordCallback)callbacks[i];

    if (pc.getIdentifer().equals("gene")) {
     pc.setPassword("mypassword");
    } else if (pc.getIdentifer().equals("frances")) {
     pc.setPassword("francespassword");
    } else {
     throw new UnsupportedCallbackException(callbacks[i], "Unknown user");
    }
   } else {
    throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Cb");
   }
  }
 }
}

Lors de l'utilisation d'un keystore (répertoire de clés), il faut spécifier où le trouver et quelle classe s'occupe de la cryptographie. Avec Axis2, celà se fait via un fichier properties

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=mykeystorepassword
org.apache.ws.security.crypto.merlin.file=mykeys.jks

Il faut ensuite spécifier que lors de l'envoi du message par le client, une signature sera ajoutée en lieu et place du timestamp. Pour le bon fonctionnement, il faut spécifier, où trouver les informations du keystore, quelle classe de Callback utiliser, et quelle partie du message sera signée.

<axisconfig name="AxisJava2.0">

 <!-- Engage the security module -->
 <module ref="rampart"/>

  <parameter name="OutflowSecurity">
   <action>
    <items>Signature</items>
    <user>gene</user>
    <passwordCallbackClass>PWCallback</passwordCallbackClass>
       
    <signaturePropFile>security.properties</signaturePropFile>
    <signatureKeyIdentifier>SKIKeyIdentifier</signatureKeyIdentifier>
    <signatureParts>
     {Element}{http://schemas.xmlsoap.org/soap/envelope/}Body
    </signatureParts>
   </action>
  </parameter>
  <!--
  <parameter name="InflowSecurity">
   <action>
    <items>Timestamp</items>
   </action>
  </parameter>
  -->
...

Bien sur il est possible de ne signer qu'une partie du message. Par exemple, c'est souvent le timestamp qui est signé, s'il est présent.

Tous ces changements rajoutent plein d'informations dans la requête comme ci-dessous.

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

 <soapenv:Header>
  <wsse:Security xmlns:wsse="..." soapenv:mustUnderstand="1">
   <ds:Signature xmlns:ds="..." Id="Signature-8789796">
    <ds:SignedInfo>
     <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
     <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />

     <ds:Reference URI="#id-17764792">
      <ds:Transforms>
       <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      </ds:Transforms>
      <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
      <ds:DigestValue>wg+9KsR6BVBiO/hakJJwMdtU7+I=</ds:DigestValue>
     </ds:Reference>
    </ds:SignedInfo>

    <ds:SignatureValue>hom9Enzu3yHBuaF...</ds:SignatureValue>
    <ds:KeyInfo Id="KeyId-19475750">
     <wsse:SecurityTokenReference xmlns:wsu=".." wsu:Id="STRId-31156635">
      <wsse:KeyIdentifier
              EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-
              wss-soap-message-security-1.0#Base64Binary"
              ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-
              wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">
       CuJdE1B2dUFd1dkLZSzQ5vj6MYg=
      </wsse:KeyIdentifier>
     </wsse:SecurityTokenReference>
    </ds:KeyInfo>
   </ds:Signature>
  </wsse:Security>

 </soapenv:Header>

 <soapenv:Body xmlns:wsu="..." wsu:Id="id-17764792">
  <cms:getNumberOfArticles xmlns:cms="http://daily-moon.com/cms">
   <cms:category>classifieds</cms:category>
  </cms:getNumberOfArticles>
 </soapenv:Body>
</soapenv:Envelope>

Changements à effectuer au service

Pour que le service puisse traiter cette signature, il faut qu'il effectue les mêmes opérations que le client à savoir: récupérer les mots de passe (keystore, Callback.class, security.properties)

La réponse contient un timestamp et inclus des informations pour indiquer la vérification de la signature.

<?xml version='1.0' encoding='UTF-8'?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Header>
  <wsse:Security xmlns:wsse="..." soapenv:mustUnderstand="1">
   <wsu:Timestamp xmlns:wsu="..." wsu:Id="Timestamp-27995990">
    <wsu:Created>2006-06-19T23:56:55.214Z</wsu:Created>
    <wsu:Expires>2006-06-20T00:01:55.214Z</wsu:Expires>
   </wsu:Timestamp>
	 
   <wsse11:SignatureConfirmation xmlns:wsse11="..." xmlns:wsu="..." 
	     Value="hom9Enzu3yHBuaigFl26b6A+5hy..." wsu:Id="SigConf-25877728" />
  </wsse:Security>
 </soapenv:Header>
 
 <soapenv:Body>
  <resp:numberOfArcticles xmlns:resp="http://daily-moon.com/cms/"
	 xmlns:tns="http://ws.apache.org/axis2">42</resp:numberOfArcticles>
 </soapenv:Body>
</soapenv:Envelope>