Für die Validierung einer XML-Signatur müssen Sie eine Implementierung der IURIDereferencer-Schnittstelle bereitstellen. Die Implementierung ist dafür zuständig, die URIs innerhalb der Reference-Elemente eines XML-Signaturdokuments aufzulösen und die Daten zurückzugeben, damit der Digest berechnet werden kann. Der berechnete Digest wird mit dem Digest in der Signatur verglichen, um festzustellen, ob die referenzierten Daten seit dem Erstellen der Signatur geändert wurden.
Hinweis:
HTML-basierte AIR-Anwendungen müssen eine SWF-Bibliothek importieren, die eine ActionScript-Implementierung enthält, um XML-Signaturen zu validieren. In JavaScript kann die IURIDereferencer-Schnittstelle nicht implementiert werden.
Die IURIDerefencer-Schnittstelle verfügt über eine einzelne Methode,
dereference(uri:String)
, die implementiert werden muss. Das XMLSignatureValidator-Objekt ruft diese Methode für jeden Verweis in der Signatur aus. Die Methode muss die Daten in einem ByteArray-Objekt zurückgeben.
In den meisten Fällen müssen Sie auch Eigenschaften oder Methoden hinzufügen, mit denen das Dereferencer-Objekt die referenzierten Daten finden kann. Wenn sich die signierten Daten zum Beispiel in demselben Dokument wie die Signatur befinden, können Sie eine Mitgliedsvariable hinzufügen, die einen Verweis auf das XML-Dokument bereitstellt. Die
dereference()
-Methode kann diese Variable dann zusammen mit dem URI verwenden, um die referenzierten Daten zu suchen. Befinden sich die signierten Daten dagegen in einem Verzeichnis des lokalen Dateisystems, benötigt die
dereference()
-Methode möglicherweise eine Eigenschaft, die den Pfad zu diesem Verzeichnis angibt, damit die referenzierten Dateien gefunden werden.
Die XMLSignatureValidator-Klasse verlässt sich bei der Interpretation von URI-Strings vollständig auf den Dereferenzierer. Die Standardregeln für die Dereferenzierung von URIs sind in Abschnitt 4.3.3 der W3C-Empfehlung für die Syntax und Verarbeitung von XML-Signaturen aufgeführt.
Dereferenzieren von URIs in umhüllten Signaturen
Wenn eine umhüllte XML-Signatur generiert wird, werden die Signaturelemente in die signierten Daten eingefügt. Wenn Sie zum Beispiel die folgende Meldung mit einer umhüllten Signaturstruktur signieren:
<message>
<data>...</data>
</message>
Dann sieht das signierte Dokument folgendermaßen aus:
<message>
<data>...</data>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>yv6...Z0Y=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>cCY...LQ==</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MII...4e</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</message>
Beachten Sie, dass die Signatur ein einzelnes Reference-Element mit einem leeren String als URI enthält. Ein leerer String verweist in diesem Kontext auf den Stamm des Dokuments.
Beachten Sie auch, dass der Transformalgorithmus angibt, dass eine umhüllte Signaturtransformation angewendet wurde. Wenn eine umhüllte Signaturtransformationen angewendet wurde, entfernt der XMLSignatureValidator die Signatur automatisch aus dem Dokument, bevor der Digest berechnet wird. Dies bedeutet, dass der Dereferenzierer das Signature-Element nicht zu entfernen braucht, wenn die Daten zurückgegeben werden.
Im folgenden Beispiel wird ein Dereferenzierer für umhüllte Signaturen veranschaulicht:
package
{
import flash.events.ErrorEvent;
import flash.events.EventDispatcher;
import flash.security.IURIDereferencer;
import flash.utils.ByteArray;
import flash.utils.IDataInput;
public class EnvelopedDereferencer
extends EventDispatcher implements IURIDereferencer
{
private var signedMessage:XML;
public function EnvelopedDereferencer( signedMessage:XML )
{
this.signedMessage = signedMessage;
}
public function dereference( uri:String ):IDataInput
{
try
{
if( uri.length != 0 )
{
throw( new Error("Unsupported signature type.") );
}
var data:ByteArray = new ByteArray();
data.writeUTFBytes( signedMessage.toXMLString() );
data.position = 0;
}
catch (e:Error)
{
var error:ErrorEvent =
new ErrorEvent("Ref error " + uri + " ", false, false, e.message);
this.dispatchEvent(error);
data = null;
throw new Error("Reference not resolvable: " + uri + ", " + e.message);
}
finally
{
return data;
}
}
}
}
Diese Dereferenziererklasse verwendet eine Konstruktorfunktion mit einem Parameter,
signedMessage
, um das umhüllte Signaturdokument der
dereference()
-Methode verfügbar zu machen. Da sich der Verweis in einer umhüllten Signatur immer auf den Stamm der signierten Daten bezieht, schreibt die
dereferencer()
-Methode das Dokument in ein ByteArray und gibt es zurück.
Dereferenzieren von URIs in umhüllenden und getrennten Signaturen
Wenn sich die signierten Daten in demselben Dokument wie die Signatur befinden, verwenden die URIs in den Verweisen normalerweise die XPath- oder XPointer-Syntax, um die signierten Elemente zu adressieren. Die W3C-Empfehlung für die Syntax und Verarbeitung von XML-Signaturen empfiehlt diese Syntax lediglich; deshalb sollte Ihre Implementierung auf den Signaturen basieren, die Sie erwarten (und Sie sollten ausreichende Fehlerüberprüfung hinzufügen, um eine nicht unterstützte Syntax definiert zu verarbeiten).
Die Signatur einer AIR-Anwendung ist ein Beispiel für eine umhüllende Signatur. Die Dateien der Anwendung sind in einem Manifest-Element aufgeführt. Das Manifest-Element wird im Reference URI-Attribut mit dem String „#PackageContents“, der auf die ID des Manifest-Elements verweist, adressiert:
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="PackageSignature">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/TR/xmldsig-core#rsa-sha1"/>
<Reference URI="#PackageContents">
<Transforms>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>ZMGqQdaRKQc1HirIRsDpeBDlaElS+pPotdziIAyAYDk=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue Id="PackageSignatureValue">cQK...7Zg==</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MII...T4e</X509Certificate>
</X509Data>
</KeyInfo>
<Object>
<Manifest Id="PackageContents">
<Reference URI="mimetype">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">
</DigestMethod>
<DigestValue>0/oCb84THKMagtI0Dy0KogEu92TegdesqRr/clXct1c=</DigestValue>
</Reference>
<Reference URI="META-INF/AIR/application.xml">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">
</DigestMethod>
<DigestValue>P9MqtqSdqcqnFgeoHCJysLQu4PmbUW2JdAnc1WLq8h4=</DigestValue>
</Reference>
<Reference URI="XMLSignatureValidation.swf">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">
</DigestMethod>
<DigestValue>OliRHRAgc9qt3Dk0m0Bi53Ur5ur3fAweIFwju74rFgE=</DigestValue>
</Reference>
</Manifest>
</Object>
</Signature>
Ein Dereferenzierer zum Validieren dieser Signatur muss den URI-String, der
"#PackageContents"
enthält, aus dem Reference-Element nehmen und das Manifest-Element in einem ByteArray-Objekt zurückgeben. Das Symbol „#“ bezieht sich auf den Wert eines Element-ID-Attributs.
Im folgenden Beispiel wird ein Dereferenzierer für die Validierung der Signaturen von AIR-Anwendungen implementiert. Die Implementierung wird einfach gehalten, indem sie von der bekannten Struktur einer AIR-Signatur ausgeht. Ein Dereferenzierer für allgemeine Zwecke könnte erheblich komplexer sein.
package
{
import flash.events.ErrorEvent;
import flash.security.IURIDereferencer;
import flash.utils.ByteArray;
import flash.utils.IDataInput;
public class AIRSignatureDereferencer implements IURIDereferencer {
private const XML_SIG_NS:Namespace =
new Namespace( "http://www.w3.org/2000/09/xmldsig#" );
private var airSignature:XML;
public function AIRSignatureDereferencer( airSignature:XML ) {
this.airSignature = airSignature;
}
public function dereference( uri:String ):IDataInput {
var data:ByteArray = null;
try
{
if( uri != "#PackageContents" )
{
throw( new Error("Unsupported signature type.") );
}
var manifest:XMLList =
airSignature.XML_SIG_NS::Object.XML_SIG_NS::Manifest;
data = new ByteArray();
data.writeUTFBytes( manifest.toXMLString());
data.position = 0;
}
catch (e:Error)
{
data = null;
throw new Error("Reference not resolvable: " + uri + ", " + e.message);
}
finally
{
return data;
}
}
}
}
Wenn Sie diesen Signaturtyp überprüfen, werden nur die Daten im Manifest-Element validiert. Die eigentlichen Dateien im Paket werden in keiner Weise überprüft. Um zu prüfen, ob die Paketdateien manipuliert wurden, berechnen Sie den SHA256-Digest und vergleichen Sie das Ergebnis mit dem im Manifest verzeichneten Digest. Der XMLSignatureValidator überprüft derartige sekundäre Verweise nicht automatisch.
Hinweis:
Dieses Beispiel ist nur zur Veranschaulichung des Ablaufs einer Signaturvalidierung aufgeführt. Eine AIR-Anwendung, die ihre eigene Signatur validiert, ist nicht besonders sinnvoll. Wenn die Anwendung bereits manipuliert wurde, könnte auch einfach die Validierungsprüfung entfernt werden.
Berechnen von Digestwerten für externe Ressourcen
AIR enthält keine integrierten Funktionen für die Berechnung von SHA256-Digests, das Flex SDK enthält jedoch eine SHA256-Utility-Klasse. Das SDK enthält auch die Base64-Encoder-Utility-Klasse, die hilfreich beim Vergleichen des berechneten Digests mit dem in der Signatur gespeicherten Digest ist.
Die folgende Beispielfunktion liest und validiert die Dateien in einem AIR-Paketmanifest:
import mx.utils.Base64Encoder;
import mx.utils.SHA256;
private function verifyManifest( sigFile:File, manifest:XML ):Boolean
{
var result:Boolean = true;
var message:String = '';
var nameSpace:Namespace = manifest.namespace();
if( manifest.nameSpace::Reference.length() <= 0 )
{
result = false;
message = "Nothing to validate.";
}
for each (var reference:XML in manifest.nameSpace::Reference)
{
var file:File = sigFile.parent.parent.resolvePath( reference.@URI );
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var fileData:ByteArray = new ByteArray();
stream.readBytes( fileData, 0, stream.bytesAvailable );
var digestHex:String = SHA256.computeDigest( fileData );
//Convert hexidecimal string to byte array
var digest:ByteArray = new ByteArray();
for( var c:int = 0; c < digestHex.length; c += 2 ){
var byteChar:String = digestHex.charAt(c) + digestHex.charAt(c+1);
digest.writeByte( parseInt( byteChar, 16 ));
}
digest.position = 0;
var base64Encoder:Base64Encoder = new Base64Encoder();
base64Encoder.insertNewLines = false;
base64Encoder.encodeBytes( digest, 0, digest.bytesAvailable );
var digestBase64:String = base64Encoder.toString();
if( digestBase64 == reference.nameSpace::DigestValue )
{
result = result && true;
message += " " + reference.@URI + " verified.\n";
}
else
{
result = false;
message += " ---- " + reference.@URI + " has been modified!\n";
}
base64Encoder.reset();
}
trace( message );
return result;
}
Die Funktion durchläuft alle Verweise im Manifest-Element. Für jeden Verweis wird der SHA256-Digest berechnet, im Base64-Format kodiert und mit dem Digest im Manifest verglichen. Die URIs in einem AIR-Paket verweisen auf Pfade relativ zum Anwendungsverzeichnis. Die Pfade werden basierend auf dem Speicherort der Signaturdatei aufgelöst, die sich immer im META-INF-Unterverzeichnis im Anwendungsverzeichnis befindet. Beachten Sie, dass die Flex-SHA256-Klasse den Digest als String von hexadezimalen Zeichen zurückgibt. Dieser String muss in ein ByteArray konvertiert werden, das die vom hexadezimalen String repräsentierten Byte enthält.
Um die mx.utils.SHA256- und Base64Encoder-Klassen in Flash CS4 zu verwenden, können Sie diese Klassen entweder suchen und in Ihr Verzeichnis für die Anwendungsentwicklung kopieren oder mit dem Flex SDK eine Bibliotheks-SWF-Datei kompilieren, die diese Klassen enthält.
Dereferenzieren von URIs in separaten Signaturen, die externe Daten referenzieren
Wenn ein URI auf eine externe Ressource verweist, muss auf die Daten zugegriffen werden und sie müssen in ein ByteArray-Objekt geladen werden. Wenn der URI eine absolute URL enthält, muss einfach nur eine Datei gelesen oder eine URL angefordert werden. Wenn der URI einen relativen Pfad enthält, was weitaus häufiger der Fall ist, muss Ihre IURIDereferencer-Implementierung eine Möglichkeit zum Auflösen der Pfade zu den signierten Dateien enthalten.
Im folgenden Beispiel wird ein File-Objekt initialisiert, wenn die Dereferenziererinstanz als Basis zum Auflösen signierter Dateien aufgebaut ist.
package
{
import flash.events.ErrorEvent;
import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.security.IURIDereferencer;
import flash.utils.ByteArray;
import flash.utils.IDataInput;
public class RelativeFileDereferencer
extends EventDispatcher implements IURIDereferencer
{
private var base:File;
public function RelativeFileDereferencer( base:File )
{
this.base = base;
}
public function dereference( uri:String ):IDataInput
{
var data:ByteArray = null;
try{
var referent:File = this.base.resolvePath( uri );
var refStream:FileStream = new FileStream();
data = new ByteArray();
refStream.open( referent, FileMode.READ );
refStream.readBytes( data, 0, data.bytesAvailable );
} catch ( e:Error ) {
data = null;
throw new Error("Reference not resolvable: " + referent.nativePath + ", " + e.message );
} finally {
return data;
}
}
}
}
Die
dereference()
-Funktion sucht die Datei, die vom Referenz-URI adressiert wird, lädt den Dateiinhalt in ein Byte-Array und gibt ein ByteArray-Objekt zurück.
Hinweis:
Vor dem Validieren remoter externer Verweise sollten Sie überlegen, ob Ihre Anwendung durch ein auf schädigende Weise konstruiertes Signaturdokument mit einem „Call home“-Angriff (durch Spyware) oder ähnlichem geschädigt werden kann.
|
|
|