Add-ons für Chrome, Firefox und Opera selbst hosten

3 Browser

Ich habe eine Browser-Extension für Chrome, Firefox und Opera erstellt. Alle Dateien liegen bei mir lokal, ich habe also z.B. nicht Firefoxs Add-on Builder verwendet, sondern nur das SDK. Als die Extension fertig war, wollte ich sie auch selbst hosten. Ich meine, wie schwer kann das schon sein? Ja, nun …

In diesem Artikel gehen wir davon aus, dass sich die Browser-spezifischen Extension-Dateien in den Unterverzeichnissen Chrome/, Firefox/ und Opera/ befinden.

Der Artikel ist lang geworden. Hier sind ein paar Sprungmarken:


Opera

In Operas config.xml muss ein Verweis auf die XML-Datei auf dem hostenden Server stehen, die später nach Updates gefragt wird.

<widget …>
    …
    <update-description href="http://example.org/updates-opera.xml" />
</widget>

Dann packen wir die Extension ein. Dafür wird einfach alles in ein ZIP-Archiv mit der Datei-Endung OEX gepackt.

cd Opera/
zip -r MyExtension.oex *

Die XML-Datei für Updates updates-opera.xml beinhaltet lediglich einen Pfad zur Extension, sowie die aktuelle Versionsnummer dieser.

<update-info xmlns="http://www.w3.org/ns/widgets" src="http://example.org/MyExtension.oex" version="1.1"></update-info>

Das war es auch schon. Ein Hindernis ist jedoch, dass die eigene Domain erst von den Benutzern als „Trusted Website“ in Opera eingetragen werden muss – wie auf diesem Screenshot zu sehen.


Chrome

In manifest.json wird für Updates ein zusätzlicher Eintrag benötigt.

{
    …
    "update_url": "http://example.org/updates-chrome.xml"
}

Wird die Extension zum ersten Mal verpackt, lautet der Befehl:

google-chrome --pack-extension=Chrome/

Dabei wird ein Private Key erzeugt und hier mit dem Namen Chrome.pem abgelegt. Diese Datei sollte geheim bleiben – also nicht einfach in ein öffentliches Repository hochladen!

Bei jedem späteren Mal, dass die Extension verpackt wird, lautet der Befehl dann:

google-chrome --pack-extension=Chrome/ --pack-extension-key=Chrome.pem

