Revelation2KeePass - importálás Revelation-ből a KeePassMobile jelszó-kezelőbe
A probléma: Szeretném a Revelation-nal kezelt érzékeny adataimat KeePass-ba importálni, hogy a mobiltelefonos jelszókezelővel is használhassam azokat.
A jelszavaimat, pin-kódjaimat, hitelkártya-számkat és egyéb érzékeny adataimat a Revelation jelszókezelő programmal tartom karban. Ez egy olyan nyílt forráskódú GTK/GNOME program, amely titkosított formában, egy mester-kulcs átlal védve tárolja az adatokat, és a mester-kulcs megadása után kényelmes felületet biztosít adataim megtekintésére, bővítésére, rendezgetésére. Mivel utóbbi időben sokat utazok és gyakran kerülök olyan helyzetbe, ahol ezekre az adatokra olyankor van szükségem, amikor nem praktikus számítógépet bekapcsolni, az az ötletem támadt, hogy a mobiltelefonomon hozdoznám a jelszavamat, persze megfelelően titkosítva. Mivel a mai telefonok mindegyike támogatja a Java ME-t, körülnéztem a neten fellelhető nyílt forráskódú midletek között.
Két szimpatikus projektet találtam: A régebbi múltra visszakintő így valószínűleg stabilabb, keepassj2me névre hallgató program viszonylag fapados az fiatalabb viszont nagyon ígéretes KeePassMobile-hoz képest. Mindkét szoftver a KeePass Password Safe jelszókezelő v1.0 adattárolási formátumát használja. Linuxon ezt a formátumot a QT alapú KeePassX lehet kezelni. Sajnos a Revelation és a KeePassX egymás formátumaihoz nem nyújtanak import/export támogatást így third party megoldás után néztem...
Létező megoldás keresése, foltozás
Találtam is egy python kódot itt, ami sajnos elvérzett kipróbálás közben... Kijavítottam, így sikeresen konvertáltam a Revelation-ból kiexportált XML állományt a KeePassX számára emészthető másik XML fájllá. A KeePassX-be való importálás után kisebb igazításokat végeztem a kulcs-adatbázisomon, majd átprüntyögtem BlueTooth-on a mobilomra. Örültem.
A netről halászott szkripten a következő módosításokat hajtottam végre:
*** ./revelation-to-keepassx 2009-08-20 15:57:19.000000000 +0200
--- ./revelation-to-keepassx_mod 2009-07-06 01:19:04.000000000 +0200
***************
*** 37,41 ****
"generic": 0,
"phone": 68,
! "website": 1
}
--- 37,43 ----
"generic": 0,
"phone": 68,
! "website": 1,
! "email" : 1,
! "shell" : 0
}
***************
*** 93,101 ****
ElementTree.SubElement(element, "url").text = item.text
elif id == "creditcard-cardnumber":
! desc_append += "Card Number: " + item.text + "\n"
elif id == "creditcard-expirydate":
! desc_append += "Card Expiry: " + item.text + "\n"
elif id == "creditcard-ccv":
! desc_append += "Card CCV: " + item.text + "\n"
elif id == "generic-pin":
if item.text is not None:
--- 95,103 ----
ElementTree.SubElement(element, "url").text = item.text
elif id == "creditcard-cardnumber":
! desc_append += "Card Number: " + str(item.text) + "\n"
elif id == "creditcard-expirydate":
! desc_append += "Card Expiry: " + str(item.text) + "\n"
elif id == "creditcard-ccv":
! desc_append += "Card CCV: " + str(item.text) + "\n"
elif id == "generic-pin":
if item.text is not None:
***************
*** 105,109 ****
elif id == "generic-code":
ElementTree.SubElement(element, "password").text = item.text
! elif id in ("creditcard-cardtype", "generic-location", "generic-certificate", "generic-keyfile"):
stripped_ids[id] = 1
else:
--- 107,111 ----
elif id == "generic-code":
ElementTree.SubElement(element, "password").text = item.text
! elif id in ("creditcard-cardtype", "generic-location", "generic-certificate", "generic-keyfile", "generic-email", "generic-domain"):
stripped_ids[id] = 1
else:
“Is your code ever completely without stain and flaw?” demanded Master Foo.
Egy-két hét elteltével még mindig hiányérzetem volt... A néhány szükséges manuális igazítást minden egyes import/export alkalmával el kellett végeznem, ami nem tetszett. Továbbá, ha valamelyik url, jelszó, felhasználónév vagy a leírás mező XML szempontból kényes karaktereket tartalmaz (& ' " < >, de akár az ékezetes szép magyar betűket is ide vehetem bizonyos körülmények között), akkor szkript simán hibás állományt kreál nekem. Ez nem menő dolog. Az sem tetszett, ahogy a szkript a Revelation fájljában található többletinformációt kezelte. Végül, de nem utolsó sorban, úgy gondolom, hogy egy XML másik XML-be való alakítására nem a Python vagy a Java, hanem (nem meglepő módon) az XSLT a megfelelő technológia. Következzék tehát az XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" doctype-system="KEEPASSX-DATABASE" encoding="us-ascii"/>
<xsl:variable name="map" select="document('map.xml')" />
<xsl:variable name="nl"><xsl:text>
</xsl:text></xsl:variable>
<xsl:template match="/revelationdata">
<database>
<xsl:if test="entry[@type!='folder']">
<group>
<icon>
<xsl:value-of select="$map//toplevel/icon" />
</icon>
<title>
<xsl:value-of select="$map//toplevel/label" />
</title>
<xsl:apply-templates select="entry[@type!='folder']"/>
</group>
</xsl:if>
<xsl:apply-templates select="entry[@type='folder']"/>
</database>
</xsl:template>
<xsl:template match="entry[@type!='folder']">
<xsl:variable name="type" select="@type" />
<entry>
<icon><xsl:value-of select="$map//type-map/icon[@for=$type]" /></icon>
<title><xsl:value-of select="name" /></title>
<lastmod></lastmod>
<lastaccess></lastaccess>
<creation></creation>
<url>
<xsl:call-template name="multivalue">
<xsl:with-param name="nodes" select="field[@id=$map//field-map/field[text()='url']/@for]" />
</xsl:call-template>
</url>
<username>
<xsl:call-template name="multivalue">
<xsl:with-param name="nodes" select="field[@id=$map//field-map/field[text()='username']/@for]" />
</xsl:call-template>
</username>
<password>
<xsl:call-template name="multivalue">
<xsl:with-param name="nodes" select="field[@id=$map//field-map/field[text()='password']/@for]" />
</xsl:call-template>
</password>
<comment>
<xsl:if test="description/text()">
<xsl:value-of select="concat(description,$nl)" />
</xsl:if>
<xsl:for-each select="field">
<xsl:variable name="id" select="@id"/>
<xsl:if test="not($map//field-map/field[@for=$id])">
<xsl:value-of select="concat(@id, ': ', text(), $nl)" />
</xsl:if>
</xsl:for-each>
</comment>
</entry>
</xsl:template>
<xsl:template match="entry[@type='folder']">
<group>
<icon>48</icon>
<title>
<xsl:value-of select="name" />
</title>
<comment>
<xsl:value-of select="description" />
</comment>
<xsl:apply-templates select="entry" />
</group>
</xsl:template>
<!-- renders $nodes to a string, eliminating duplicate values and using $separator to separate the unique values -->
<!-- pseudocode: result = nodelist.unique().join($separator) -->
<xsl:template name="multivalue">
<xsl:param name="nodes" select="/.."/>
<xsl:param name="separator" select="'|'"/>
<xsl:param name="distinct" select="/.."/>
<xsl:choose>
<xsl:when test="$nodes">
<xsl:call-template name="multivalue">
<xsl:with-param name="distinct" select="$distinct | $nodes[1][not(. = $distinct)]"/>
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="separator" select="$separator"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$distinct[position() = 1]" />
<xsl:if test="count($distinct) > 1">
<xsl:for-each select="$distinct[position() != 1]">
<xsl:value-of select="concat($separator, .)" />
</xsl:for-each>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
A program logikáját és a konfigurációt szétválasztandó, valamint az XSLT-t szerkeszteni-nem-kívánókra gondolva a transzformáció beállításai egy XML konfigurációs fájlban kaptak helyet, amit az XSL mellé (azonos mappába) kell helyezni. Ez a fájl határozza meg, hogy mely Revelation mezők mely KeePass mezővé legyenek átalakítva - egyéb mezők a megjegyzésbe kerülnek, soronkénti név-érték párok formájában. Szintén a konfig adja meg, hogy milyen típusú Revelation bejegyzéshez milyen KeePass ikon társuljon. (A KeePass v1 formátuma csak egyféle bejegyzést kezel: felhasználónév, jelszó, url, megjegyzés négyest.) Szintén itt állíthatjuk be, hogy a Revelation állomány azon bejegyzései, amelyek nem mappában, hanem a legfelsőbb szinten helyezkednek el, milyen nevű és milyen ikonú mappába kerüljenek KeePassX alatt. A KeePass formátuma ugyanis nem enged top-level bejegyzéseket.
Az átalakító logikát úgy valósítottam meg, hogy csak a nem mappa típusú top-level elemek kerüljenek egy speciális konténerbe. A legfelső szintű mappa típusú Relevation bejegyzések legfelsőbb szintű mappák (group-ok) maradnak a KeePass fájlban is. Ez bejegyzésenként egy felelseges klikkelést megspórol a telefonos KeePass szoftverek használata során...
<?xml version="1.0" encoding="UTF-8"?>
<revelation-to-keepassx>
<field-map>
<field for="generic-username">username</field>
<field for="generic-email">username</field>
<field for="generic-password">password</field>
<field for="generic-pin">password</field>
<field for="generic-url">url</field>
<field for="generic-hostname">url</field>
</field-map>
<type-map>
<icon for="creditcard">9</icon>
<icon for="cryptokey">29</icon>
<icon for="door">51</icon>
<icon for="folder">48</icon>
<icon for="generic">0</icon>
<icon for="phone">68</icon>
<icon for="website">1</icon>
<icon for="email">19</icon>
<icon for="shell">30</icon>
</type-map>
<toplevel>
<icon>50</icon>
<label>TopLevel</label>
</toplevel>
</revelation-to-keepassx>