GIDS Cards

GIDS Cards are wonderful gifts from Vincent Le Toux (@mysmartlogon), they can transform JavaCards (2.2.1 or +) into universal SmartCards for Windows (7/2008r2 or +), without any middleware/crappy software to install.

You can find more information at: https://www.mysmartlogon.com/generic-identity-device-specification-gids-smart-card/ & https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642100(v=vs.85)

Prerequisites

Building the applet

Download Vincent’s GIDS applet source code (clone the repository, download master branch, …) then go to its directory.
After adjusting line 1 & 2 to correct paths, you can build the binary.

set JC_HOME=c:\security\javacard\java_card_kit-2_2_1
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_291
set PATH=%JC_HOME%\bin;%JAVA_HOME%\bin;%PATH%

javac -Xlint:-options -g -source 1.2 -target 1.2 ^
-classpath %JC_HOME%\lib\api.jar ^
src\com\mysmartlogon\gidsApplet\*.java

java -classpath %JC_HOME%\lib\converter.jar;%JC_HOME%\lib\offcardverifier.jar com.sun.javacard.converter.Converter ^
-out CAP -exportpath %JC_HOME%\api_export_files -classdir src ^
-applet 0xa0:0x00:0x00:0x03:0x97:0x42:0x54:0x46:0x59:0x02:0x01 com.mysmartlogon.gidsApplet.GidsApplet ^
com.mysmartlogon.gidsApplet 0xa0:0x00:0x00:0x03:0x97:0x42:0x54:0x46:0x59 1.0

Applet will be in: src\com\mysmartlogon\gidsApplet\javacard\gidsApplet.cap

You can check some informations:

c:\security\javacard\GidsApplet-master>java -jar ..\gp.jar --info --cap src\com\mysmartlogon\gidsApplet\javacard\gidsApplet.cap
GlobalPlatformPro v20.01.23-0-g5ad373b
Running on Windows 10 10.0 amd64, Java 1.8.0_291 by Oracle Corporation
**** CAP info of gidsApplet.cap
CAP file (v2.1), contains: applets for JavaCard 2.2.1
Package: com.mysmartlogon.gidsApplet A00000039742544659 v1.0
Applet:  A000000397425446590201
Import:  A0000000620001                   v1.0 java.lang
Import:  A0000000620101                   v1.2 javacard.framework
Import:  A0000000620102                   v1.2 javacard.security
Import:  A0000000620201                   v1.2 javacardx.crypto
Generated by Sun Microsystems Inc. converter 1.3
On Thu Jun 24 14:29:36 CEST 2021 with JDK 1.8.0_291 (Oracle Corporation)
Code size 14965 bytes (17973 with debug)
SHA-256 ec1a1a642dbac5087ae9051c04c13c33734bd83a89139d5d30cfc238ea8d9832
SHA-1   a4379a1880e6f28c4dd3f6d4105b5ede5e59d8c9

Automated build

An automated build is available at: https://ci.appveyor.com/project/gentilkiwi/gidsapplet (for logs & artifacts).

Installing the applet in a JavaCard

  1. Tests were made with:
  2. Prefer contact readers to avoid problems during installations (or even key generations) – if interface includes contactless too, you’ll be able to use it after.
  3. Interacting with an incorect authentication key can brick the card, examples here are with default keys

Selecting reader

In case of multiple smartcard readers, you can list them to know their names

java -jar gp.jar --verbose 2>NUL | findstr /i Reader
Reader: ACS ACR122 0
# ACS ACR39U ICC Reader 0
Reader: ACS ACR39U ICC Reader 0
Reader: certgate GmbH AirID BLE 0
# HID Global OMNIKEY 5022 Smart Card Reader 0
Reader: HID Global OMNIKEY 5022 Smart Card Reader 0
Reader: OMNIKEY CardMan 3x21 0
Reader: Windows Hello for Business 1

List content

For A40

c:\security\javacard>java -jar gp.jar --reader "ACS ACR39U ICC Reader 0" --key 404142434445464748494a4b4c4d4e4f --list
ISD: A000000003000000 (OP_READY)
Privs:   SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement

For A22 – one default applet is present

c:\security\javacard>java -jar gp.jar --reader "ACS ACR39U ICC Reader 0" --key 404142434445464748494a4b4c4d4e4f --list
ISD: A000000003000000 (OP_READY)
Parent:  A000000003000000
From:    A0000000620001
Privs:   SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

PKG: A0000001515350 (LOADED)
Applet:  A000000151535041

Installing

… then listing

c:\security\javacard\GidsApplet-master>java -jar ..\gp.jar --reader "ACS ACR39U ICC Reader 0" --key 404142434445464748494a4b4c4d4e4f --install src\com\mysmartlogon\gidsApplet\javacard\gidsApplet.cap
CAP loaded

c:\security\javacard\GidsApplet-master>java -jar ..\gp.jar --reader "ACS ACR39U ICC Reader 0" --key 404142434445464748494a4b4c4d4e4f --list
ISD: A000000003000000 (OP_READY)
Privs:   SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement

APP: A000000397425446590201 (SELECTABLE)
Privs:

PKG: A00000039742544659 (LOADED)
Version: 1.0
Applet:  A000000397425446590201

(optional) Delete the applet (optional)

java -jar ..\gp.jar --reader "ACS ACR39U ICC Reader 0" --key 404142434445464748494a4b4c4d4e4f --delete A00000039742544659

Card initialization

Identifying smartcard readers

c:\security\javacard>opensc_tool --list-readers
# Detected readers (pcsc)
Nr.  Card  Features  Name
0    Yes             ACS ACR122 0
1    Yes             ACS ACR39U ICC Reader 0
2    Yes             certgate GmbH AirID BLE 0
3    Yes             HID Global OMNIKEY 5022 Smart Card Reader 0
4    Yes             OMNIKEY CardMan 3x21 0
5    Yes             Windows Hello for Business 1

Initialization

…change values, of course!

c:\security\javacard>gids_tool --reader 1 --initialize ^
--admin-key 000000000000000000000000000000000000000000000000 ^
--pin 0000 ^
--serial-number 00000000000000000000000000000000

Test

c:\security\javacard>certutil -scinfo "ACS ACR39U ICC Reader 0"
Le gestionnaire de ressource des cartes à puce est en cours d’exécution.
État de la carte/lecteur actuel :
Lecteurs : 1
0: ACS ACR39U ICC Reader 0
--- Lecteur : ACS ACR39U ICC Reader 0
--- Statut : SCARD_STATE_PRESENT | SCARD_STATE_UNPOWERED
--- Statut : Carte disponible pour utilisation.
---   Carte : Identity Device (Microsoft Generic Profile)
---    ATR :
3b 9f 95 81 31 fe 9f 00  66 46 53 05 10 00 ff 71   ;...1...fFS....q
df 00 00 00 00 00 ec                               .......

Cryptographie rapide sous Windows

Comment obtenir un hash MD5 sous Windows, déchiffrer un bloc DES ?

j’ai totalement conscience d’évoquer dans ce post des algorithmes considérés comme dépassés, mais ces derniers sont toujours massivement utilisés dans les mécanismes internes de Windows
mimikatz 2.0 s’en sert lui aussi massivement ;)

Limitations

Les algorithmes évoqués dans ce post sont seulement les suivants :

MD4 LM DES (ECB)
MD5 NTLM RC4
SHA1    

Déjà vu sur Internet *

  • assemblage de bouts de code provenant d’Internet (réimplémentation des algorithmes)
  • utilisation des librairies d’OpenSSL
  • utilisation de la CryptoAPI
  • utilisation de la CNG

* je passe sur l’utilisation de CSP/PKCS#11 pour communiquer avec son HSM préféré ;)

A notre disposition sous Windows

Comme déjà listé, il est possible d’utiliser nativement la CryptoAPI, ou sa nouvelle version la CNG… mais n’est-ce-pas un peu lourd pour de si simples besoins ?

Windows incorpore déjà beaucoup de routines dans ses librairies système et, si l’on en utilise une grande partie via les SDK référençant ces API, l’on en occulte quelques-unes très pratiques !

Une majorité de ces API se situent dans la librairie advapi32.

API clairement identifiées

MD4Init MD5Init A_SHAInit
MD4Update MD5Update A_SHAUpdate
MD4Final MD5Final A_SHAFinal

API derrières SystemFunctionXXX