In der XML-Datei updates-chrome.xml steht die ID der Extension, der Pfad zur Installations-Datei und die aktuelle Versionsnummer. Die ID erfährt man von der Chrome-Erweiterungs-Seite (chrome://extensions) – dafür muss man ein Häkchen bei „Developer Mode“ setzen. Diese ID ändert sich nicht mehr, auch nicht mit späteren Updates.

<?xml version="1.0" encoding="UTF-8"?>
<gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
    <app appid="replace-this-with-your-extension-id">
        <updatecheck codebase="http://example.org/MyExtension.crx" version="1.1" />
    </app>
</gupdate>

Ein Problem bei der Installation ist, dass dies nicht direkt über die Website erfolgen kann. Die Extension muss vom Benutzer heruntergeladen und dann auf die Chrome-Erweiterungs-Seite (chrome://extensions) gezogen werden (Drag&Drop). Updates funktionieren dann jedoch ganz normal.


Firefox

Ich sage es vorne weg: Firefox hat mich ordentlich angepisst. Mir ist klar, dass alles im Sinne von Sicherheit geschieht und durchaus sinnvoll ist. Aber aus Entwicklersicht war es eine Qual überhaupt erst einmal herauszufinden, wie vorzugehen ist. Erschwerend kam hinzu, dass ich kein SSL verwende. Dafür ist die Firefox-Extension die einzige, die für eine Installation keine größere Mitarbeit seitens des Benutzers erfordert.

Zunächst wird die Extension mit dem cfx tool aus dem SDK verpackt.

CFX=~/.firefox-addon-sdk-1.13.2/bin/cfx
cd Firefox/
$CFX xpi --update-url http://example.org/updates-firefox.rdf

Nun müssen wir allerdings das für das XPI erstellte install.rdf bearbeiten. Also extrahieren wir diesen Teil.

unzip MyExtension.xpi install.rdf

Nun benötigen wir ein weiteres Tool, genannt McCoy. Dafür gibt es eine grafische Variante von Mozilla selbst, oder eine gepatchte Version, die man über das Terminal verwenden kann, was ich hier mache.

Zuerst erstellen wir für unsere Extension einen Schlüssel. Ähnlich wie bei Chrome, wird dies nur einmalig gemacht und dann immer wieder verwendet.

MCCOY=~/.mccoy/mccoy
$MCCOY -createKey "ThinkOfSomeKey"

Als nächstes fügen wir unseren Public Key in das install.rdf ein und packen es wieder in das XPI.

$MCCOY -installRDF install.rdf -key "ThinkOfSomeKey"
zip -f MyExtension.xpi install.rdf

Nun bilden wir einen Hash vom XPI, da wir diesen noch brauchen werden. Welches Hashing-Verfahren eingesetzt wird, ist relativ egal. Ich habe mich für sha256 entschieden.

XPI_HASH=$(sha256sum MyExtension.xpi | sed "s/ .*//g" -)

Gratuliere, wir sind ungefähr bei der Hälfte! Schauen wir uns jetzt etwas Leichtes an: Den Button auf der Website, um den Installations-Prozess einzuleiten. Dafür haben wir eine HTML-Seite mit einem Element, das die ID trigger_ff hat. Stehe das Nachfolgende nun in einer JS-Datei namens foo.js:

var trigger = document.getElementById( "trigger_ff" );

trigger.addEventListener( "click", function( e ) {
    e.preventDefault();

    var params = {
        "MyExtension": {
            URL: "http://example.org/MyExtension.xpi",
            IconURL: "http://example.org/MyIcon_32x32.png",
            Hash: "sha256:%XPI_HASH%",
            toString: function() { return this.URL; }
        }
    };
    InstallTrigger.install( params );
}, false );

Den Hash können wir beispielsweise so einfügen:

sed -i "s;%XPI_HASH%;sha256:$XPI_HASH;g" foo.js

Als nächstes widmen wir uns der RDF-Datei für Updates. Erstelle eine Datei updates-firefox.rdf.

<?xml version="1.0" encoding="utf-8"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
    <Description about="urn:mozilla:extension:MyExtension@example.org">
        <em:updates>
            <Seq>
                <li>
                    <Description>
                        <em:version>1.1</em:version>
                        <em:targetApplication>
                            <Description>
                                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                                <em:minVersion>18.0</em:minVersion>
                                <em:maxVersion>19.*</em:maxVersion>
                                <em:updateLink>http://example.org/MyExtension.xpi</em:updateLink>
                                <em:updateHash>%XPI_HASH%</em:updateHash>
                            </Description>
                        </em:targetApplication>
                    </Description>
                </li>
            </Seq>
        </em:updates>
    </Description>
</RDF>

Die ID {ec8030f7-c20a-464f-9b0e-13a3a9e97384} wird nicht verändert, denn hierbei handelt es sich um die ID für Firefox – es wird gesagt, dass dies eine Extension für Firefox ist und z.B. nicht für Thunderbird.

Klickt der Benutzer den Installations-Button, erfährt Firefox den Hash und überprüft, ob die zu installierende Datei auch die ist, die hier angepriesen wurde. Dieser Vergleich findet auch bei Updates statt. Daher müssen wir auch hier den Platzhalter %XPI_HASH% ersetzen.

sed -i "s;%XPI_HASH%;sha256:$XPI_HASH;g" updates-firefox.rdf

Hinweis: Der Hash muss für jede neue Version erneut errechnet und eingefügt werden!

Nun kommt wieder McCoy zum Einsatz, indem wir in das Update-RDF unsere Signatur einfügen.

$MCCOY -signRDF updates-firefox.rdf -key "ThinkOfSomeKey"

Wenn man möchte, kann man das erzeugte RDF auch noch überprüfen lassen.

$MCCOY -verifyRDF updates-firefox.rdf -key "ThinkOfSomeKey"

Geschafft.


Relevantes

Wer mein Build-Skript und Templates für die XML/RDF/JS-Dateien sehen möchte, findet diese in meinem GitHub-Repository.

Diese Tutorials und Dokumentation könnten von Interesse sein: