├── .gitignore ├── README.md ├── pidgin2gajim.py └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | *.swp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pidgin2gajim 2 | ============ 3 | 4 | This program converts OTR keys from Pidgin format to Gajim format. 5 | 6 | Your Pidgin OTR files are here: 7 | 8 | ~/.purple/otr.private_key # secret key(s) 9 | ~/.purple/otr.fingerprints # fingerprints 10 | 11 | Your Gajim OTR files are here: 12 | 13 | ~/.local/share/gajim/ACCOUNT.key3 # secret key 14 | ~/.local/share/gajim/ACCOUNT.fpr # fingerprints 15 | 16 | When you run pidgin2gajim.py, it automatically loads your Pidgin OTR files from ~/.purple/. Then it creates a new directory relative to your current path called output and saves Gajim-formatted .key3 and .fpr files into it for each Pidgin account you have. 17 | 18 | You then have to manually copy the .key3 and .fpr files from the output directory into ~/.local/share/gajim/. 19 | You may have to slightly rename the files (e.g. remove the username@ prefix). 20 | 21 | I copied a bunch of code from Guardian Project's otrfileconverter project to load and parse the Pidgin OTR private key file: https://github.com/guardianproject/otrfileconverter 22 | 23 | How to Use 24 | ---------- 25 | 26 | First install Gajim: 27 | 28 | sudo apt-get install gajim 29 | 30 | Run it and set up your jabber accounts. Click Edit, Plugins, switch to the Available tab, and download and install the Off-the-Record plugin. On the Plugins window, click Configure while Off-the-Record is selected to open the OTR plugin settings. For each jabber account, generate a new OTR key (just to create filenames that we'll overwrite). When you have done this, completely exit Gajim. 31 | 32 | Then download and run pidgin2gajim: 33 | 34 | git clone https://github.com/micahflee/pidgin2gajim.git 35 | cd pidgin2gajim 36 | virtualenv env # needs python-virtualenv 37 | . env/bin/activate 38 | pip install pyparsing 39 | pip install python-potr 40 | ./pidgin2gajim.py 41 | ls -l output 42 | deactivate 43 | 44 | Then overwrite your Gajim OTR keys with the ones that were just created in the output directory. Something like: 45 | 46 | # note: gajim does not want the username@ prefix 47 | cp output/micah@jabber.ccc.de.key3 ~/.local/share/gajim/jabber.ccc.de.key3 48 | cp output/micah@jabber.ccc.de.fpr ~/.local/share/gajim/jabber.ccc.de.fpr 49 | 50 | Now open Gajim again. If all went well, you should now have your Pidgin OTR keys in Gajim. 51 | 52 | There is a known bug where Gajim sometimes crashes the first time it tries to parse your saved fingerprints: https://github.com/micahflee/pidgin2gajim/issues/1 53 | 54 | If this happens, you can just run Gajim again and it should work fine. You'll just be missing a couple of fingerprints from people you've talked to. Your actual secret key should be exactly the same and have the same fingerprint. 55 | -------------------------------------------------------------------------------- /pidgin2gajim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Locate existing libpurple (pidgin) otr private keys and fingerprints and 4 | convert them to gajim format. 5 | 6 | Note: 7 | You have to manually copy the .key3 and .fpr files from the output directory 8 | into ~/.local/share/gajim/. 9 | """ 10 | 11 | import os 12 | from base64 import b64decode 13 | 14 | from pyparsing import * 15 | from potr.utils import bytes_to_long 16 | from potr.compatcrypto import DSAKey 17 | 18 | # much of this is based on: 19 | # https://github.com/guardianproject/otrfileconverter 20 | 21 | def verify_len(t): 22 | t = t[0] 23 | if t.len is not None: 24 | t1len = len(t[1]) 25 | if t1len != t.len: 26 | raise ParseFatalException( 27 | "invalid data of length %d, expected %s" % (t1len, t.len)) 28 | return t[1] 29 | 30 | 31 | def parse_sexp(data): 32 | """parse sexp/S-expression format and return a python list""" 33 | # define punctuation literals 34 | LPAR, RPAR, LBRK, RBRK, LBRC, RBRC, VBAR = map(Suppress, "()[]{}|") 35 | 36 | decimal = Word("123456789", nums).setParseAction(lambda t: int(t[0])) 37 | bytes = Word(printables) 38 | raw = Group(decimal.setResultsName("len") + Suppress(":") + bytes).setParseAction(verify_len) 39 | token = Word(alphanums + "-./_:*+=") 40 | base64_ = Group(Optional(decimal, default=None).setResultsName("len") + VBAR 41 | + OneOrMore(Word(alphanums + "+/=")).setParseAction(lambda t: b64decode("".join(t))) 42 | + VBAR).setParseAction(verify_len) 43 | 44 | hexadecimal = ("#" + OneOrMore(Word(hexnums)) + "#") \ 45 | .setParseAction(lambda t: int("".join(t[1:-1]), 16)) 46 | qString = Group(Optional(decimal, default=None).setResultsName("len") + 47 | dblQuotedString.setParseAction(removeQuotes)).setParseAction(verify_len) 48 | simpleString = raw | token | base64_ | hexadecimal | qString 49 | 50 | display = LBRK + simpleString + RBRK 51 | string_ = Optional(display) + simpleString 52 | 53 | sexp = Forward() 54 | sexpList = Group(LPAR + ZeroOrMore(sexp) + RPAR) 55 | sexp << ( string_ | sexpList ) 56 | 57 | try: 58 | sexpr = sexp.parseString(data) 59 | return sexpr.asList()[0][1:] 60 | except ParseFatalException as pfe: 61 | print "Error:", pfe.msg 62 | print line(pfe.loc, data) 63 | print pfe.markInputline() 64 | 65 | 66 | def parse(filename): 67 | """parse the otr.private_key S-Expression and return an OTR dict""" 68 | with open(filename, 'r') as f: 69 | data = ''.join(line for line in f.readlines()) 70 | 71 | sexplist = parse_sexp(data) 72 | keydict = dict() 73 | for sexpkey in sexplist: 74 | if sexpkey[0] == "account": 75 | key = dict() 76 | name = '' 77 | for element in sexpkey: 78 | # 'name' must be the first element in the sexp or BOOM! 79 | if element[0] == "name": 80 | if element[1].find('/') > -1: 81 | name, resource = element[1].split('/') 82 | else: 83 | name = element[1].strip() 84 | resource = '' 85 | key = dict() 86 | key['name'] = name.strip() 87 | key['resource'] = resource.strip() 88 | elif element[0] == "protocol": 89 | key['protocol'] = element[1] 90 | elif element[0] == "private-key": 91 | if element[1][0] == 'dsa': 92 | key['type'] = 'dsa' 93 | for num in element[1][1:6]: 94 | key[num[0]] = num[1] 95 | keytuple = (key['y'], key['g'], key['p'], key['q'], key['x']) 96 | key['dsakey'] = DSAKey(keytuple, private=True) 97 | key['fingerprint'] = '{0:040x}'.format(bytes_to_long(key['dsakey'].fingerprint())) 98 | keydict[name] = key 99 | return keydict 100 | 101 | 102 | if __name__ == "__main__": 103 | home_dir = os.getenv('HOME') 104 | pidgin_key_filename = os.path.join(home_dir, '.purple', 'otr.private_key') 105 | pidgin_fp_filename = os.path.join(home_dir, '.purple', 'otr.fingerprints') 106 | 107 | output_dir = 'output' 108 | if not os.path.exists(output_dir): 109 | os.mkdir(output_dir) 110 | 111 | keys = parse(pidgin_key_filename) 112 | 113 | gajim_fps = dict() 114 | for account in keys: 115 | gajim_fps[account] = '' 116 | 117 | with open(pidgin_fp_filename, 'r') as f: 118 | pidgin_fps = [x.split() for x in f] 119 | for fp in pidgin_fps: 120 | if fp[2] == 'prpl-jabber': 121 | if len(fp) < 5: 122 | fp.append('') 123 | fp[1] = fp[1].split('/')[0] 124 | fp[2] = 'xmpp' 125 | gajim_fps[fp[1]] += '\t'.join(fp) + '\n' 126 | 127 | for account in keys: 128 | serialized_private_key = keys[account]['dsakey'].serializePrivateKey() 129 | with open(os.path.join(output_dir, '%s.key3' % account), 'w') as f: 130 | f.write(serialized_private_key) 131 | with open(os.path.join(output_dir, '%s.fpr' % account), 'w') as f: 132 | f.write(gajim_fps[account]) 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | --------------------------------------------------------------------------------