RtlEncryptDES1block1key SystemFunction001
RtlDecryptDES1block1key SystemFunction002
RtlEncryptDESMagicBlock1key SystemFunction003
RtlEncryptDESblocksECB SystemFunction004
RtlDecryptDESblocksECB SystemFunction005
RtlDigestLM SystemFunction006
RtlDigestNTLM SystemFunction007
RtlLMResponseToChallenge
NTLMv1 aussi sans doute
SystemFunction008
SystemFunction009
RtlDigestMD4only16Bytes SystemFunction010
SystemFunction011
RtlEncryptDES2blocks2keys SystemFunction012
SystemFunction014
SystemFunction020
SystemFunction022
RtlDecryptDES2blocks2keys SystemFunction013
SystemFunction015
SystemFunction021
SystemFunction023
RtlEncryptDES2blocks1key SystemFunction016
SystemFunction018
RtlDecryptDES2blocks1key SystemFunction017
SystemFunction019
RtlEncryptDES2blocks1DWORD SystemFunction024
SystemFunction026
RtlDecryptDES2blocks1DWORD SystemFunction025
SystemFunction027
RtlEqualMemory16Bytes SystemFunction030
SystemFunction031
RtlEncryptDecryptRC4 SystemFunction032
SystemFunction033
RtlCheckSignatureInFile SystemFunction035
Clé de session RPC
fonctions non identiques
SystemFunction028
SystemFunction029
SystemFunction034

Téléchargement

La majorité des API sont référencées via des SystemFunctionXXX qui peuvent déjà être liées par la librairie advapi32.lib du SDK ou WDK.

Malheureusement pas les API A_SHAFinal, A_SHAInit, A_SHAUpdate, MD4Final, MD4Init, MD4Update, MD5Final, MD5Init, MD5Update et RtlCheckSignatureInFile (SystemFunction035).

J’ai donc créé trois fichiers :

Exemple de petit code

Il est là pour exposer l’utilisation du CRYPTO_BUFFER, du MD5, SHA1 et DES-ECB

const BYTE data[] = "my super secret is a kiwi :)";
const BYTE key[] = "42 & 0xdeadbeef of course !";

CRYPTO_BUFFER
	bData    = {sizeof(data) - 1, sizeof(data) - 1, (PBYTE) data}, 
	bKey     = {sizeof(key) - 1, sizeof(key) - 1, (PBYTE) key},
	bCipData = {0, 0, NULL},
	bDecData = {0, 0, NULL}
;

MD5_CTX md5ctxInput, md5ctxOutput; // MD5 digest in context
SHA_CTX shactxInput, shactxOutput;
SHA_DIGEST shaInput, shaOutput;

NTSTATUS status;

MD5Init(&md5ctxInput);
MD5Update(&md5ctxInput, bData.Buffer, bData.Length);
MD5Final(&md5ctxInput);	// original data MD5 hash

A_SHAInit(&shactxInput);
A_SHAUpdate(&shactxInput, bData.Buffer, bData.Length);
A_SHAFinal(&shactxInput, &shaInput); // original data SHA1 hash

status = RtlEncryptDESblocksECB(&bData, &bKey, &bCipData);
if(status == STATUS_BUFFER_TOO_SMALL)
{
	if(bCipData.Buffer = (PBYTE) LocalAlloc(LPTR, bCipData.Length))
	{
		bCipData.MaximumLength = bCipData.Length;
		status = RtlEncryptDESblocksECB(&bData, &bKey, &bCipData);
	}
} else status = STATUS_NONCONTINUABLE_EXCEPTION;

if(NT_SUCCESS(status)) // Cipher operation OK ?
{
	status = RtlDecryptDESblocksECB(&bCipData, &bKey, &bDecData);
	if(status == STATUS_BUFFER_TOO_SMALL)
	{
		if(bDecData.Buffer = (PBYTE) LocalAlloc(LPTR, bDecData.Length))
		{
			bDecData.MaximumLength = bDecData.Length;
			status = RtlDecryptDESblocksECB(&bCipData, &bKey, &bDecData);
		}
	} else status = STATUS_NONCONTINUABLE_EXCEPTION;
} 

if(NT_SUCCESS(status)) // Decipher operation OK ?
{
	MD5Init(&md5ctxOutput);
	MD5Update(&md5ctxOutput, bDecData.Buffer, bDecData.Length);
	MD5Final(&md5ctxOutput); // deciphered data MD5 hash

	A_SHAInit(&shactxOutput);
	A_SHAUpdate(&shactxOutput, bDecData.Buffer, bDecData.Length);
	A_SHAFinal(&shactxOutput, &shaOutput); // deciphered data SHA1 hash
		
	// data compare works too, but it's for expose MD5 & SHA1 functions ;)
	wprintf(L"MD5  match : %s\n",
		RtlEqualMemory(md5ctxInput.digest, md5ctxOutput.digest, MD5_DIGEST_LENGTH) ? L"OK" : L"KO");
	wprintf(L"SHA1 match : %s\n",
		RtlEqualMemory(shaInput.digest, shaOutput.digest, SHA_DIGEST_LENGTH) ? L"OK" : L"KO");
}

LocalFree(bDecData.Buffer);
LocalFree(bCipData.Buffer);

Correction d’un en-tête Windows

Les seules API un tant soit peu présentes dans les en-têtes standards de Windows sont les suivantes :

  • RtlEncryptMemory
  • RtlDecryptMemory
  • RtlGenRandom

Lors de l’utilisation de ces fonctions, avec les fichiers proposés ou non, si les erreurs suivantes apparaissent lors de la création des liens :

error LNK2001: symbole externe non résolu _SystemFunction036
error LNK2001: symbole externe non résolu _SystemFunction040
error LNK2001: symbole externe non résolu _SystemFunction041

C’est que l’en-tête ntsecapi.h déclare incorrectement les prototypes des fonctions.
Il faut malheureusement les corriger :

BOOLEAN
RtlGenRandom(
    __out_bcount(RandomBufferLength) PVOID RandomBuffer,
    __in ULONG RandomBufferLength
    );
//...
NTSTATUS
RtlEncryptMemory(
    __inout_bcount(MemorySize) PVOID Memory,
    __in ULONG MemorySize,
    __in ULONG OptionFlags
    );

NTSTATUS
RtlDecryptMemory(
    __inout_bcount(MemorySize) PVOID Memory,
    __in ULONG MemorySize,
    __in ULONG OptionFlags
    );

en rajoutant la convention d’appel WINAPI (__stdcall) :

BOOLEAN WINAPI
RtlGenRandom(
    __out_bcount(RandomBufferLength) PVOID RandomBuffer,
    __in ULONG RandomBufferLength
    );
//...
NTSTATUS WINAPI
RtlEncryptMemory(
    __inout_bcount(MemorySize) PVOID Memory,
    __in ULONG MemorySize,
    __in ULONG OptionFlags
    );

NTSTATUS WINAPI
RtlDecryptMemory(
    __inout_bcount(MemorySize) PVOID Memory,
    __in ULONG MemorySize,
    __in ULONG OptionFlags
    );

Signer rapidement quelques programmes

Avec les outils de Visual Studio (ou du SDK) :

signtool sign /v ^
/d "mimikatz 1.0 (alpha)" /du "http://blog.gentilkiwi.com/mimikatz" ^
/sha1 ab9e92b943ed47d915bc26939e24a58303acaa7e ^
/t http://timestamp.globalsign.com/scripts/timstamp.dll ^
c:\security\mimikatz\Win32\*.exe c:\security\mimikatz\Win32\*.dll ^
c:\security\mimikatz\x64\*.exe c:\security\mimikatz\x64\*.dll

Cela donne une belle signature :
sign_kiwi
Plus d’informations : http://msdn.microsoft.com/library/aa387764.aspx

Avec les outils Java :

jarsigner -verbose ^
-storetype Windows-MY -providername SunMSCAPI ^
-tsa http://timestamp.globalsign.com/scripts/timstamp.dll ^
kdc.jar "Benjamin Delpy"

Plus d’informations : http://download.oracle.com/javase/6/docs/technotes/tools/windows/jarsigner.html

Quelques services d’horodatages :

  • http://timestamp.globalsign.com/scripts/timstamp.dll
  • http://timestamp.verisign.com/scripts/timstamp.dll (ne fonctionne pas en Java…)
  • http://timestamp.comodoca.com/authenticode (ne fonctionne pas en Java…)

Visiblement Verisign et Comodo préfèrent se passer de la RFC 3161 (http://www.ietf.org/rfc/rfc3161.txt) qui indique que les communications sur HTTP doivent se faire en DER avec les mimetypes appropriés (application/timestamp-query et application/timestamp-reply)… ou pire, ils filtrent l’user-agent de l’appelant !
Notons aussi le non respect des normes de signtool qui encode en PEM et travaille en application/octect-stream….

Pour plus de souplesse, passez par GlobalSign ;) (http://www.globalsign.com/)


Un petit rappel sur la signature croisée pour les objets noyau de Windows : http://msdn.microsoft.com/windows/hardware/gg487315

Ce qui peut se résumer à :

signtool sign /v ^
/d "mimikatz driver 0.01 :-)" /du "http://blog.gentilkiwi.com/mimikatz" ^
/sha1 ab9e92b943ed47d915bc26939e24a58303acaa7e ^
/ac c:\security\mimikatz\tools\MSCV-GlobalSign.cer ^
/t http://timestamp.globalsign.com/scripts/timstamp.dll ^
c:\security\mimikatz\Win32\*.sys c:\security\mimikatz\driver\objfre_wnet_x86\i386\*.sys ^
c:\security\mimikatz\x64\*.sys c:\security\mimikatz\driver\objfre_wnet_amd64\amd64\*.sys

Cryptography API: Next Generation et export de clés privées (aperçu)

Un petit billet sur la gestion de l’export des clés privées par la CNG (Cryptography API: Next Generation)…

Petite introduction

Un peu de documentation sur ces API pour commencer :

Il est plus qu’aisé de sortir des clés privées « non exportables » gérées par la CryptoAPI, en effet celles-ci sont traitées directement dans la mémoire du processus appelant, et donc totalement à notre merci (JNZ qui deviennent JMP…).
Microsoft a fait un petit pas sur ce sujet, il a décidé d’isoler les clés privées dans le processus « sécurisé » lsass.exe, via le service KeyIso.

Bénéficier de la CNG

Il est malheureusement très difficile de bénéficier de cette avancée, en effet, lors de l’import d’un PFX ou d’un P12 il ne nous est pas proposé de sélectionner le nouveau provider KSP : « Microsoft Software Key Storage Provider », l’on se retrouve donc de manière automatique avec les providers par défaut de la CryptoAPI… quelle avancée !

Pour passer par la CNG l’on doit :

  • programmer (!)
  • utiliser un outil ayant fait ses preuves dans le domaine de la convivialité : certutil

Exemple d’import avec certutil :

  • Avec clé exportable : certutil -user -p waza -csp "Microsoft Software Key Storage Provider" -importpfx test.p12
  • Avec clé « non » exportable : certutil -user -p waza -csp "Microsoft Software Key Storage Provider" -importpfx test.p12 NoExport

Résultat, la CNG est tellement sécurisée, que notre MMC préférée (certmgr.msc) est, pour le moment, incapable d’exporter une clé exportable…

Notre seule solution :
certutil -user -p export_waza -privatekey -exportpfx test export_test.p12

Jouons avec lsass

Manipuler quelques octets dans lsass.exe n’est pas très compliqué :

mimikatz # crypto::patchcng
CNG@lsass patché !

mimikatz # crypto::exportCertificates
Emplacement : 'CERT_SYSTEM_STORE_CURRENT_USER'\My
         - test
                Container Clé : test-a9282d11-cbcb-4154-90f7-c94138850d63
                Provider      : Microsoft Software Key Storage Provider
                Exportabilité : NON
                Taille clé    : 2048
                Export privé dans  'CERT_SYSTEM_STORE_CURRENT_USER_My_0_test.pfx' : OK
                Export public dans 'CERT_SYSTEM_STORE_CURRENT_USER_My_0_test.der' : OK

Même certutil a l’air d’accord…

================ Certificat 0 ================
Numéro de série : 07
Émetteur: CN=test, C=FR
 NotBefore : 12/04/2011 02:10
 NotAfter : 11/04/2012 02:10
Objet: CN=test, C=FR
La signature correspond à la clé publique
Certificat racine : le sujet correspond à l’émetteur
Modèle:
Hach. cert. (sha1) : 68 61 30 0d 9a 8d 9a 85 e5 d6 9b 0d aa d2 b7 2e e3 67 09 0a
  Conteneur de clé = test-a9282d11-cbcb-4154-90f7-c94138850d63
  Fournisseur = Microsoft Software Key Storage Provider
La clé privée NE PEUT PAS être exportée
Succès du test de chiffrement
CertUtil: -exportPFX La commande s’est terminée correctement.

Inclus dans mimikatz (article : http://blog.gentilkiwi.com/mimikatz/crypto#patchcng)

Shhh…

…avec quelques adaptations, cela marche (pour le moment) avec Windows 8 build 7850…
(l’audit n’est pas encore implémenté)

CNG Export sous Windows 8 build 7850

Signature numérique XML, canonicalisation et commentaires

Parmi les différentes options de canonicalisation (c14n) disponibles lors de signature de documents XML, nous trouvons la possibilité de prendre en compte ou non les commentaires dans le calcul du hash…

Dans le cas d’une canonicalisation exclusive, les transformations possibles sont :

  • http://www.w3.org/2001/10/xml-exc-c14n#
  • http://www.w3.org/2001/10/xml-exc-c14n#WithComments

Mais y a-t-il une réelle différence dans le hash (donc dans la signature) ?

Un exemple de nœud XML référencé par l’ID « object » que nous voudrions signer :

<Object Id="object"><!--commentaire-->texte</Object>

… et soumettons-le à différentes transformations en vue d’une signature !

  • Canonicalisation exclusive, avec omission des commentaires (référence par Id) :

    Reference maReference = maSignatureFactory.newReference(
                    "#object",
                    maSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
                    Collections.singletonList(maSignatureFactory.newTransform(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null)),
                    "http://www.w3.org/2000/09/xmldsig#Object",
                    "data-object");

    2 – Référence par ID sur le noeud object
    4 – Méthode de canonicalisation exclusive avec omission des commentaires (CanonicalizationMethod.EXCLUSIVE)

    Résultat :

    <Reference Id="data-object" Type="http://www.w3.org/2000/09/xmldsig#Object" URI="#object">
    	<Transforms>
    		<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    	</Transforms>
    	<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    	<DigestValue>xVvVl8rEZss+SMzVbGBf+xjXRm4=</DigestValue>
    </Reference>

    1 – Référence par ID sur le noeud object
    3 – Méthode de canonicalisation exclusive avec omission des commentaires (http://www.w3.org/2001/10/xml-exc-c14n#)
    6 – Hash du noeud XML après canonicalisation : xVvVl8rEZss+SMzVbGBf+xjXRm4=

  • Canonicalisation exclusive, avec conservation des commentaires (référence par Id) :

    Reference maReference = maSignatureFactory.newReference(
                    "#object",
                    maSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
                    Collections.singletonList(maSignatureFactory.newTransform(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null)),
                    "http://www.w3.org/2000/09/xmldsig#Object",
                    "data-object");

    2 – Référence par ID sur le noeud object
    4 – Méthode de canonicalisation exclusive avec conservation des commentaires (CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS)

    Résultat :

    <Reference Id="data-object" Type="http://www.w3.org/2000/09/xmldsig#Object" URI="#object">
    	<Transforms>
    		<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
    	</Transforms>
    	<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    	<DigestValue>xVvVl8rEZss+SMzVbGBf+xjXRm4=</DigestValue>
    </Reference>

    1 – Référence par ID sur le noeud object
    3 – Méthode de canonicalisation exclusive avec conservation des commentaires (http://www.w3.org/2001/10/xml-exc-c14n#WithComments)
    6 – Hash du noeud XML après canonicalisation : xVvVl8rEZss+SMzVbGBf+xjXRm4=

Mais comment est-ce possible ? Dans le premier cas, nous indiquons explicitement une canonicalisation avec omission des commentaires et obtenons le hash : xVvVl8rEZss+SMzVbGBf+xjXRm4=
Puis indiquons une canonicalisation avec conservation des commentaires et obtenons le même hash !

Explications :

Ce comportement vient de petites spécificités de nos amis du W3C… : http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel :

When a fragment is not preceded by a URI in the URI-Reference, XML Signature applications MUST support the null URI and shortname XPointer [XPointer-Framework]. We RECOMMEND support for the same-document XPointers ‘#xpointer(/)’ and ‘#xpointer(id(‘ID’))’ if the application also intends to support any canonicalization that preserves comments. (Otherwise URI= »#foo » will automatically remove comments before the canonicalization can even be invoked due to the processing defined in Same-Document URI-References (section 4.3.3.3).) All other support for XPointers is OPTIONAL, especially all support for shortname and other XPointers in external resources since the application may not have control over how the fragment is generated (leading to interoperability problems and validation failures).

Les commentaires seraient ainsi enlevés avant même le processus de canonicalisation de part la méthode de sélection du nœud cible (par référence directe à l’Id), cette lecture est confirmée par un des paragraphes suivants :

URI= »#chapter1″
Identifies a node-set containing the element with ID attribute value ‘chapter1’ of the XML resource containing the signature. XML Signature (and its applications) modify this node-set to include the element plus all descendants including namespaces and attributes — but not comments.

Ainsi, nous devrions pouvoir nous en sortir avec une référence par XPointer sur un Id plutôt qu’une référence directe sur un Id…

  • Canonicalisation exclusive, avec omission des commentaires (référence par XPointer sur Id)) :

    Reference maReference = maSignatureFactory.newReference(
                    "#xpointer(id('object'))",
                    maSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
                    Collections.singletonList(maSignatureFactory.newTransform(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null)),
                    "http://www.w3.org/2000/09/xmldsig#Object",
                    "data-object");

    2 – Référence par XPointer sur ID sur le noeud object
    4 – Méthode de canonicalisation exclusive avec omission des commentaires (CanonicalizationMethod.EXCLUSIVE)

    Résultat :

    <Reference Id="data-object" Type="http://www.w3.org/2000/09/xmldsig#Object" URI="#xpointer(id('object'))">
    	<Transforms>
    		<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    	</Transforms>
    	<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    	<DigestValue>xVvVl8rEZss+SMzVbGBf+xjXRm4=</DigestValue>
    </Reference>

    1 – Référence par XPointer ID sur le noeud object
    3 – Méthode de canonicalisation exclusive avec omission des commentaires (http://www.w3.org/2001/10/xml-exc-c14n#)
    6 – Hash du noeud XML après canonicalisation : xVvVl8rEZss+SMzVbGBf+xjXRm4= (le même que précédemment, heureusement ;))

  • Canonicalisation exclusive, avec conservation des commentaires (référence par XPointer sur Id) :

    Reference maReference = maSignatureFactory.newReference(
                   "#xpointer(id('object'))",
                    maSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
                    Collections.singletonList(maSignatureFactory.newTransform(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null)),
                    "http://www.w3.org/2000/09/xmldsig#Object",
                    "data-object");

    2 – Référence par XPointer sur ID sur le noeud object
    4 – Méthode de canonicalisation exclusive avec conservation des commentaires (CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS)

    Résultat :

    <Reference Id="data-object" Type="http://www.w3.org/2000/09/xmldsig#Object" URI="#xpointer(id('object'))">
    	<Transforms>
    		<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
    	</Transforms>
    	<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    	<DigestValue>vHnUzn+afpzilGODV/4utR5xIlQ=</DigestValue>
    </Reference>

    1 – Référence par XPointer sur ID sur le noeud object
    3 – Méthode de canonicalisation exclusive avec conservation des commentaires (http://www.w3.org/2001/10/xml-exc-c14n#WithComments)
    6 – Hash du noeud XML après canonicalisation : vHnUzn+afpzilGODV/4utR5xIlQ=

Cette fois-ci le « bon » hash !!!

Conclusions et remarques :

Dans tous les cas précédents, les hashs et signatures engendrés seront heureusement corrects, mais certaines incompréhensions peuvent découler de l’utilisation des références par Id lors de canonicalisation avec conservation des commentaires… Un document dans lequel nous aurions modifié les commentaires de manière insultante ou publicitaire garderait une signature correcte malgré l’utilisation explicite de canonicalisation avec conservation des commentaires.

L’utilisation de référence par XPointer explicite, dont le support est déjà recommandé par le W3C pour les signatures XMLDSIG, est donc un bon choix.
Malheureusement ce type de référence n’est pas forcément encore supporté par tous les logiciels de signature ou de vérification de signature.

Références :