├── .gitignore
├── .pylint-plugins
└── astng_hashlib.py
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── icons
├── 128x128
│ └── keysync.png
├── add.png
├── adium.png
├── chatsecure.png
├── gajim.png
├── gnupg.png
├── irssi.png
├── jitsi.png
├── keysync.icns
├── keysync.ico
├── keysync.png
├── kopete.png
├── pidgin.png
└── xchat.png
├── jenkins-build
├── keysync
├── keysync-gui
├── keysync-gui-folder.spec
├── keysync-gui-onefile.spec
├── keysync.desktop
├── man
└── keysync.1
├── otrapps
├── __init__.py
├── adium.py
├── chatsecure.py
├── errors.py
├── gajim.py
├── gnupg.py
├── irssi.py
├── jitsi.py
├── kopete.py
├── otr_fingerprints.py
├── otr_private_key.py
├── pidgin.py
├── util.py
└── xchat.py
├── py2app-python2.6.patch
├── setup.py
├── tests
├── SAMENESS
│ ├── adium
│ │ ├── Accounts.plist
│ │ ├── otr.fingerprints
│ │ └── otr.private_key
│ └── pidgin
│ │ ├── accounts.xml
│ │ ├── otr.fingerprints
│ │ └── otr.private_key
├── adium
│ ├── Accounts.plist
│ ├── Library
│ │ └── Application Support
│ │ │ └── Adium 2.0
│ │ │ ├── Login Preferences.plist
│ │ │ └── Users
│ │ │ └── Default
│ │ │ ├── AccountPrefs.plist
│ │ │ ├── Accounts.plist
│ │ │ ├── ByObjectPrefs.plist
│ │ │ ├── Contact Alerts.plist
│ │ │ ├── Contact List.plist
│ │ │ ├── Event Presets.plist
│ │ │ ├── General.plist
│ │ │ ├── List Layout.plist
│ │ │ ├── List Theme.plist
│ │ │ ├── Logging.plist
│ │ │ ├── OTR.plist
│ │ │ ├── URL Handling Group.plist
│ │ │ ├── WebKit Message Display.plist
│ │ │ └── libpurple
│ │ │ ├── accounts.xml
│ │ │ ├── blist.xml
│ │ │ ├── prefs.xml
│ │ │ └── xmpp-caps.xml
│ ├── otr.fingerprints
│ └── otr.private_key
├── chatsecure
│ ├── otr_keystore
│ └── otr_keystore.ofcaes
├── gajim
│ ├── config
│ ├── guardianproject.info.fpr
│ └── guardianproject.info.key3
├── gnupg
│ ├── pubring.gpg
│ ├── random_seed
│ ├── secring.gpg
│ └── trustdb.gpg
├── irssi
│ ├── otr.fp
│ └── otr.key
├── jitsi
│ ├── contactlist.xml
│ └── sip-communicator.properties
├── keyczar
│ ├── privatekey-v0.5b.json
│ ├── privatekey-v0.6b.json
│ ├── publickey-v0.5b.json
│ └── publickey-v0.6b.json
├── kopete
│ ├── fingerprints
│ └── privkeys
├── openssl
│ ├── parse-private-key-PKCS#8.py
│ └── useful-commands.sh
├── pidgin
│ ├── accounts.xml
│ ├── otr.fingerprints
│ └── otr.private_key
├── run-tests.sh
└── xchat
│ ├── otr.fp
│ └── otr.key
└── win32
├── README.md
├── cypherpunks-otr-pubkey.asc
├── fetch-and-verify.sh
├── pidgin-windows-pubkeys.asc
├── pycrypto-pubkey.asc
├── python-pubkeys.asc
└── w32-screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | .DS_Store
4 | output/
5 | tests/openssl/*.pem
6 | tests/openssl/*.properties
7 | pylint.parseable
8 | #
9 | win32/files/*
10 | #
11 | MANIFEST
12 | build/
13 | dist/
14 | keysync.egg-info/
15 | # recommended virtualenv folder
16 | env/
17 |
--------------------------------------------------------------------------------
/.pylint-plugins/astng_hashlib.py:
--------------------------------------------------------------------------------
1 | #
2 | # started from http://www.logilab.org/blogentry/78354
3 | #
4 |
5 | from logilab.astng import MANAGER
6 | from logilab.astng.builder import ASTNGBuilder
7 |
8 | def hashlib_transform(module):
9 | if module.name == 'hashlib':
10 | fake = ASTNGBuilder(MANAGER).string_build('''
11 |
12 | class fakehash(object):
13 | digest_size = -1
14 | def __init__(self, value): pass
15 | def digest(self):
16 | return u''
17 | def hexdigest(self):
18 | return u''
19 | def update(self, value): pass
20 |
21 | class md5(fakehash):
22 | pass
23 |
24 | class sha1(fakehash):
25 | pass
26 |
27 | class sha256(fakehash):
28 | pass
29 |
30 | ''')
31 | for hashfunc in ('sha256', 'sha1', 'md5'):
32 | module.locals[hashfunc] = fake.locals[hashfunc]
33 |
34 | def register(linter):
35 | """called when loaded by pylint --load-plugins, register our tranformation
36 | function here
37 | """
38 | MANAGER.register_transformer(hashlib_transform)
39 |
40 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE.txt
3 | include jenkins-build
4 | include keysync.desktop
5 | include keysync-gui-folder.spec
6 | include keysync-gui-onefile.spec
7 | include man/keysync.1
8 | include py2app-python2.6.patch
9 | include pylint.parseable
10 | include icons/128x128/keysync.png
11 | include icons/add.png
12 | include icons/adium.png
13 | include icons/chatsecure.png
14 | include icons/gajim.png
15 | include icons/gnupg.png
16 | include icons/irssi.png
17 | include icons/jitsi.png
18 | include icons/keysync.icns
19 | include icons/keysync.ico
20 | include icons/keysync.png
21 | include icons/kopete.png
22 | include icons/pidgin.png
23 | include icons/xchat.png
24 | include tests/run-tests.sh
25 | include tests/chatsecure/otr_keystore
26 | recursive-include tests/adium *.*
27 | recursive-include tests/gajim *.*
28 | recursive-include tests/gnupg *.*
29 | recursive-include tests/irssi *.*
30 | recursive-include tests/jitsi *.*
31 | recursive-include tests/keyczar *.*
32 | recursive-include tests/openssl *.*
33 | recursive-include tests/pidgin *.*
34 | recursive-include tests/SAMENESS *.*
35 | recursive-include tests/xchat *.*
36 | include win32/cypherpunks-otr-pubkey.asc
37 | include win32/fetch-and-verify.sh
38 | include win32/pidgin-windows-pubkeys.asc
39 | include win32/pycrypto-pubkey.asc
40 | include win32/python-pubkeys.asc
41 | include win32/README.md
42 | include win32/w32-screenshot.png
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | KeySync - One key for all of your chat apps
3 | ===========================================
4 |
5 | There are many chat apps that support OTR encryption for verifying messages.
6 | Many of us use multiple chat apps, including one for desktop and another for
7 | mobile, or one for Mac OS X and another for GNU/Linux or Windows. The trust
8 | relationships are only stored locally in the app in a format that is specific
9 | to that app. Switching between all of them means that you have to manage your
10 | trust relationships for each app that you use.
11 |
12 | KeySync addresses this problem by allowing you to sync your OTR identity and
13 | trust relationships between multiple apps, which eliminates the need to
14 | re-establish those relationships on each new client/device. It currently works
15 | with [ChatSecure] on Android, and [Pidgin], [Adium], and [Jitsi] on desktop.
16 |
17 | Please note: KeySync does not make it easier to have simultaneous encrypted
18 | chats with the same account logged into multiple clients or devices. There
19 | are limitations in the OTR protocol that make this impossible.
20 |
21 |
22 | How to sync
23 | -----------
24 |
25 | To sync between ChatSecure and your desktop apps, plug in your phone or device
26 | via USB and run the sync. Or you can manually copy the `otr_keystore.ofcaes`
27 | file over to your device's SD Card, where ChatSecure looks for it. Using
28 | ChatSecure, you will need to scan the QRCode that KeySync shows you in order
29 | to complete the sync. The `otr_keystore.ofcaes` file is encrypted to prevent
30 | your private information from leaking out. That QRCode is the password to
31 | your keystore, so do not share it with anyone.
32 |
33 | If you have multiple chat apps that you use, or you are switching from one to
34 | another, you can use KeySync to sync up the trust relationships between
35 | multiple desktop apps. Here's how:
36 |
37 | 1. quit all of the chat apps that you want to
38 | 2. select whichever apps you want to sync
39 | 3. then run the sync
40 |
41 | Now, open your chat apps and you should have synced trust! In case of
42 | problems, it saved your original OTR files in place. They are named using a
43 | long string of numbers that represent the time when they were backed up.
44 |
45 | This is beta software, do not rely on it for strong identity verification. It
46 | is unlikely to mess up so bad as to produce compromised private keys, but
47 | anything is possible. Also, keep in mind that program is handling your
48 | private OTR keys, so make sure that you don't copy, send or email the
49 | `otr_keystore.ofcaes` file somewhere unsafe. All that said, testing
50 | and feedback is greatly appreciated, so we can get it to the point where we
51 | can trust it.
52 |
53 |
54 | Adding apps to KeySync
55 | ----------------------
56 |
57 | This project has libraries for converting the various OTR file formats between
58 | each other. We have focused on the two major versions: [libotr] format and [otr4j],
59 | and then a few variants of those major formats. All OTR implementations can
60 | be supported as long as they can be read and parsed by Python.
61 |
62 | KeySync has preliminary support for Gajim and IRSSI, and it has a modular
63 | architecture to allow for expansion. If you want to add an app that is not
64 | already included, you just need to make a single python file that converts
65 | between that app's OTR data format and the KeySync internal format (documented
66 | below).
67 |
68 |
69 | Reporting bugs
70 | --------------
71 |
72 | We appreciate all feedback, be it bug reports, patches, pull requests, feature
73 | requests, etc. Please add your bugs and requests to our issue tracker:
74 |
75 | https://dev.guardianproject.info/projects/keysync/issues
76 |
77 | Email us at support@guardianproject.info with questions, problems, etc., or
78 | just to let us know that you are using KeySync and find it useful.
79 |
80 |
81 | INSTALL
82 | =======
83 |
84 | KeySync uses lots of Python modules to achieve a smooth syncing
85 | experience. To see a complete list of python modules used on your
86 | platform, see the `dependencies` list in setup.py. Here are some of
87 | the key libraries:
88 |
89 | * BeautifulSoup 4 - http://www.crummy.com/software/BeautifulSoup
90 | * psutil - https://code.google.com/p/psutil
91 | * pure-python-otr - https://github.com/afflux/pure-python-otr
92 | * pyasn1 - http://pyasn1.sourceforge.net/
93 | * pycrypto - https://www.dlitz.net/software/pycrypto
94 | * pyjavaproperties - http://pypi.python.org/pypi/pyjavaproperties
95 | * pymtp - https://github.com/eighthave/pymtp
96 | * pyparsing - http://pyparsing.wikispaces.com
97 | * python-pgpdump - https://pypi.python.org/pypi/pgpdump
98 | * Python Imaging Library - http://www.pythonware.com/products/pil
99 | * qrcode - https://github.com/lincolnloop/python-qrcode
100 | * Tkinter - https://wiki.python.org/moin/TkInter
101 |
102 |
103 | Debian/Ubuntu/Mint/etc
104 | ----------------------
105 |
106 | We're working to get all packages into official Debian/Ubuntu/etc. releases,
107 | in the meantime you can install KeySync by adding our PPA (fingerprint
108 | F50EADDD2234F563):
109 |
110 | sudo add-apt-repository ppa:guardianproject/ppa
111 | sudo apt-get update
112 | sudo apt-get install keysync
113 |
114 | PPA URL: https://launchpad.net/~guardianproject/+archive/ppa
115 |
116 | If you want to install the dependencies because you're going to develop
117 | KeySync, then install them manually with:
118 |
119 | sudo apt-get install python-pyparsing python-pyasn1 python-potr python-pymtp \
120 | python-pyjavaproperties python-beautifulsoup python-qrcode libmtp-dev \
121 | python-pgpdump python-crypto python-psutil python-tk python-imaging-tk
122 |
123 | For Debian, you can try using the Ubuntu PPA, with something like oneiric for
124 | wheezy, and natty for squeeze:
125 |
126 | deb http://ppa.launchpad.net/guardianproject/ppa/ubuntu oneiric main
127 |
128 |
129 | Mac OS X
130 | --------
131 |
132 | For Mac OS X, you can download the binary app from our website:
133 | https://guardianproject.info
134 |
135 | You use Brew, Fink, or MacPorts to install pip and virtualenv, then
136 | use those tools to install KeySync (see the *pip+virtualenv install*
137 | section):
138 |
139 | fink install pip-systempython26 virtualenv-systempython26
140 | sudo port install py27-pip py27-virtualenv
141 |
142 | For homebrew, see: https://gist.github.com/pithyless/1208841
143 |
144 | Once you have pip and virtualenv, then you can start to build the
145 | whole thing. First follow the pip+virtualenv instructions below.
146 | Then come back here and do the following in your virtualenv:
147 |
148 | rm -rf build dist
149 | pip install py2app
150 | python setup.py py2app
151 | ls dist/
152 |
153 | If you are using py2app older than 0.7.4 on Mac OS X 10.6, then you
154 | need to patch py2app to make it work with python2.6:
155 |
156 | pip install --upgrade py2app
157 | patch env/lib/python2.6/site-packages/py2app/build_app.py py2app-python2.6.patch
158 |
159 |
160 | Windows
161 | -------
162 |
163 | For Windows, you can download the binary app from our website:
164 | https://guardianproject.info
165 |
166 | Build instructions for Windows are in win32/README.md in the source folder.
167 |
168 | Fedora/Redhat/RPMs
169 | ------------------
170 |
171 | Install these build dependencies locally, then follow the instructions for
172 | pip+virtualenv below:
173 |
174 | sudo yum install gmp-devel tk tkinter python-pillow python-pillow-tk
175 |
176 |
177 | pip+virtualenv install
178 | ------------------
179 |
180 | Activate your virtual python environment then run pip to install the dependencies:
181 |
182 | virtualenv ./env
183 | . env/bin/activate
184 | pip install -e .
185 | ./keysync
186 |
187 | For a nice step-by-step HOWTO, see:
188 | http://exyr.org/2011/virtualenv-HOWTO/slides.html
189 |
190 | *Note*: Doesn't work on Windows! See win32/README.md
191 |
192 |
193 | =====
194 | USAGE
195 | =====
196 |
197 | Currently, the code allows for reading multiple file formats into a python
198 | dictionary form. The only export method currently activated is for ChatSecure
199 | format in a file called otr_keystore.ofcaes. To use, point the `keysync`
200 | script the app that you want to read OTR info from, and it will generate
201 | `otr_keystore` to send to ChatSecure on your Android device (run
202 | `keysync --help` to see all currently available options).
203 |
204 | keysync --input pidgin
205 |
206 |
207 | =======
208 | FORMATS
209 | =======
210 |
211 | libotr
212 | ------
213 |
214 | Adium:
215 | ~/Library/Application Support/Adium 2.0/Users/Default/otr.private_key
216 | ~/Library/Application Support/Adium 2.0/Users/Default/otr.fingerprints
217 | ~/Library/Application Support/Adium 2.0/Users/Default/Accounts.plist
218 |
219 | Uses the standard libotr files and overall file format for
220 | otr.private_key and otr.fingerprints. Account ID is stored as an
221 | integer which must be referenced from the Accounts.plist to get the
222 | actuall XMPP account name (e.g. me@jabber.org). Uses full word
223 | descriptive tags for the various protocols, e.g. libpurple-oscar-AIM,
224 | libpurple-Jabber, etc.
225 |
226 | Pidgin
227 | GNU/Linux
228 | ~/.purple/otr.private_key
229 | ~/.purple/otr.fingerprints
230 | Windows
231 | %APPDATA%\.purple\otr.private_key
232 | %APPDATA%\.purple\otr.fingerprints
233 |
234 | Uses the standard libotr files and overall file format for
235 | otr.private_key and otr.fingerprints. Account IDs are used directly
236 | in the libotr files. XMPP/Jabber Account IDs include the "Resource"
237 | e.g. me@jabber.org/Resource or me@jabber.org/Pidgin.
238 |
239 | irssi
240 | ~/.irssi/otr/otr.key
241 | ~/.irssi/otr/otr.fp
242 |
243 | Uses the standard libotr file format and files, but names the files
244 | differently, basically abbreviated versions of the libotr names.
245 | Account IDs are used directly in the libotr files.
246 |
247 | xchat
248 | ~/.xchat2/otr/otr.key
249 | ~/.xchat2/otr/otr.fp
250 |
251 | Same as irssi
252 |
253 | otr4j
254 | -----
255 |
256 | ChatSecure:
257 | /data/data/info.guardianproject.otr.app.im/files/otr_keystore
258 |
259 | All OTR information is stored in a single Java .properties
260 | file. Private keys, public key fingerprints, and verification status
261 | are each individual properties. This format also includes the
262 | storage of the remote public keys, unlike libotr. [otr4j]
263 | implementations load the remote public key from the store rather
264 | than always getting it from the OTR session.
265 |
266 | Jitsi:
267 | GNU/Linux
268 | ~/.jitsi/sip-communicator.properties
269 | ~/.jitsi/contactlist.xml
270 | Mac OS X
271 | ~/Library/Application Support/Jitsi/sip-communicator.properties
272 | ~/Library/Application Support/Jitsi/contactlist.xml
273 | Windows
274 | ~/Application Data/Jitsi/sip-communicator.properties
275 | ~/Application Data/Jitsi/contactlist.xml
276 |
277 | All app settings are stored in a single Java .properties file,
278 | including OTR information. Private keys, public key fingerprints,
279 | and verification status are each individual properties.
280 |
281 |
282 | pure-python-otr
283 | ---------------
284 |
285 | pure-python-otr is pure python implementation of the OTR spec. It
286 | includes newer features like Socialist Millionaire's Protocol. The
287 | private key is stored in a separate file per-account. The
288 | fingerprints are stored in the same tab-separated-value format as
289 | [libotr] but with a fingerprint file per-account.
290 |
291 | Gajim:
292 | GNU/Linux:
293 | ~/.local/share/gajim/_SERVERNAME_.key3
294 | ~/.local/share/gajim/_SERVERNAME_.fpr
295 | ~/.config/gajim/config
296 | Windows:
297 | ~/Application Data/Gajim/
298 |
299 | The private key is serialized in a custom format: p, q, g, y, x, written
300 | consecutively to a file as MPIs. See
301 | [potr/compatcrypto/pycrypto.py](https://github.com/afflux/pure-python-otr/blob/master/src/potr/compatcrypto/pycrypto.py).
302 |
303 |
304 | keyczar
305 | -------
306 |
307 | KeyCzar stores keys in JSON files with two different formats: 0.5b and
308 | 0.6b. It uses a special base64 encoding with a URL-safe alphabet:
309 | - replaces +
310 | _ replaces /
311 |
312 | http://code.google.com/p/keyczar/wiki/DsaPrivateKey
313 | http://code.google.com/p/keyczar/wiki/DsaPublicKey
314 |
315 | 0.6b
316 | public:
317 | "q": The DSA subprime
318 | "p": The DSA prime
319 | "g": The DSA base
320 | "y": The DSA public key exponent
321 | "size" : The size of the modulus in bits
322 | private:
323 | "publicKey": The JSON representation of the corresponding DsaPublicKey
324 | "x": The secret exponent of this private key
325 | "size" : The size of the modulus in bits
326 |
327 | 0.5b
328 | public:
329 | "x509": A WebSafeBase64 encoded X509 representation
330 | private:
331 | "pkcs8": A WebSafeBase64 encoded PKCS#8 representation of the private key
332 | "publicKey": A WebSafeBase64 encoding of the key's corresponding DsaPublicKey
333 |
334 |
335 | ZRTP
336 | ----
337 |
338 | A ZID record stores (caches) ZID (ZRTP ID) specific data that helps
339 | ZRTP to achives its key continuity feature. Please refer to the ZRTP
340 | specification to get detailed information about the ZID.
341 |
342 | ZRTP key types:
343 | 2048 bit Diffie-Helman values
344 | 3072 bit Diffie-Helman values
345 | 256 bit Diffie-Helman elliptic curve
346 | 384 bit Diffie-Helman elliptic curve
347 |
348 |
349 |
350 | IMPLEMENTATION
351 | ==============
352 |
353 | Here are some notes on how things are implemented in KeySync.
354 |
355 | Internal key storage
356 | --------------------
357 |
358 | The key idea in the implementation is to get everything into a common format
359 | internally. That common format can then be handed to any class for a given
360 | program, which knows how to output it to the correct file format. The current
361 | internal data format is a dict of dicts representing a key, called 'keydict'.
362 | So first, you have a dict representing a given account with a given key
363 | associated with it. This account name is used as the unique ID. Then the
364 | whole collection of keys, both local private keys and remote public keys, are
365 | collected in meta dict with the account name as the key and the whole dict as
366 | the value. This format allows for easy merging, which enables syncing between
367 | files.
368 |
369 | Sample structure in python dict notation:
370 |
371 | keydict = {
372 | 'userid': {
373 | 'fingerprint': 'ff66e8c909c4deb51cbd4a02b9e6af4d6af215f8',
374 | 'name': 'userid',
375 | 'protocol': 'IRC',
376 | 'resource': 'laptop', # the XMPP "resource"
377 | 'verification': 'verified', # or 'smp' for Socialist Millionares
378 | 'p': '
', # public part of the DSA key
379 | 'q': '', # public part of the DSA key
380 | 'g': '', # public part of the DSA key
381 | 'x': '', # core of private DSA key
382 | 'y': '', # core of public DSA key
383 | },
384 | 'userid2' : { ... },
385 | ...
386 | 'useridn' : { ... }
387 | }
388 |
389 |
390 | Protocol IDs
391 | ------------
392 |
393 | Unfortunately each app has its own string IDs to represent the different IM
394 | protocols. KeySync uses the Pidgin IDs internally. KeySync is currently
395 | focused on the widely deployed common standards of XMPP and IRC. We welcome
396 | contributions for working with the other protocols. These are the IDs that are
397 | currently working throughout:
398 |
399 | prpl-bonjour XMPP Bonjour (serverless XMPP with mDNS discovery)
400 | prpl-irc IRC (Internet Relay Chat)
401 | prpl-jabber XMPP (Jabber)
402 |
403 | Here is the full list of IDs from Pidgin:
404 | https://developer.pidgin.im/wiki/prpl_id
405 |
406 |
407 | [Adium]: https://adium.im/
408 | [ChatSecure]: https://chatsecure.org/
409 | [jitsi]: https://jitsi.org/
410 | [libotr]: https://otr.cypherpunks.ca/
411 | [otr4j]: https://github.com/otr4j/otr4j
412 | [pidgin]: https://pidgin.im/
413 |
--------------------------------------------------------------------------------
/icons/128x128/keysync.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/128x128/keysync.png
--------------------------------------------------------------------------------
/icons/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/add.png
--------------------------------------------------------------------------------
/icons/adium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/adium.png
--------------------------------------------------------------------------------
/icons/chatsecure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/chatsecure.png
--------------------------------------------------------------------------------
/icons/gajim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/gajim.png
--------------------------------------------------------------------------------
/icons/gnupg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/gnupg.png
--------------------------------------------------------------------------------
/icons/irssi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/irssi.png
--------------------------------------------------------------------------------
/icons/jitsi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/jitsi.png
--------------------------------------------------------------------------------
/icons/keysync.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/keysync.icns
--------------------------------------------------------------------------------
/icons/keysync.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/keysync.ico
--------------------------------------------------------------------------------
/icons/keysync.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/keysync.png
--------------------------------------------------------------------------------
/icons/kopete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/kopete.png
--------------------------------------------------------------------------------
/icons/pidgin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/pidgin.png
--------------------------------------------------------------------------------
/icons/xchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/icons/xchat.png
--------------------------------------------------------------------------------
/jenkins-build:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # this is the script run by the Jenkins server to run the build and tests. Be
4 | # sure to always run it in its dir, i.e. ./run-tests.sh, otherwise it might
5 | # remove things that you don't want it to.
6 |
7 | if [ `dirname $0` != "." ]; then
8 | echo "only run this script like ./`basename $0`"
9 | exit
10 | fi
11 |
12 | set -e
13 | set -x
14 |
15 | if [ -z $WORKSPACE ]; then
16 | WORKSPACE=`pwd`
17 | fi
18 |
19 |
20 | #------------------------------------------------------------------------------#
21 | # cache pypi downloads
22 | if [ -z $PIP_DOWNLOAD_CACHE ]; then
23 | export PIP_DOWNLOAD_CACHE=$HOME/.pip_download_cache
24 | fi
25 |
26 | #------------------------------------------------------------------------------#
27 | # run local tests
28 | cd $WORKSPACE/tests
29 | ./run-tests.sh
30 |
31 |
32 | #------------------------------------------------------------------------------#
33 | # test install using site packages
34 | rm -rf $WORKSPACE/env
35 | virtualenv --system-site-packages $WORKSPACE/env
36 | . $WORKSPACE/env/bin/activate
37 | pip install -e $WORKSPACE
38 |
39 | # run tests in new pip+virtualenv install
40 | . $WORKSPACE/env/bin/activate
41 | keysync=$WORKSPACE/env/bin/keysync $WORKSPACE/tests/run-tests.sh
42 |
43 |
44 | #------------------------------------------------------------------------------#
45 | # test install using packages from pypi
46 | rm -rf $WORKSPACE/env
47 | virtualenv --no-site-packages $WORKSPACE/env
48 | . $WORKSPACE/env/bin/activate
49 | pip install -e $WORKSPACE
50 |
51 | # run tests in new pip+virtualenv install
52 | . $WORKSPACE/env/bin/activate
53 | keysync=$WORKSPACE/env/bin/keysync $WORKSPACE/tests/run-tests.sh
54 |
55 |
56 | #------------------------------------------------------------------------------#
57 | # run pyflakes
58 |
59 | cd $WORKSPACE
60 | # there are a couple warnings to work around in keysync and keysync-gui
61 | #pyflakes keysync keysync-gui otrapps/*.py setup.py
62 | pyflakes otrapps/*.py setup.py
63 |
64 |
65 | #------------------------------------------------------------------------------#
66 | # run pylint
67 |
68 | cd $WORKSPACE
69 | set +e
70 | # disable E1101 until there is a plugin to handle this properly:
71 | # Module 'sys' has no '_MEIPASS' member
72 | # disable F0401 until there is a plugin to handle this properly:
73 | # keysync-gui:25: [F] Unable to import 'ordereddict'
74 | PYTHONPATH=$WORKSPACE/.pylint-plugins \
75 | pylint --output-format=parseable --reports=n \
76 | --disable=E1101,F0401 \
77 | --load-plugins astng_hashlib \
78 | otrapps/*.py keysync keysync-gui > $WORKSPACE/pylint.parseable
79 | # only tell jenkins there was an error if we got ERROR or FATAL
80 | [ $(($? & 1)) = "1" ] && exit 1
81 | [ $(($? & 2)) = "2" ] && exit 2
82 | set -e
83 |
--------------------------------------------------------------------------------
/keysync:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''keysync is a command line program'''
4 |
5 | from __future__ import print_function
6 | import sys
7 | import os
8 | import platform
9 | import argparse
10 |
11 | # if python < 2.7, get OrderedDict from a standalone lib
12 | if sys.version_info[0] == 2 and sys.version_info[1] < 7:
13 | from ordereddict import OrderedDict
14 | else:
15 | from collections import OrderedDict
16 |
17 | import otrapps.util
18 | import otrapps
19 | from otrapps.chatsecure import ChatSecureProperties
20 |
21 | # no argv passed in here because argparse just checks sys.argv directly
22 | def main():
23 | '''this is the main entry into this program'''
24 |
25 | if len(sys.argv) == 1:
26 | sys.argv.append('--help') # if no args, show help
27 |
28 | # defaults
29 | if platform.system() == 'Darwin':
30 | default_input = 'adium'
31 | else:
32 | default_input = 'pidgin'
33 |
34 | default_output = 'chatsecure'
35 |
36 |
37 | parser = argparse.ArgumentParser()
38 | # Note: the 'default' argument is not used with the input and output args
39 | # below, because the combination of the append action and default, means
40 | # the default will ALWAYS appear in the actions.
41 | # this may or may not be an argparse bug.
42 | parser.add_argument('-i', '--input', action='append',
43 | choices=sorted(otrapps.apps_supported),
44 | help="specify which programs to take as input. if multiple then they'll be merged (default: %s)" % (default_input))
45 | parser.add_argument('-o', '--output', action='append',
46 | choices=sorted(otrapps.apps_supported),
47 | help="specify which format to write out. if multiple then each will be written out (default: %s)" % (default_output))
48 | parser.add_argument('--output-folder', default=os.getcwd(),
49 | help='write the output files to this folder (default: current folder)')
50 | parser.add_argument('--no-qrcode', action='store_true', default=False,
51 | help='do not print the ChatSecure QR Code to the terminal')
52 | parser.add_argument('-q', '--quiet', action='store_true', default=False,
53 | help='do not print anything to the terminal')
54 | parser.add_argument('-t', '--test', help=argparse.SUPPRESS, default=None)
55 | parser.add_argument('--version', action='version', version='%(prog)s {v}'.format(v=otrapps.__version__))
56 | args = parser.parse_args()
57 |
58 | # manually set defaults, see Note above
59 | if args.input == None or len(args.input) == 0:
60 | args.input = [default_input]
61 | if args.output == None or len(args.output) == 0:
62 | args.output = [default_output]
63 |
64 | # downcase all names to be a little more friendly
65 | args.input = [i.lower() for i in args.input]
66 | args.output = [o.lower() for o in args.output]
67 |
68 | keydict = dict()
69 | for app in args.input:
70 | print('Reading %s files...' % ( app ))
71 | # special case GB for now 'cause of this keyfile business
72 | if app == 'chatsecure':
73 | keyfile = os.path.join(args.output_folder, ChatSecureProperties.keyfile)
74 | if os.path.exists(keyfile):
75 | otrapps.util.merge_keydicts(keydict, ChatSecureProperties.parse(keyfile))
76 | else:
77 | encrypted_keyfile = os.path.join(args.output_folder, ChatSecureProperties.encryptedkeyfile)
78 | if os.path.exists(encrypted_keyfile):
79 | if sys.version_info[0] == 2:
80 | password = raw_input("What is the encryption password for keystore \"otr_keystore.ofcaes\"?\n")
81 | else:
82 | password = input("What is the encryption password for keystore \"otr_keystore.ofcaes\"?\n")
83 | keyfile = ChatSecureProperties._decrypt_ofcaes(encrypted_keyfile, password)
84 | otrapps.util.merge_keydicts(keydict, ChatSecureProperties.parse(keyfile))
85 | else:
86 | print(('ChatSecure WARNING: No usable "' + ChatSecureProperties.keyfile +
87 | '" or "' + ChatSecureProperties.encryptedkeyfile +
88 | '" file found, not reading keys from ChatSecure!'))
89 | break
90 |
91 | properties = otrapps.apps[app]
92 | if args.test:
93 | # example: "tests/gajim/"
94 | settings_dir = os.path.join(args.test, app)
95 | otrapps.util.merge_keydicts(keydict, properties.parse(settings_dir))
96 | else:
97 | otrapps.util.merge_keydicts(keydict, properties.parse())
98 |
99 | if keydict:
100 | keydict = OrderedDict(sorted(keydict.items(), key=lambda t: t[0]))
101 | otrapps.make_outdir(args.output_folder, '')
102 | for app in args.output:
103 | # once again special case GB
104 | if 'chatsecure' in args.output:
105 | ChatSecureProperties.write(keydict, args.output_folder)
106 | if not args.quiet and ChatSecureProperties.password:
107 | if not args.no_qrcode and sys.stdout.isatty():
108 | print('\nScan this QR Code:')
109 | import qrcode
110 | pwqr = qrcode.QRCode()
111 | pwqr.add_data(ChatSecureProperties.password)
112 | pwqr.print_tty()
113 | print(('\nor enter this password into ChatSecure: \n\t' + ChatSecureProperties.password))
114 | break
115 |
116 | properties = otrapps.apps[app]
117 | properties.write(keydict, args.output_folder)
118 |
119 | if __name__ == "__main__":
120 | main()
121 |
--------------------------------------------------------------------------------
/keysync-gui-folder.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 | a = Analysis(['keysync-gui'],
3 | pathex=['c:\\Users\\abel\\Documents\\keysync'],
4 | hiddenimports=[],
5 | hookspath=None,
6 | runtime_hooks=None)
7 | pyz = PYZ(a.pure)
8 | icon_dir = Tree('icons', prefix='icons')
9 | exe = EXE(pyz,
10 | a.scripts,
11 | exclude_binaries=True,
12 | name='KeySync.exe',
13 | icon='icons/keysync.ico',
14 | onefile=True,
15 | debug=False,
16 | strip=None,
17 | upx=True,
18 | console=True)
19 | coll = COLLECT(exe,
20 | a.binaries,
21 | a.zipfiles,
22 | a.datas,
23 | icon_dir,
24 | strip=None,
25 | upx=True,
26 | name='KeySync')
27 |
--------------------------------------------------------------------------------
/keysync-gui-onefile.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 | a = Analysis(['keysync-gui'],
3 | pathex=['c:\\Users\\abel\\Documents\\keysync'],
4 | hiddenimports=[],
5 | hookspath=None,
6 | runtime_hooks=None)
7 | pyz = PYZ(a.pure)
8 | icon_dir = Tree('icons', prefix='icons')
9 | exe = EXE(pyz,
10 | a.scripts,
11 | a.binaries,
12 | a.zipfiles,
13 | a.datas,
14 | icon_dir,
15 | name='KeySync.exe',
16 | icon='icons/keysync.ico',
17 | debug=False,
18 | strip=None,
19 | upx=True,
20 | console=False)
21 |
--------------------------------------------------------------------------------
/keysync.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=KeySync
3 | GenericName=KeySync
4 | Comment=Sync all the OTR trust relationships between multiple chat apps
5 | Exec=keysync-gui
6 | Terminal=false
7 | Type=Application
8 | Icon=keysync
9 | Categories=Utility;
10 | StartupNotify=false
11 | Keywords=secure;private;verified;chat;im;messaging;key;encryption;otr;openpgp;pgp;gpg;gnupg;jabber;xmpp;irc;
12 |
--------------------------------------------------------------------------------
/man/keysync.1:
--------------------------------------------------------------------------------
1 | .\" Hey, EMACS: -*- nroff -*-
2 | .\" First parameter, NAME, should be all caps
3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
4 | .\" other parameters are allowed: see man(7), man(1)
5 | .TH keysync 1 "2012 Apr 21"
6 | .\" Please adjust this date whenever revising the manpage.
7 | .\"
8 | .\" Some roff macros, for reference:
9 | .\" .nh disable hyphenation
10 | .\" .hy enable hyphenation
11 | .\" .ad l left justify
12 | .\" .ad b justify to both left and right margins
13 | .\" .nf disable filling
14 | .\" .fi enable filling
15 | .\" .br insert line break
16 | .\" .sp insert n+1 empty lines
17 | .\" for manpage-specific macros, see man(7)
18 | .SH NAME
19 | keysync \- convert OTR key info between different formats
20 | .SH SYNOPSIS
21 | .B keysync
22 | .RI [options]
23 | .RI [path]
24 | .br
25 | .SH DESCRIPTION
26 | This manual page documents briefly the
27 | .B keysync
28 | command.
29 | .PP
30 | .\" TeX users may be more comfortable with the \fB\fP and
31 | .\" \fI\fP escape sequences to invode bold face and italics,
32 | .\" respectively.
33 | \fBkeysync\fP is a program for converting the various OTR file formats between
34 | each other. KeySync works with the two major OTR keystore versions: libotr
35 | format and otr4j, and then a few variants of those major formats. There is
36 | some preliminary support for pure-python-otr keystores.
37 |
38 | It can read and write the OTR keystore formats from Adium, ChatSecure, IRSSI,
39 | Jitsi, Pidgin, and XChat. It can also read some data from Gajim and GnuPG,
40 | but that is not yet fully implemented.
41 |
42 | For syncing the OTR data to ChatSecure on an Android device, use
43 | \fBkeysync-gui\fP.
44 |
45 | .SH WARNING!
46 | This is beta software, do not rely on it for strong identity verification. It
47 | is unlikely to mess up so bad as to produce compromised secret keys, but
48 | anything is possible. Also, keep in mind that program is handling your
49 | private OTR keys, so careful with the files produced with keysync! That said,
50 | testing and feedback is greatly appreciated, so we can get it to the point
51 | where it can be fully trusted.
52 | .SH OPTIONS
53 | .TP
54 | .B \-h, --help
55 | show this help message and exit
56 | .TP
57 | .B \-i, --input {adium,chatsecure,gajim,gpg,irssi,jitsi,pidgin,xchat}
58 | specify which programs to take as input. if multiple then they'll be merged (default: pidgin)
59 | .TP
60 | .B \-o, --output {adium,chatsecure,gajim,irssi,jitsi,pidgin,xchat}
61 | specify which format to write out. if multiple then
62 | each will be written out (default: chatsecure)
63 | .TP
64 | .B \--output-folder OUTPUT_FOLDER
65 | write the output files to this folder (default: current folder)
66 | .TP
67 | .B \--no-qrcode
68 | do not print the ChatSecure QR Code to the terminal
69 | .TP
70 | .B \-q, --quiet
71 | do not print anything to the terminal
72 | .TP
73 | .B \--version
74 | show program's version number and exit
75 | .SH AUTHOR
76 | keysync was written by The Guardian Project .
77 | .PP
78 |
--------------------------------------------------------------------------------
/otrapps/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | '''sets up the otrapps module with all of the currently supported apps'''
3 |
4 | import os
5 | import sys
6 |
7 | from pkg_resources import get_distribution, DistributionNotFound
8 | try:
9 | _dist = get_distribution('keysync')
10 | if not __file__.startswith(os.path.join(_dist.location, 'otrapps')):
11 | raise DistributionNotFound
12 | except DistributionNotFound:
13 | # probably running from source repo or another version is installed
14 | __version__ = '(local version)'
15 | else:
16 | __version__ = _dist.version
17 |
18 |
19 | __all__ = ['adium', 'chatsecure', 'irssi', 'jitsi', 'pidgin', 'gajim', 'gnupg', 'xchat', 'kopete',]
20 |
21 | if __name__ == '__main__':
22 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
23 | import otrapps.adium
24 | import otrapps.chatsecure
25 | import otrapps.irssi
26 | import otrapps.jitsi
27 | import otrapps.pidgin
28 | import otrapps.gajim
29 | import otrapps.gnupg
30 | import otrapps.xchat
31 | import otrapps.kopete
32 |
33 | apps = { 'adium' : otrapps.adium.AdiumProperties,
34 | 'chatsecure': otrapps.chatsecure.ChatSecureProperties,
35 | 'irssi' : otrapps.irssi.IrssiProperties,
36 | 'jitsi' : otrapps.jitsi.JitsiProperties,
37 | 'pidgin' : otrapps.pidgin.PidginProperties,
38 | 'gajim' : otrapps.gajim.GajimProperties,
39 | 'gnupg' : otrapps.gnupg.GnuPGProperties,
40 | 'xchat' : otrapps.xchat.XchatProperties,
41 | 'kopete' : otrapps.kopete.KopeteProperties,
42 | }
43 | apps_supported = apps.keys()
44 |
45 | def make_outdir(output_folder, subdir):
46 | '''create the folder that the results will be written to'''
47 | outdir = os.path.join(output_folder, subdir)
48 | if not os.path.exists(outdir):
49 | os.makedirs(outdir)
50 | return outdir
51 |
52 |
53 |
--------------------------------------------------------------------------------
/otrapps/adium.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2.6
2 | # -*- coding: utf-8 -*-
3 |
4 | from __future__ import print_function
5 | import os
6 | import platform
7 | import plistlib
8 | import sys
9 |
10 | if __name__ == '__main__':
11 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
12 | import otrapps.util
13 | from otrapps.otr_private_key import OtrPrivateKeys
14 | from otrapps.otr_fingerprints import OtrFingerprints
15 |
16 | class AdiumProperties():
17 |
18 | path = os.path.expanduser('~/Library/Application Support/Adium 2.0/Users/Default')
19 | accountsfile = 'Accounts.plist'
20 | keyfile = 'otr.private_key'
21 | fingerprintfile = 'otr.fingerprints'
22 | files = (accountsfile, keyfile, fingerprintfile)
23 |
24 | @staticmethod
25 | def _get_accounts_from_plist(settingsdir):
26 | '''get dict of accounts from Accounts.plist'''
27 | # convert index numbers used for the name into the actual account name
28 | accountsfile = os.path.join(settingsdir, 'Accounts.plist')
29 | print('accountsfile: ', end=' ')
30 | print(accountsfile)
31 | if not os.path.exists(accountsfile):
32 | oldaccountsfile = accountsfile
33 | accountsfile = os.path.join(AdiumProperties.path, AdiumProperties.accountsfile)
34 | if platform.system() == 'Darwin' and os.path.exists(accountsfile):
35 | print('Adium WARNING: "' + oldaccountsfile + '" does not exist! Using:')
36 | print('\t"' + accountsfile + '"')
37 | else:
38 | print('Adium ERROR: No usable Accounts.plist file found, cannot create Adium files!')
39 | return []
40 | # make sure the plist is in XML format, not binary,
41 | # this should be converted to use python-biplist.
42 | if platform.system() == 'Darwin':
43 | os.system("plutil -convert xml1 '" + accountsfile + "'")
44 | return plistlib.readPlist(accountsfile)['Accounts']
45 |
46 | @staticmethod
47 | def parse(settingsdir=None):
48 | if settingsdir == None:
49 | settingsdir = AdiumProperties.path
50 |
51 | kf = os.path.join(settingsdir, AdiumProperties.keyfile)
52 | if os.path.exists(kf):
53 | keydict = OtrPrivateKeys.parse(kf)
54 | else:
55 | keydict = dict()
56 |
57 | accounts = AdiumProperties._get_accounts_from_plist(settingsdir)
58 | newkeydict = dict()
59 | for adiumIndex, key in keydict.items():
60 | for account in accounts:
61 | if account['ObjectID'] == key['name']:
62 | name = account['UID']
63 | key['name'] = name
64 | newkeydict[name] = key
65 | keydict = newkeydict
66 |
67 | fpf = os.path.join(settingsdir, AdiumProperties.fingerprintfile)
68 | if os.path.exists(fpf):
69 | otrapps.util.merge_keydicts(keydict, OtrFingerprints.parse(fpf))
70 |
71 | return keydict
72 |
73 | @staticmethod
74 | def write(keydict, savedir='./'):
75 | if not os.path.exists(savedir):
76 | raise Exception('"' + savedir + '" does not exist!')
77 |
78 | # need when converting account names back to Adium's account index number
79 | accountsplist = AdiumProperties._get_accounts_from_plist(savedir)
80 |
81 | kf = os.path.join(savedir, AdiumProperties.keyfile)
82 | adiumkeydict = dict()
83 | for name, key in keydict.items():
84 | name = key['name']
85 | for account in accountsplist:
86 | if account['UID'] == name:
87 | key['name'] = account['ObjectID']
88 | adiumkeydict[name] = key
89 | OtrPrivateKeys.write(keydict, kf)
90 |
91 | accounts = []
92 | for account in accountsplist:
93 | accounts.append(account['ObjectID'])
94 | fpf = os.path.join(savedir, AdiumProperties.fingerprintfile)
95 | OtrFingerprints.write(keydict, fpf, accounts)
96 |
97 |
98 | if __name__ == '__main__':
99 |
100 | import pprint
101 |
102 | print('Adium stores its files in ' + AdiumProperties.path)
103 |
104 | if len(sys.argv) == 2:
105 | settingsdir = sys.argv[1]
106 | else:
107 | settingsdir = '../tests/adium'
108 | keydict = AdiumProperties.parse(settingsdir)
109 | pprint.pprint(keydict)
110 |
111 | AdiumProperties.write(keydict, '/tmp')
112 |
--------------------------------------------------------------------------------
/otrapps/chatsecure.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing ChatSecure's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import hashlib
7 | import os
8 | import sys
9 | import pyjavaproperties
10 | import subprocess
11 | import tempfile
12 |
13 | if __name__ == '__main__':
14 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
15 | import otrapps.util
16 |
17 | class ChatSecureProperties():
18 |
19 | path = '/data/data/info.guardianproject.otr.app.im/files/otr_keystore'
20 | keyfile = 'otr_keystore'
21 | encryptedkeyfile = keyfile + '.ofcaes'
22 | files = (keyfile, encryptedkeyfile)
23 |
24 | password = None
25 |
26 | @staticmethod
27 | def parse(filename):
28 | '''parse the given file into the standard keydict'''
29 | # the parsing and generation is done in separate passes so that
30 | # multiple properties are combined into a single keydict per account,
31 | # containing all of the fields
32 | p = pyjavaproperties.Properties()
33 | p.load(open(filename))
34 | parsed = []
35 | for item in p.items():
36 | propkey = item[0]
37 | if propkey.endswith('.publicKey'):
38 | id = '.'.join(propkey.split('.')[0:-1])
39 | parsed.append(('public-key', id, item[1]))
40 | elif propkey.endswith('.publicKey.verified'):
41 | keylist = propkey.split('.')
42 | fingerprint = keylist[-3]
43 | id = '.'.join(keylist[0:-3])
44 | parsed.append(('verified', id, fingerprint))
45 | elif propkey.endswith('.privateKey'):
46 | id = '.'.join(propkey.split('.')[0:-1])
47 | parsed.append(('private-key', id, item[1]))
48 | # create blank keys for all IDs
49 | keydict = dict()
50 | for keydata in parsed:
51 | name = keydata[1]
52 | if not name in keydict:
53 | keydict[name] = dict()
54 | keydict[name]['name'] = name
55 | keydict[name]['protocol'] = 'prpl-jabber'
56 | if keydata[0] == 'private-key':
57 | cleaned = keydata[2].replace('\\n', '')
58 | numdict = otrapps.util.ParsePkcs8(cleaned)
59 | for num in ('g', 'p', 'q', 'x'):
60 | keydict[name][num] = numdict[num]
61 | elif keydata[0] == 'verified':
62 | keydict[name]['verification'] = 'verified'
63 | fingerprint = keydata[2].lower()
64 | otrapps.util.check_and_set(keydict[name], 'fingerprint', fingerprint)
65 | elif keydata[0] == 'public-key':
66 | cleaned = keydata[2].replace('\\n', '')
67 | numdict = otrapps.util.ParseX509(cleaned)
68 | for num in ('y', 'g', 'p', 'q'):
69 | keydict[name][num] = numdict[num]
70 | fingerprint = otrapps.util.fingerprint((numdict['y'], numdict['g'], numdict['p'], numdict['q']))
71 | otrapps.util.check_and_set(keydict[name], 'fingerprint', fingerprint)
72 | return keydict
73 |
74 | @staticmethod
75 | def write(keydict, savedir, password=None):
76 | '''given a keydict, generate a chatsecure file in the savedir'''
77 | p = pyjavaproperties.Properties()
78 | for name, key in keydict.items():
79 | # only include XMPP keys, since ChatSecure only supports XMPP
80 | # accounts, so we avoid spreading private keys around
81 | if key['protocol'] == 'prpl-jabber' or key['protocol'] == 'prpl-bonjour':
82 | if 'y' in key:
83 | p.setProperty(key['name'] + '.publicKey', otrapps.util.ExportDsaX509(key))
84 | if 'x' in key:
85 | if not password:
86 | h = hashlib.sha256()
87 | h.update(os.urandom(16)) # salt
88 | h.update(bytes(key['x']))
89 | password = h.digest().encode('base64')
90 | p.setProperty(key['name'] + '.privateKey', otrapps.util.ExportDsaPkcs8(key))
91 | if 'fingerprint' in key:
92 | p.setProperty(key['name'] + '.fingerprint', key['fingerprint'])
93 | if 'verification' in key and key['verification'] != None:
94 | p.setProperty(key['name'] + '.' + key['fingerprint'].lower()
95 | + '.publicKey.verified', 'true')
96 | fd, filename = tempfile.mkstemp()
97 | f = os.fdopen(fd, 'w')
98 | p.store(f)
99 |
100 | # if there is no password, then one has not been set, or there
101 | # are not private keys included in the file, so its a lower
102 | # risk file. Encryption only needs to protect the meta data,
103 | # not the private keys. Therefore, its not as bad to generate
104 | # a "random" password here
105 | if not password:
106 | password = os.urandom(32).encode('base64')
107 |
108 | # create passphrase file from the first private key
109 | cmd = ['openssl', 'aes-256-cbc', '-pass', 'stdin', '-in', filename,
110 | '-out', os.path.join(savedir, 'otr_keystore.ofcaes')]
111 | p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
112 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
113 | ChatSecureProperties.password = password
114 | print((p.communicate(password)))
115 |
116 | @staticmethod
117 | def _decrypt_ofcaes(ofcaes_filename, password):
118 | ''' Decrypt an encrypted key file (with user-supplied password).'''
119 | # It might be a bad idea to write out this unencrypted file.
120 |
121 | # get a tmp place to put the decrypted file
122 | fd, filename = tempfile.mkstemp()
123 | f = os.fdopen(fd, 'w')
124 | f.close()
125 |
126 | # same as above, but with the -d flag to decrypt
127 | cmd = ['openssl', 'aes-256-cbc', '-d', '-pass', 'stdin', '-in', ofcaes_filename,
128 | '-out', filename]
129 | p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
130 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
131 | p.communicate(password)
132 | return filename
133 |
134 |
135 | #------------------------------------------------------------------------------#
136 | # for testing from the command line:
137 | def main(argv):
138 | import pprint
139 |
140 | print('ChatSecure stores its files in ' + ChatSecureProperties.path)
141 |
142 | if len(sys.argv) == 2:
143 | settingsfile = sys.argv[1]
144 | else:
145 | settingsfile = '../tests/chatsecure/otr_keystore'
146 |
147 | p = ChatSecureProperties.parse(settingsfile)
148 | print('----------------------------------------')
149 | pprint.pprint(p)
150 | print('----------------------------------------')
151 |
152 | if __name__ == "__main__":
153 | main(sys.argv[1:])
154 |
--------------------------------------------------------------------------------
/otrapps/errors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python2.4
2 | #
3 | # Copyright 2008 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """
18 | Contains hierarchy of all possible exceptions thrown by Keyczar.
19 |
20 | @author: arkajit.dey@gmail.com (Arkajit Dey)
21 | """
22 |
23 | class KeyczarError(Exception):
24 | """Indicates exceptions raised by a Keyczar class."""
25 |
26 | class BadVersionError(KeyczarError):
27 | """Indicates a bad version number was received."""
28 |
29 | def __init__(self, version):
30 | KeyczarError.__init__(self,
31 | "Received a bad version number: " + str(version))
32 |
33 | class Base64DecodingError(KeyczarError):
34 | """Indicates an error while performing Base 64 decoding."""
35 |
36 | class InvalidSignatureError(KeyczarError):
37 | """Indicates an invalid ciphertext signature."""
38 |
39 | def __init__(self):
40 | KeyczarError.__init__(self, "Invalid ciphertext signature")
41 |
42 | class KeyNotFoundError(KeyczarError):
43 | """Indicates a key with a certain hash id was not found."""
44 |
45 | def __init__(self, hash):
46 | KeyczarError.__init__(self,
47 | "Key with hash identifier %s not found." % hash)
48 |
49 | class ShortCiphertextError(KeyczarError):
50 | """Indicates a ciphertext too short to be valid."""
51 |
52 | def __init__(self, length):
53 | KeyczarError.__init__(self,
54 | "Input of length %s is too short to be valid ciphertext." % length)
55 |
56 | class ShortSignatureError(KeyczarError):
57 | """Indicates a signature too short to be valid."""
58 |
59 | def __init__(self, length):
60 | KeyczarError.__init__(self,
61 | "Input of length %s is too short to be valid signature." % length)
62 |
63 | class NoPrimaryKeyError(KeyNotFoundError):
64 | """Indicates missing primary key."""
65 |
66 | def __init__(self):
67 | KeyczarError.__init__(self, "No primary key found")
68 |
--------------------------------------------------------------------------------
/otrapps/gajim.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing Gajim's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import os
7 | import glob
8 | import platform
9 | import sys
10 | import re
11 | import collections
12 | import shutil
13 |
14 | import potr
15 |
16 | if __name__ == '__main__':
17 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
18 |
19 | import otrapps.util
20 | from otrapps.otr_fingerprints import OtrFingerprints
21 |
22 | # the private key is stored in ~/.local/share/gajim/_SERVERNAME_.key_file
23 | # the fingerprints are stored in ~/.local/share/gajim/_SERVERNAME_.fpr
24 | # the accounts are stored in ~/.config/gajim/config
25 |
26 |
27 | class GajimProperties():
28 |
29 | if platform.system() == 'Windows':
30 | path = os.path.expanduser('~/Application Data/Gajim')
31 | accounts_path = '???'
32 | else:
33 | accounts_file = 'config'
34 | path = os.path.expanduser('~/.local/share/gajim')
35 | accounts_path = os.path.expanduser('~/.config/gajim')
36 |
37 | @staticmethod
38 | def _parse_account_config(accounts_path):
39 | """
40 | Crudely parses the dot-style config syntax of gajim's config file
41 | """
42 | if accounts_path is None:
43 | accounts_path = GajimProperties.accounts_path
44 |
45 | accounts_config = os.path.join(accounts_path, GajimProperties.accounts_file)
46 |
47 | keys = ['name', 'hostname', 'resource']
48 | patterns = []
49 | for key in keys:
50 | # matches lines like:
51 | # accounts.guardianproject.info.hostname = "guardianproject.info"
52 | patterns.append((key, re.compile('accounts\.(.*)\.%s = (.*)' % key)))
53 |
54 | accounts = collections.defaultdict(dict)
55 | for line in open(accounts_config, 'r'):
56 | for key, pattern in patterns:
57 | for match in re.finditer(pattern, line):
58 | accounts[match.groups()[0]][key] = match.groups()[1]
59 |
60 | return accounts
61 |
62 | @staticmethod
63 | def parse(settingsdir=None):
64 | if settingsdir is None:
65 | settingsdir = GajimProperties.path
66 | accounts_config = GajimProperties.accounts_path
67 | else:
68 | accounts_config = settingsdir
69 |
70 | keydict = dict()
71 | for fpf in glob.glob(os.path.join(settingsdir, '*.fpr')):
72 | print('Reading in ' + fpf)
73 | keys = OtrFingerprints.parse(fpf)
74 |
75 | # replace gajim's 'xmpp' protocol with 'prpl-jabber' that we use in keysync
76 | for key, value in keys.items():
77 | value['protocol'] = 'prpl-jabber'
78 | keys[key] = value
79 |
80 | otrapps.util.merge_keydicts(keydict, keys)
81 |
82 | accounts = GajimProperties._parse_account_config(accounts_config)
83 |
84 | for key_file in glob.glob(os.path.join(settingsdir, '*.key3')):
85 | account_name = os.path.splitext(os.path.basename(key_file))[0]
86 | if not account_name in accounts.keys():
87 | print("ERROR found %s not in the account list", key_file)
88 | continue
89 | with open(key_file, 'rb') as key_file:
90 | name = '%s@%s' % (accounts[account_name]['name'],
91 | accounts[account_name]['hostname'])
92 | if name in keydict:
93 | key = keydict[name]
94 | else:
95 | key = dict()
96 | key['name'] = name
97 | key['protocol'] = 'prpl-jabber'
98 | key['resource'] = accounts[account_name]['resource']
99 |
100 | pk = potr.crypt.PK.parsePrivateKey(key_file.read())[0]
101 | keydata = ['y', 'g', 'p', 'q', 'x']
102 | for data in keydata:
103 | key[data] = getattr(pk.priv, data)
104 |
105 | key['fingerprint'] = otrapps.util.fingerprint((key['y'], key['g'],
106 | key['p'], key['q']))
107 |
108 | keydict[key['name']] = key
109 |
110 | return keydict
111 |
112 | @staticmethod
113 | def write(keys, savedir):
114 | if not os.path.exists(savedir):
115 | raise Exception('"' + savedir + '" does not exist!')
116 |
117 | # get existing accounts, we need that to figure out how to call the keys
118 | if os.path.exists(os.path.join(savedir, GajimProperties.accounts_file)):
119 | accountsdir = savedir
120 | elif os.path.exists(os.path.join(GajimProperties.accounts_path,
121 | GajimProperties.accounts_file)):
122 | accountsdir = GajimProperties.accounts_path
123 | else:
124 | raise Exception('Cannot find "' + GajimProperties.accounts_file
125 | + '" in "' + savedir + '"')
126 | accounts = GajimProperties._parse_account_config(accountsdir)
127 |
128 | # now for each account, write the fingerprints and key
129 | accounts_written = set()
130 | for account_name in accounts:
131 | xmpp_name = accounts[account_name]['name'] + '@' + accounts[account_name]['hostname']
132 | if not xmpp_name in keys:
133 | # no private key for this account, skip it
134 | continue
135 | key = keys[xmpp_name]
136 | if not 'x' in key:
137 | # this is not a private key, nothing to do here
138 | continue
139 |
140 | # write fingerprints. We do this ourselves to make sure we get the right line-endings.
141 | with open(os.path.join(savedir, account_name + '.fpr'), 'w') as fp_file:
142 | for fp_name, fp_key in keys.items():
143 | if 'fingerprint' in fp_key and 'verification' in fp_key:
144 | row = [fp_name, xmpp_name, 'xmpp', fp_key['fingerprint'], fp_key['verification']]
145 | fp_file.write('\t'.join(row)+'\n')
146 |
147 | # write private key
148 | private_key = potr.compatcrypto.DSAKey((key['y'], key['g'], key['p'], key['q'], key['x']), private=True)
149 | with open(os.path.join(savedir, account_name + '.key3'), 'wb') as key_file:
150 | key_file.write(private_key.serializePrivateKey())
151 |
152 | print("Wrote key for Gajim:",xmpp_name)
153 | accounts_written.add(xmpp_name)
154 |
155 | # check for unwritten keys
156 | for key_name in keys.keys():
157 | if 'x' in keys[key_name]:
158 | if key_name not in accounts_written:
159 | print("No Gajim accont found for",key_name+", key has not been written.")
160 |
161 |
162 | #------------------------------------------------------------------------------#
163 | # for testing from the command line:
164 | def main(argv):
165 | import pprint
166 |
167 | print('Gajim stores its files in ' + GajimProperties.path)
168 |
169 | if len(sys.argv) == 2:
170 | settingsdir = sys.argv[1]
171 | else:
172 | settingsdir = '../tests/gajim'
173 |
174 | keydict = GajimProperties.parse(settingsdir)
175 | print('----------------------------------------')
176 | pprint.pprint(keydict)
177 | print('----------------------------------------')
178 | if not os.path.exists(os.path.join('/tmp', GajimProperties.accounts_file)):
179 | shutil.copy(os.path.join(settingsdir, GajimProperties.accounts_file), '/tmp')
180 | GajimProperties.write(keydict, '/tmp')
181 |
182 | if __name__ == "__main__":
183 | main(sys.argv[1:])
184 |
--------------------------------------------------------------------------------
/otrapps/gnupg.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from __future__ import print_function
5 | import os
6 | import sys
7 | import pgpdump
8 |
9 |
10 | class GnuPGProperties():
11 |
12 | path = os.path.expanduser('~/.gnupg')
13 | secring = 'secring.gpg'
14 | pubring = 'pubring.gpg'
15 | files = (secring, pubring)
16 |
17 | @staticmethod
18 | def parse(settingsdir=None):
19 | if settingsdir == None:
20 | settingsdir = GnuPGProperties.path
21 |
22 | secring_file = os.path.join(settingsdir, GnuPGProperties.secring)
23 | if not os.path.exists(secring_file):
24 | return dict()
25 | rawdata = GnuPGProperties.load_data(secring_file)
26 | try:
27 | data = pgpdump.BinaryData(rawdata)
28 | except pgpdump.utils.PgpdumpException, e:
29 | print("gnupg: %s" % (e))
30 | return dict()
31 | packets = list(data.packets())
32 |
33 | names = []
34 | keydict = dict()
35 | for packet in packets:
36 | values = dict()
37 | if isinstance(packet, pgpdump.packet.SecretSubkeyPacket):
38 | if packet.pub_algorithm_type == "dsa":
39 | values['p'] = packet.prime
40 | values['q'] = packet.group_order
41 | values['g'] = packet.group_gen
42 | values['y'] = packet.key_value
43 | values['x'] = packet.exponent_x
44 | # the data comes directly from secret key, mark verified
45 | values['verification'] = 'verified'
46 | values['fingerprint'] = packet.fingerprint
47 | elif isinstance(packet, pgpdump.packet.UserIDPacket):
48 | names.append(str(packet.user_email)) # everything is str, not unicode
49 | if 'fingerprint' in values.keys():
50 | for name in names:
51 | keydict[name] = values
52 | keydict[name]['name'] = name
53 | keydict[name]['protocol'] = 'prpl-jabber' # assume XMPP for now
54 | return keydict
55 |
56 | @staticmethod
57 | def write(keys, savedir):
58 | print('Writing GnuPG output files is not yet supported!')
59 |
60 | @staticmethod
61 | def load_data(filename):
62 | with open(filename, 'rb') as fileobj:
63 | data = fileobj.read()
64 | return data
65 |
66 |
67 | if __name__ == '__main__':
68 |
69 | import pprint
70 |
71 | print('GnuPG stores its files in ' + GnuPGProperties.path)
72 |
73 | if len(sys.argv) == 2:
74 | settingsdir = sys.argv[1]
75 | else:
76 | settingsdir = '../tests/gnupg'
77 |
78 | l = GnuPGProperties.parse(settingsdir)
79 | pprint.pprint(l)
80 |
--------------------------------------------------------------------------------
/otrapps/irssi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing irssi's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import os
7 | import sys
8 |
9 | if __name__ == '__main__':
10 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
11 | import otrapps.util
12 | from otrapps.otr_private_key import OtrPrivateKeys
13 | from otrapps.otr_fingerprints import OtrFingerprints
14 |
15 | class IrssiProperties():
16 |
17 | path = os.path.expanduser('~/.irssi/otr')
18 | keyfile = 'otr.key'
19 | fingerprintfile = 'otr.fp'
20 | files = (keyfile, fingerprintfile)
21 |
22 | @staticmethod
23 | def parse(settingsdir=None):
24 | if settingsdir == None:
25 | settingsdir = IrssiProperties.path
26 |
27 | kf = os.path.join(settingsdir, IrssiProperties.keyfile)
28 | if os.path.exists(kf):
29 | keydict = OtrPrivateKeys.parse(kf)
30 | else:
31 | keydict = dict()
32 |
33 | fpf = os.path.join(settingsdir, IrssiProperties.fingerprintfile)
34 | if os.path.exists(fpf):
35 | otrapps.util.merge_keydicts(keydict, OtrFingerprints.parse(fpf))
36 |
37 | return keydict
38 |
39 | @staticmethod
40 | def write(keydict, savedir):
41 | if not os.path.exists(savedir):
42 | raise Exception('"' + savedir + '" does not exist!')
43 |
44 | kf = os.path.join(savedir, IrssiProperties.keyfile)
45 | OtrPrivateKeys.write(keydict, kf)
46 |
47 | accounts = []
48 | # look for all private keys and use them for the accounts list
49 | for name, key in keydict.items():
50 | if 'x' in key:
51 | accounts.append(name)
52 | fpf = os.path.join(savedir, IrssiProperties.fingerprintfile)
53 | OtrFingerprints.write(keydict, fpf, accounts)
54 |
55 |
56 | if __name__ == '__main__':
57 |
58 | import pprint
59 |
60 | print('Irssi stores its files in ' + IrssiProperties.path)
61 |
62 | if len(sys.argv) == 2:
63 | settingsdir = sys.argv[1]
64 | else:
65 | settingsdir = '../tests/irssi'
66 |
67 | keydict = IrssiProperties.parse(settingsdir)
68 | pprint.pprint(keydict)
69 |
70 | IrssiProperties.write(keydict, '/tmp')
71 |
--------------------------------------------------------------------------------
/otrapps/jitsi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing Jitsi's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import os
7 | import platform
8 | import re
9 | import sys
10 | from pyjavaproperties import Properties
11 | from bs4 import BeautifulSoup
12 |
13 | if __name__ == '__main__':
14 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
15 | import otrapps.util
16 |
17 |
18 | # the accounts, private/public keys, and fingerprints are in sip-communicator.properties
19 | # the contacts list is in contactlist.xml
20 |
21 | class JitsiProperties():
22 |
23 | if platform.system() == 'Darwin':
24 | path = os.path.expanduser('~/Library/Application Support/Jitsi')
25 | elif platform.system() == 'Windows':
26 | path = os.path.expanduser('~/Application Data/Jitsi')
27 | else:
28 | path = os.path.expanduser('~/.jitsi')
29 | propertiesfile = 'sip-communicator.properties'
30 | contactsfile = 'contactlist.xml'
31 | files = (propertiesfile, contactsfile)
32 |
33 | @staticmethod
34 | def _parse_account_uid(uidstring):
35 | username, domain, server = uidstring.split(':')[1].split('@')
36 | return username + '@' + domain
37 |
38 | @staticmethod
39 | def _convert_protocol_name(protocol):
40 | if protocol == 'Jabber':
41 | return 'prpl-jabber'
42 | elif protocol == 'Google Talk':
43 | # this should also mark it as the gtalk variant
44 | return 'prpl-jabber'
45 | else:
46 | return 'IMPLEMENTME'
47 |
48 | @staticmethod
49 | def _parse_account_from_propkey(settingsdir, propkey):
50 | '''give a Java Properties key, parse out a real account UID and
51 | protocol, based on what's listed in the contacts file'''
52 | # jitsi stores the account name in the properties key, so it strips the @ out
53 | m = re.match('net\.java\.sip\.communicator\.plugin\.otr\.(.*)_publicKey.*', propkey)
54 | name_from_prop = '.'.join(m.group(1).split('_'))
55 | # so let's find where the @ was originally placed:
56 | xml = ''
57 | for line in open(os.path.join(settingsdir, JitsiProperties.contactsfile), 'r').readlines():
58 | xml += line
59 | name = None
60 | protocol = None
61 | for e in BeautifulSoup(xml).find_all('contact'):
62 | if re.match(name_from_prop, e['address']):
63 | name = e['address']
64 | protocol = JitsiProperties._convert_protocol_name(e['account-id'].split(':')[0])
65 | break
66 | return str(name), protocol
67 |
68 |
69 | @staticmethod
70 | def parse(settingsdir=None):
71 | if settingsdir == None:
72 | settingsdir = JitsiProperties.path
73 | p = Properties()
74 | p.load(open(os.path.join(settingsdir, JitsiProperties.propertiesfile)))
75 | keydict = dict()
76 | for item in p.items():
77 | propkey = item[0]
78 | name = ''
79 | if re.match('net\.java\.sip\.communicator\.impl\.protocol\.jabber\.acc[0-9]+\.ACCOUNT_UID', propkey):
80 | name = JitsiProperties._parse_account_uid(item[1])
81 | if name in keydict:
82 | key = keydict[name]
83 | else:
84 | key = dict()
85 | key['name'] = name
86 | key['protocol'] = 'prpl-jabber'
87 | keydict[name] = key
88 |
89 | propkey_base = ('net.java.sip.communicator.plugin.otr.'
90 | + re.sub('[^a-zA-Z0-9_]', '_', item[1]))
91 | private_key = p.getProperty(propkey_base + '_privateKey').strip()
92 | public_key = p.getProperty(propkey_base + '_publicKey').strip()
93 | numdict = otrapps.util.ParsePkcs8(private_key)
94 | key['x'] = numdict['x']
95 | numdict = otrapps.util.ParseX509(public_key)
96 | for num in ('y', 'g', 'p', 'q'):
97 | key[num] = numdict[num]
98 | key['fingerprint'] = otrapps.util.fingerprint((key['y'], key['g'], key['p'], key['q']))
99 | verifiedkey = ('net.java.sip.communicator.plugin.otr.'
100 | + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
101 | + '_publicKey_verified')
102 | if p.getProperty(verifiedkey).strip() == 'true':
103 | key['verification'] = 'verified'
104 | elif (re.match('net\.java\.sip\.communicator\.plugin\.otr\..*_publicKey_verified', propkey)):
105 | name, protocol = JitsiProperties._parse_account_from_propkey(settingsdir, propkey)
106 | if name != None:
107 | if name in keydict:
108 | key = keydict[name]
109 | else:
110 | key = dict()
111 | key['name'] = name
112 | keydict[name] = key
113 | if protocol and 'protocol' not in keydict[name]:
114 | key['protocol'] = protocol
115 | key['verification'] = 'verified'
116 | # if the protocol name is included in the property name, its a local account with private key
117 | elif (re.match('net\.java\.sip\.communicator\.plugin\.otr\..*_publicKey', propkey) and not
118 | re.match('net\.java\.sip\.communicator\.plugin\.otr\.(Jabber_|Google_Talk_)', propkey)):
119 | name, ignored = JitsiProperties._parse_account_from_propkey(settingsdir, propkey)
120 | if name in keydict:
121 | key = keydict[name]
122 | else:
123 | key = dict()
124 | key['name'] = name
125 | key['protocol'] = 'prpl-jabber'
126 | keydict[name] = key
127 | numdict = otrapps.util.ParseX509(item[1])
128 | for num in ('y', 'g', 'p', 'q'):
129 | key[num] = numdict[num]
130 | key['fingerprint'] = otrapps.util.fingerprint((key['y'], key['g'], key['p'], key['q']))
131 | return keydict
132 |
133 | @staticmethod
134 | def write(keydict, savedir):
135 | if not os.path.exists(savedir):
136 | raise Exception('"' + savedir + '" does not exist!')
137 |
138 | loadfile = os.path.join(savedir, JitsiProperties.propertiesfile)
139 | savefile = loadfile
140 | if not os.path.exists(loadfile) and os.path.exists(JitsiProperties.path):
141 | print('Jitsi NOTICE: "' + loadfile + '" does not exist! Reading from:')
142 | loadfile = os.path.join(JitsiProperties.path, JitsiProperties.propertiesfile)
143 | print('\t"' + loadfile + '"')
144 |
145 | propkey_base = 'net.java.sip.communicator.plugin.otr.'
146 | p = Properties()
147 | p.load(open(loadfile))
148 | for name, key in keydict.items():
149 | if 'verification' in key and key['verification'] != '':
150 | verifiedkey = (propkey_base + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
151 | + '_publicKey_verified')
152 | p[verifiedkey] = 'true'
153 | if 'y' in key:
154 | pubkey = (propkey_base + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
155 | + '_publicKey')
156 | p.setProperty(pubkey, otrapps.util.ExportDsaX509(key))
157 | if 'x' in key:
158 | protocol_id = 'UNKNOWN_'
159 | domain_id = 'unknown'
160 | servername = None
161 | if '@' in key['name']:
162 | domainname = key['name'].split('@')[1]
163 | domain_id = re.sub('[^a-zA-Z0-9_]', '_', domainname)
164 | if domainname == 'chat.facebook.com':
165 | protocol_id = 'Facebook_'
166 | elif domainname == 'gmail.com' \
167 | or domainname == 'google.com' \
168 | or domainname == 'googlemail.com':
169 | protocol_id = 'Google_Talk_'
170 | servername = 'talk_google_com'
171 | else:
172 | protocol_id = 'Jabber_'
173 | else:
174 | if key['protocol'] == 'prpl-icq':
175 | protocol_id = 'ICQ_'
176 | domain_id = 'icq_com'
177 | elif key['protocol'] == 'prpl-yahoo':
178 | protocol_id = 'Yahoo__'
179 | domain_id = 'yahoo_com'
180 | # Writing
181 | pubkey = (propkey_base + protocol_id + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
182 | + '_' + domain_id + '_publicKey')
183 | p.setProperty(pubkey, otrapps.util.ExportDsaX509(key))
184 | privkey = (propkey_base + protocol_id + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
185 | + '_' + domain_id + '_privateKey')
186 | p.setProperty(privkey, otrapps.util.ExportDsaPkcs8(key))
187 |
188 | if servername:
189 | pubkey = (propkey_base + protocol_id + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
190 | + '_' + servername + '_publicKey')
191 | p.setProperty(pubkey, otrapps.util.ExportDsaX509(key))
192 | privkey = (propkey_base + protocol_id + re.sub('[^a-zA-Z0-9_]', '_', key['name'])
193 | + '_' + servername + '_privateKey')
194 | p.setProperty(privkey, otrapps.util.ExportDsaPkcs8(key))
195 |
196 | p.store(open(savefile, 'w'))
197 |
198 |
199 |
200 | #------------------------------------------------------------------------------#
201 | # for testing from the command line:
202 | def main(argv):
203 | import pprint
204 |
205 | print('Jitsi stores its files in ' + JitsiProperties.path)
206 |
207 | if len(sys.argv) == 2:
208 | settingsdir = sys.argv[1]
209 | else:
210 | settingsdir = '../tests/jitsi'
211 |
212 | p = JitsiProperties.parse(settingsdir)
213 | print('----------------------------------------')
214 | pprint.pprint(p)
215 | print('----------------------------------------')
216 |
217 | if __name__ == "__main__":
218 | main(sys.argv[1:])
219 |
--------------------------------------------------------------------------------
/otrapps/kopete.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing Kopete's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import os
7 | import sys
8 |
9 | if __name__ == '__main__':
10 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
11 | import otrapps.util
12 | from otrapps.otr_private_key import OtrPrivateKeys
13 | from otrapps.otr_fingerprints import OtrFingerprints
14 |
15 | class KopeteProperties():
16 |
17 | path = os.path.expanduser('~/.kde/share/apps/kopete_otr')
18 | keyfile = 'privkeys'
19 | fingerprintfile = 'fingerprints'
20 | files = (keyfile, fingerprintfile)
21 |
22 | @staticmethod
23 | def _convert_protocol_name(protocol):
24 | if protocol == 'Jabber':
25 | return 'prpl-jabber'
26 | elif protocol == 'prpl-jabber':
27 | return 'Jabber'
28 | elif protocol == 'Google Talk':
29 | # this should also mark it as the gtalk variant
30 | return 'prpl-jabber'
31 | else:
32 | print('IMPLEMENTME')
33 | print(protocol)
34 | return 'IMPLEMENTME'
35 |
36 | @staticmethod
37 | def parse(settingsdir=None):
38 | if settingsdir == None:
39 | settingsdir = KopeteProperties.path
40 |
41 | kf = os.path.join(settingsdir, KopeteProperties.keyfile)
42 | if os.path.exists(kf):
43 | keydict = OtrPrivateKeys.parse(kf)
44 | for key in keydict:
45 | for value in keydict[key]:
46 | if value == 'protocol':
47 | keydict[key][value] = KopeteProperties._convert_protocol_name(keydict[key][value])
48 | else:
49 | keydict = dict()
50 |
51 | fpf = os.path.join(settingsdir, KopeteProperties.fingerprintfile)
52 | if os.path.exists(fpf):
53 | tmpdict = OtrFingerprints.parse(fpf)
54 | for key in tmpdict:
55 | for value in tmpdict[key]:
56 | if value == 'protocol':
57 | tmpdict[key][value] = KopeteProperties._convert_protocol_name(tmpdict[key][value])
58 |
59 | otrapps.util.merge_keydicts(keydict, tmpdict)
60 |
61 | return keydict
62 |
63 | @staticmethod
64 | def write(keydict, savedir):
65 | if not os.path.exists(savedir):
66 | raise Exception('"' + savedir + '" does not exist!')
67 |
68 | for key in keydict:
69 | for value in keydict[key]:
70 | if value == 'protocol':
71 | keydict[key][value] = KopeteProperties._convert_protocol_name(keydict[key][value])
72 |
73 | kf = os.path.join(savedir, KopeteProperties.keyfile)
74 | OtrPrivateKeys.write(keydict, kf)
75 |
76 | accounts = []
77 | # look for all private keys and use them for the accounts list
78 | for name, key in keydict.items():
79 | if 'x' in key:
80 | accounts.append(name)
81 | fpf = os.path.join(savedir, KopeteProperties.fingerprintfile)
82 | OtrFingerprints.write(keydict, fpf, accounts)
83 |
84 |
85 |
86 | if __name__ == '__main__':
87 |
88 | import pprint
89 |
90 | print('Kopete stores its files in ' + KopeteProperties.path)
91 |
92 | if len(sys.argv) == 2:
93 | settingsdir = sys.argv[1]
94 | else:
95 | settingsdir = '../tests/kopete'
96 |
97 | keydict = KopeteProperties.parse(settingsdir)
98 | pprint.pprint(keydict)
99 |
100 | KopeteProperties.write(keydict, '/tmp')
101 |
--------------------------------------------------------------------------------
/otrapps/otr_fingerprints.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing libotr's public key data'''
4 |
5 | from __future__ import print_function
6 | import csv
7 |
8 | class OtrFingerprints():
9 |
10 | @staticmethod
11 | def parse(filename):
12 | '''parse the otr.fingerprints file and return a list of keydicts'''
13 | tsv = csv.reader(open(filename, 'r'), delimiter='\t')
14 | keydict = dict()
15 | for row in tsv:
16 | key = dict()
17 | name = row[0].strip()
18 | key['name'] = name
19 | key['protocol'] = row[2].strip()
20 | key['fingerprint'] = row[3].strip()
21 | key['verification'] = row[4].strip()
22 | keydict[name] = key
23 | return keydict
24 |
25 | @staticmethod
26 | def _includexmppresource(accounts, resources):
27 | '''pidgin requires the XMPP Resource in the name of the associated account'''
28 | returnlist = []
29 | for account in accounts:
30 | if account in resources.keys():
31 | returnlist.append(account + '/' + resources[account])
32 | else:
33 | returnlist.append(account + '/' + 'ReplaceMeWithActualXMPPResource')
34 | return returnlist
35 |
36 | @staticmethod
37 | def write(keydict, filename, accounts, resources=None):
38 | if resources:
39 | accounts = OtrFingerprints._includexmppresource(accounts, resources)
40 | # we have to use this list 'accounts' rather than the private
41 | # keys in the keydict in order to support apps like Adium that
42 | # don't use the actual account ID as the index in the files.
43 | tsv = csv.writer(open(filename, 'w'), delimiter='\t')
44 | for name, key in keydict.items():
45 | if 'fingerprint' in key:
46 | for account in accounts:
47 | row = [name, account, key['protocol'], key['fingerprint']]
48 | if 'verification' in key and key['verification'] != None:
49 | row.append(key['verification'])
50 | tsv.writerow(row)
51 |
52 |
53 | if __name__ == '__main__':
54 |
55 | import sys
56 | import pprint
57 | keydict = OtrFingerprints.parse(sys.argv[1])
58 | pprint.pprint(keydict)
59 | accounts = [ 'gptest@jabber.org', 'gptest@limun.org', 'hans@eds.org']
60 | OtrFingerprints.write(keydict, 'otr.fingerprints', accounts)
61 |
--------------------------------------------------------------------------------
/otrapps/otr_private_key.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing libotr's secret key data'''
4 |
5 | from __future__ import print_function
6 | from pyparsing import Forward, Group, OneOrMore, Optional, ParseFatalException, \
7 | Suppress, Word, ZeroOrMore, alphanums, dblQuotedString, hexnums, nums, \
8 | printables, removeQuotes
9 | from base64 import b64decode
10 | import sys
11 |
12 | if __name__ == '__main__':
13 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
14 | import otrapps.util
15 |
16 | class OtrPrivateKeys():
17 |
18 | @staticmethod
19 | def verifyLen(t):
20 | t = t[0]
21 | if t.len is not None:
22 | t1len = len(t[1])
23 | if t1len != t.len:
24 | raise ParseFatalException, \
25 | "invalid data of length %d, expected %s" % (t1len, t.len)
26 | return t[1]
27 |
28 | @staticmethod
29 | def parse_sexp(data):
30 | '''parse sexp/S-expression format and return a python list'''
31 | # define punctuation literals
32 | LPAR, RPAR, LBRK, RBRK, LBRC, RBRC, VBAR = map(Suppress, "()[]{}|")
33 |
34 | decimal = Word("123456789", nums).setParseAction(lambda t: int(t[0]))
35 | bytes = Word(printables)
36 | raw = Group(decimal.setResultsName("len") + Suppress(":") + bytes).setParseAction(OtrPrivateKeys.verifyLen)
37 | token = Word(alphanums + "-./_:*+=")
38 | base64_ = Group(Optional(decimal, default=None).setResultsName("len") + VBAR
39 | + OneOrMore(Word( alphanums +"+/=" )).setParseAction(lambda t: b64decode("".join(t)))
40 | + VBAR).setParseAction(OtrPrivateKeys.verifyLen)
41 |
42 | hexadecimal = ("#" + OneOrMore(Word(hexnums)) + "#")\
43 | .setParseAction(lambda t: int("".join(t[1:-1]),16))
44 | qString = Group(Optional(decimal, default=None).setResultsName("len") +
45 | dblQuotedString.setParseAction(removeQuotes)).setParseAction(OtrPrivateKeys.verifyLen)
46 | simpleString = raw | token | base64_ | hexadecimal | qString
47 |
48 | display = LBRK + simpleString + RBRK
49 | string_ = Optional(display) + simpleString
50 |
51 | sexp = Forward()
52 | sexpList = Group(LPAR + ZeroOrMore(sexp) + RPAR)
53 | sexp << ( string_ | sexpList )
54 |
55 | try:
56 | sexpr = sexp.parseString(data)
57 | return sexpr.asList()[0][1:]
58 | except ParseFatalException, pfe:
59 | print("Error:", pfe.msg)
60 | print(pfe.loc)
61 | print(pfe.markInputline())
62 |
63 | @staticmethod
64 | def parse(filename):
65 | '''parse the otr.private_key S-Expression and return an OTR dict'''
66 |
67 | f = open(filename, 'r')
68 | data = ""
69 | for line in f.readlines():
70 | data += line
71 | f.close()
72 |
73 | sexplist = OtrPrivateKeys.parse_sexp(data)
74 | keydict = dict()
75 | for sexpkey in sexplist:
76 | if sexpkey[0] == "account":
77 | key = dict()
78 | name = ''
79 | for element in sexpkey:
80 | # 'name' must be the first element in the sexp or BOOM!
81 | if element[0] == "name":
82 | if element[1].find('/') > -1:
83 | name, resource = element[1].split('/')
84 | else:
85 | name = element[1].strip()
86 | resource = ''
87 | key = dict()
88 | key['name'] = name.strip()
89 | key['resource'] = resource.strip()
90 | if element[0] == "protocol":
91 | key['protocol'] = element[1]
92 | elif element[0] == "private-key":
93 | if element[1][0] == 'dsa':
94 | key['type'] = 'dsa'
95 | for num in element[1][1:6]:
96 | key[num[0]] = num[1]
97 | keytuple = (key['y'], key['g'], key['p'], key['q'])
98 | key['fingerprint'] = otrapps.util.fingerprint(keytuple)
99 | keydict[name] = key
100 | return keydict
101 |
102 | @staticmethod
103 | def _getaccountname(key, resources):
104 | if resources:
105 | # pidgin requires the XMPP Resource in the account name for otr.private_keys
106 | if key['protocol'] == 'prpl-jabber' and 'x' in key.keys():
107 | name = key['name']
108 | if name in resources.keys():
109 | return key['name'] + '/' + resources[name]
110 | else:
111 | return key['name'] + '/' + 'ReplaceMeWithActualXMPPResource'
112 | return key['name']
113 |
114 | @staticmethod
115 | def write(keydict, filename, resources=None):
116 | privkeys = '(privkeys\n'
117 | for name, key in keydict.items():
118 | if 'x' in key:
119 | dsa = ' (p #' + ('%0258X' % key['p']) + '#)\n'
120 | dsa += ' (q #' + ('%042X' % key['q']) + '#)\n'
121 | dsa += ' (g #' + ('%0258X' % key['g']) + '#)\n'
122 | dsa += ' (y #' + ('%0256X' % key['y']) + '#)\n'
123 | dsa += ' (x #' + ('%042X' % key['x']) + '#)\n'
124 | account = OtrPrivateKeys._getaccountname(key, resources)
125 | contents = ('(name "' + account + '")\n' +
126 | '(protocol ' + key['protocol'] + ')\n' +
127 | '(private-key \n (dsa \n' + dsa + ' )\n )\n')
128 | privkeys += ' (account\n' + contents + ' )\n'
129 | privkeys += ')\n'
130 | f = open(filename, 'w')
131 | f.write(privkeys)
132 | f.close()
133 |
134 | if __name__ == "__main__":
135 | import sys
136 | import pprint
137 |
138 | pp = pprint.PrettyPrinter(indent=4)
139 | pp.pprint(OtrPrivateKeys.parse(sys.argv[1]))
140 |
--------------------------------------------------------------------------------
/otrapps/pidgin.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing Pidgin's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import os
7 | import sys
8 | from bs4 import BeautifulSoup
9 |
10 | if __name__ == '__main__':
11 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
12 | import otrapps.util
13 | from otrapps.otr_private_key import OtrPrivateKeys
14 | from otrapps.otr_fingerprints import OtrFingerprints
15 |
16 | class PidginProperties():
17 |
18 | if sys.platform == 'win32':
19 | path = os.path.join(os.environ.get('APPDATA'), '.purple')
20 | else:
21 | path = os.path.expanduser('~/.purple')
22 | accountsfile = 'accounts.xml'
23 | keyfile = 'otr.private_key'
24 | fingerprintfile = 'otr.fingerprints'
25 |
26 | @staticmethod
27 | def _get_resources(settingsdir):
28 | '''parse out the XMPP Resource from every Pidgin account'''
29 | resources = dict()
30 | accountsfile = os.path.join(settingsdir, PidginProperties.accountsfile)
31 | if not os.path.exists(accountsfile):
32 | print('Pidgin WARNING: No usable accounts.xml file found, add XMPP Resource to otr.private_key by hand!')
33 | return resources
34 | xml = ''
35 | for line in open(accountsfile, 'r').readlines():
36 | xml += line
37 | for e in BeautifulSoup(xml)(text='prpl-jabber'):
38 | pidginname = e.parent.parent.find('name').contents[0].split('/')
39 | name = pidginname[0]
40 | if len(pidginname) == 2:
41 | resources[name] = pidginname[1]
42 | else:
43 | # Pidgin requires an XMPP Resource, even if its blank
44 | resources[name] = ''
45 | return resources
46 |
47 | @staticmethod
48 | def parse(settingsdir=None):
49 | if settingsdir == None:
50 | settingsdir = PidginProperties.path
51 |
52 | kf = os.path.join(settingsdir, PidginProperties.keyfile)
53 | if os.path.exists(kf):
54 | keydict = OtrPrivateKeys.parse(kf)
55 | else:
56 | keydict = dict()
57 |
58 | fpf = os.path.join(settingsdir, PidginProperties.fingerprintfile)
59 | if os.path.exists(fpf):
60 | otrapps.util.merge_keydicts(keydict, OtrFingerprints.parse(fpf))
61 |
62 | resources = PidginProperties._get_resources(settingsdir)
63 | for name, key in keydict.items():
64 | if key['protocol'] == 'prpl-jabber' \
65 | and 'x' in key.keys() \
66 | and name in resources.keys():
67 | key['resource'] = resources[name]
68 |
69 | return keydict
70 |
71 | @staticmethod
72 | def write(keydict, savedir):
73 | if not os.path.exists(savedir):
74 | raise Exception('"' + savedir + '" does not exist!')
75 |
76 | kf = os.path.join(savedir, PidginProperties.keyfile)
77 | # Pidgin requires the XMPP resource in the account name field of the
78 | # OTR private keys file, so fetch it from the existing account info
79 | if os.path.exists(os.path.join(savedir, PidginProperties.accountsfile)):
80 | accountsdir = savedir
81 | elif os.path.exists(os.path.join(PidginProperties.path,
82 | PidginProperties.accountsfile)):
83 | accountsdir = PidginProperties.path
84 | else:
85 | raise Exception('Cannot find "' + PidginProperties.accountsfile
86 | + '" in "' + savedir + '"')
87 | resources = PidginProperties._get_resources(accountsdir)
88 | OtrPrivateKeys.write(keydict, kf, resources=resources)
89 |
90 | accounts = []
91 | # look for all private keys and use them for the accounts list
92 | for name, key in keydict.items():
93 | if 'x' in key:
94 | accounts.append(name)
95 | fpf = os.path.join(savedir, PidginProperties.fingerprintfile)
96 | OtrFingerprints.write(keydict, fpf, accounts, resources=resources)
97 |
98 |
99 | if __name__ == '__main__':
100 |
101 | import pprint
102 | import shutil
103 |
104 | print('Pidgin stores its files in ' + PidginProperties.path)
105 |
106 | if len(sys.argv) == 2:
107 | settingsdir = sys.argv[1]
108 | else:
109 | settingsdir = '../tests/pidgin'
110 |
111 | keydict = PidginProperties.parse(settingsdir)
112 | pprint.pprint(keydict)
113 |
114 | if not os.path.exists(os.path.join('/tmp', PidginProperties.accountsfile)):
115 | shutil.copy(os.path.join(settingsdir, PidginProperties.accountsfile),
116 | '/tmp')
117 | PidginProperties.write(keydict, '/tmp')
118 |
--------------------------------------------------------------------------------
/otrapps/util.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2008 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | """
17 | Utility functions for keyczar package modified to use standard base64
18 | format that is commonly used for OTR keys.
19 |
20 | @author: arkajit.dey@gmail.com (Arkajit Dey)
21 | @author: hans@eds.org (Hans-Christoph Steiner)
22 | """
23 |
24 | from __future__ import print_function
25 | import base64
26 | import math
27 | import os
28 | import psutil
29 | import re
30 | import signal
31 | import sys
32 | import tempfile
33 | try:
34 | # Import hashlib if Python >= 2.5
35 | from hashlib import sha1
36 | assert sha1 # silence pyflakes
37 | except ImportError:
38 | from sha import sha as sha1
39 |
40 | from pyasn1.codec.der import decoder
41 | from pyasn1.codec.der import encoder
42 | from pyasn1.type import univ
43 |
44 | from potr.utils import bytes_to_long
45 | from potr.compatcrypto import DSAKey
46 |
47 | if __name__ == '__main__':
48 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
49 | import otrapps.errors as errors
50 |
51 |
52 | # gracefully handle it when pymtp doesn't exist
53 | class MTPDummy():
54 | def detect_devices(self):
55 | return []
56 | try:
57 | import pymtp
58 | mtp = pymtp.MTP()
59 | except:
60 | mtp = MTPDummy()
61 | # GNOME GVFS mount point for MTP devices
62 |
63 | if sys.platform != 'win32':
64 | # this crashes windows in the ntpath sys lib
65 | mtp.gvfs_mountpoint = os.path.join(os.getenv('HOME'), '.gvfs', 'mtp')
66 |
67 |
68 | HLEN = sha1().digest_size # length of the hash output
69 |
70 | #RSAPrivateKey ::= SEQUENCE {
71 | # version Version,
72 | # modulus INTEGER, -- n
73 | # publicExponent INTEGER, -- e
74 | # privateExponent INTEGER, -- d
75 | # prime1 INTEGER, -- p
76 | # prime2 INTEGER, -- q
77 | # exponent1 INTEGER, -- d mod (p-1)
78 | # exponent2 INTEGER, -- d mod (q-1)
79 | # coefficient INTEGER -- (inverse of q) mod p }
80 | #
81 | #Version ::= INTEGER
82 | RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.1')
83 | RSA_PARAMS = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'invq']
84 | DSA_OID = univ.ObjectIdentifier('1.2.840.10040.4.1')
85 | DSA_PARAMS = ['p', 'q', 'g'] # only algorithm params, not public/private keys
86 | SHA1RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.5')
87 | SHA1_OID = univ.ObjectIdentifier('1.3.14.3.2.26')
88 |
89 | def ASN1Sequence(*vals):
90 | seq = univ.Sequence()
91 | for i in range(len(vals)):
92 | seq.setComponentByPosition(i, vals[i])
93 | return seq
94 |
95 | def ParseASN1Sequence(seq):
96 | return [seq.getComponentByPosition(i) for i in range(len(seq))]
97 |
98 | #PrivateKeyInfo ::= SEQUENCE {
99 | # version Version,
100 | #
101 | # privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
102 | # privateKey PrivateKey,
103 | # attributes [0] IMPLICIT Attributes OPTIONAL }
104 | #
105 | #Version ::= INTEGER
106 | #
107 | #PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
108 | #
109 | #PrivateKey ::= OCTET STRING
110 | #
111 | #Attributes ::= SET OF Attribute
112 | def ParsePkcs8(pkcs8):
113 | seq = ParseASN1Sequence(decoder.decode(Decode(pkcs8))[0])
114 | if len(seq) != 3: # need three fields in PrivateKeyInfo
115 | raise errors.KeyczarError("Illegal PKCS8 String.")
116 | version = int(seq[0])
117 | if version != 0:
118 | raise errors.KeyczarError("Unrecognized PKCS8 Version")
119 | [oid, alg_params] = ParseASN1Sequence(seq[1])
120 | key = decoder.decode(seq[2])[0]
121 | # Component 2 is an OCTET STRING which is further decoded
122 | params = {}
123 | if oid == RSA_OID:
124 | key = ParseASN1Sequence(key)
125 | version = int(key[0])
126 | if version != 0:
127 | raise errors.KeyczarError("Unrecognized RSA Private Key Version")
128 | for i in range(len(RSA_PARAMS)):
129 | params[RSA_PARAMS[i]] = long(key[i+1])
130 | elif oid == DSA_OID:
131 | alg_params = ParseASN1Sequence(alg_params)
132 | for i in range(len(DSA_PARAMS)):
133 | params[DSA_PARAMS[i]] = long(alg_params[i])
134 | params['x'] = long(key)
135 | else:
136 | raise errors.KeyczarError("Unrecognized AlgorithmIdentifier: not RSA/DSA")
137 | return params
138 |
139 | def ExportRsaPkcs8(params):
140 | oid = ASN1Sequence(RSA_OID, univ.Null())
141 | key = univ.Sequence().setComponentByPosition(0, univ.Integer(0)) # version
142 | for i in range(len(RSA_PARAMS)):
143 | key.setComponentByPosition(i+1, univ.Integer(params[RSA_PARAMS[i]]))
144 | octkey = encoder.encode(key)
145 | seq = ASN1Sequence(univ.Integer(0), oid, univ.OctetString(octkey))
146 | return Encode(encoder.encode(seq))
147 |
148 | def ExportDsaPkcs8(params):
149 | alg_params = univ.Sequence()
150 | for i in range(len(DSA_PARAMS)):
151 | alg_params.setComponentByPosition(i, univ.Integer(params[DSA_PARAMS[i]]))
152 | oid = ASN1Sequence(DSA_OID, alg_params)
153 | octkey = encoder.encode(univ.Integer(params['x']))
154 | seq = ASN1Sequence(univ.Integer(0), oid, univ.OctetString(octkey))
155 | return Encode(encoder.encode(seq))
156 |
157 | #NOTE: not full X.509 certificate, just public key info
158 | #SubjectPublicKeyInfo ::= SEQUENCE {
159 | # algorithm AlgorithmIdentifier,
160 | # subjectPublicKey BIT STRING }
161 | def ParseX509(x509):
162 | seq = ParseASN1Sequence(decoder.decode(Decode(x509))[0])
163 | if len(seq) != 2: # need two fields in SubjectPublicKeyInfo
164 | raise errors.KeyczarError("Illegal X.509 String.")
165 | [oid, alg_params] = ParseASN1Sequence(seq[0])
166 | binstring = seq[1].prettyPrint()[1:-2]
167 | pubkey = decoder.decode(univ.OctetString(BinToBytes(binstring.replace("'", ""))))[0]
168 | # Component 1 should be a BIT STRING, get raw bits by discarding extra chars,
169 | # then convert to OCTET STRING which can be ASN.1 decoded
170 | params = {}
171 | if oid == RSA_OID:
172 | [params['n'], params['e']] = [long(x) for x in ParseASN1Sequence(pubkey)]
173 | elif oid == DSA_OID:
174 | vals = [long(x) for x in ParseASN1Sequence(alg_params)]
175 | for i in range(len(DSA_PARAMS)):
176 | params[DSA_PARAMS[i]] = vals[i]
177 | params['y'] = long(pubkey)
178 | else:
179 | raise errors.KeyczarError("Unrecognized AlgorithmIdentifier: not RSA/DSA")
180 | return params
181 |
182 | def ExportRsaX509(params):
183 | oid = ASN1Sequence(RSA_OID, univ.Null())
184 | key = ASN1Sequence(univ.Integer(params['n']), univ.Integer(params['e']))
185 | binkey = BytesToBin(encoder.encode(key))
186 | pubkey = univ.BitString("'%s'B" % binkey) # needs to be a BIT STRING
187 | seq = ASN1Sequence(oid, pubkey)
188 | return Encode(encoder.encode(seq))
189 |
190 | def ExportDsaX509(params):
191 | alg_params = ASN1Sequence(univ.Integer(params['p']),
192 | univ.Integer(params['q']),
193 | univ.Integer(params['g']))
194 | oid = ASN1Sequence(DSA_OID, alg_params)
195 | binkey = BytesToBin(encoder.encode(univ.Integer(params['y'])))
196 | pubkey = univ.BitString("'%s'B" % binkey) # needs to be a BIT STRING
197 | seq = ASN1Sequence(oid, pubkey)
198 | return Encode(encoder.encode(seq))
199 |
200 | def MakeDsaSig(r, s):
201 | """
202 | Given the raw parameters of a DSA signature, return a Base64 signature.
203 |
204 | @param r: parameter r of DSA signature
205 | @type r: long int
206 |
207 | @param s: parameter s of DSA signature
208 | @type s: long int
209 |
210 | @return: raw byte string formatted as an ASN.1 sequence of r and s
211 | @rtype: string
212 | """
213 | seq = ASN1Sequence(univ.Integer(r), univ.Integer(s))
214 | return encoder.encode(seq)
215 |
216 | def ParseDsaSig(sig):
217 | """
218 | Given a raw byte string, return tuple of DSA signature parameters.
219 |
220 | @param sig: byte string of ASN.1 representation
221 | @type sig: string
222 |
223 | @return: parameters r, s as a tuple
224 | @rtype: tuple
225 |
226 | @raise KeyczarErrror: if the DSA signature format is invalid
227 | """
228 | seq = decoder.decode(sig)[0]
229 | if len(seq) != 2:
230 | raise errors.KeyczarError("Illegal DSA signature.")
231 | r = long(seq.getComponentByPosition(0))
232 | s = long(seq.getComponentByPosition(1))
233 | return (r, s)
234 |
235 | def MakeEmsaMessage(msg, modulus_size):
236 | """Algorithm EMSA_PKCS1-v1_5 from PKCS 1 version 2"""
237 | magic_sha1_header = [0x30, 0x21, 0x30, 0x9, 0x6, 0x5, 0x2b, 0xe, 0x3, 0x2,
238 | 0x1a, 0x5, 0x0, 0x4, 0x14]
239 | encoded = "".join([chr(c) for c in magic_sha1_header]) + Hash(msg)
240 | pad_string = chr(0xFF) * (modulus_size / 8 - len(encoded) - 3)
241 | return chr(1) + pad_string + chr(0) + encoded
242 |
243 | def BinToBytes(bits):
244 | """Convert bit string to byte string."""
245 | bits = _PadByte(bits)
246 | octets = [bits[8*i:8*(i+1)] for i in range(len(bits)/8)]
247 | bytes = [chr(int(x, 2)) for x in octets]
248 | return "".join(bytes)
249 |
250 | def BytesToBin(bytes):
251 | """Convert byte string to bit string."""
252 | return "".join([_PadByte(IntToBin(ord(byte))) for byte in bytes])
253 |
254 | def _PadByte(bits):
255 | """Pad a string of bits with zeros to make its length a multiple of 8."""
256 | r = len(bits) % 8
257 | return ((8-r) % 8)*'0' + bits
258 |
259 | def IntToBin(n):
260 | if n == 0 or n == 1:
261 | return str(n)
262 | elif n % 2 == 0:
263 | return IntToBin(n/2) + "0"
264 | else:
265 | return IntToBin(n/2) + "1"
266 |
267 | def BigIntToBytes(n):
268 | """Return a big-endian byte string representation of an arbitrary length n."""
269 | chars = []
270 | while (n > 0):
271 | chars.append(chr(n % 256))
272 | n = n >> 8
273 | chars.reverse()
274 | return "".join(chars)
275 |
276 | def IntToBytes(n):
277 | """Return byte string of 4 big-endian ordered bytes representing n."""
278 | bytes = [m % 256 for m in [n >> 24, n >> 16, n >> 8, n]]
279 | return "".join([chr(b) for b in bytes]) # byte array to byte string
280 |
281 | def BytesToLong(bytes):
282 | l = len(bytes)
283 | return long(sum([ord(bytes[i]) * 256**(l - 1 - i) for i in range(l)]))
284 |
285 | def Xor(a, b):
286 | """Return a ^ b as a byte string where a and b are byte strings."""
287 | # pad shorter byte string with zeros to make length equal
288 | m = max(len(a), len(b))
289 | if m > len(a):
290 | a = PadBytes(a, m - len(a))
291 | elif m > len(b):
292 | b = PadBytes(b, m - len(b))
293 | x = [ord(c) for c in a]
294 | y = [ord(c) for c in b]
295 | z = [chr(x[i] ^ y[i]) for i in range(m)]
296 | return "".join(z)
297 |
298 | def PadBytes(bytes, n):
299 | """Prepend a byte string with n zero bytes."""
300 | return n * '\x00' + bytes
301 |
302 | def TrimBytes(bytes):
303 | """Trim leading zero bytes."""
304 | trimmed = bytes.lstrip(chr(0))
305 | if trimmed == "": # was a string of all zero bytes
306 | return chr(0)
307 | else:
308 | return trimmed
309 |
310 | def RandBytes(n):
311 | """Return n random bytes."""
312 | # This function requires at least Python 2.4.
313 | return os.urandom(n)
314 |
315 | def Hash(*inputs):
316 | """Return a SHA-1 hash over a variable number of inputs."""
317 | md = sha1()
318 | for i in inputs:
319 | md.update(i)
320 | return md.digest()
321 |
322 | def PrefixHash(*inputs):
323 | """Return a SHA-1 hash over a variable number of inputs."""
324 | md = sha1()
325 | for i in inputs:
326 | md.update(IntToBytes(len(i)))
327 | md.update(i)
328 | return md.digest()
329 |
330 |
331 | def Encode(s):
332 | """
333 | Return Base64 encoding of s.
334 |
335 | @param s: string to encode as Base64
336 | @type s: string
337 |
338 | @return: Base64 representation of s.
339 | @rtype: string
340 | """
341 | return base64.b64encode(str(s))
342 |
343 |
344 | def Decode(s):
345 | """
346 | Return decoded version of given Base64 string. Ignore whitespace.
347 |
348 | @param s: Base64 string to decode
349 | @type s: string
350 |
351 | @return: original string that was encoded as Base64
352 | @rtype: string
353 |
354 | @raise Base64DecodingError: If length of string (ignoring whitespace) is one
355 | more than a multiple of four.
356 | """
357 | s = str(s.replace(" ", "")) # kill whitespace, make string (not unicode)
358 | d = len(s) % 4
359 | if d == 1:
360 | raise errors.Base64DecodingError()
361 | elif d == 2:
362 | s += "=="
363 | elif d == 3:
364 | s += "="
365 | return base64.b64decode(s)
366 |
367 | def WriteFile(data, loc):
368 | """
369 | Writes data to file at given location.
370 |
371 | @param data: contents to be written to file
372 | @type data: string
373 |
374 | @param loc: name of file to write to
375 | @type loc: string
376 |
377 | @raise KeyczarError: if unable to write to file because of IOError
378 | """
379 | try:
380 | f = open(loc, "w")
381 | f.write(data)
382 | f.close()
383 | except IOError:
384 | raise errors.KeyczarError("Unable to write to file %s." % loc)
385 |
386 | def ReadFile(loc):
387 | """
388 | Read data from file at given location.
389 |
390 | @param loc: name of file to read from
391 | @type loc: string
392 |
393 | @return: contents of the file
394 | @rtype: string
395 |
396 | @raise KeyczarError: if unable to read from file because of IOError
397 | """
398 | try:
399 | return open(loc).read()
400 | except IOError:
401 | raise errors.KeyczarError("Unable to read file %s." % loc)
402 |
403 | def MGF(seed, mlen):
404 | """
405 | Mask Generation Function (MGF1) with SHA-1 as hash.
406 |
407 | @param seed: used to generate mask, a byte string
408 | @type seed: string
409 |
410 | @param mlen: desired length of mask
411 | @type mlen: integer
412 |
413 | @return: mask, byte string of length mlen
414 | @rtype: string
415 |
416 | @raise KeyczarError: if mask length too long, > 2^32 * hash_length
417 | """
418 | if mlen > 2**32 * HLEN:
419 | raise errors.KeyczarError("MGF1 mask length too long.")
420 | output = ""
421 | for i in range(int(math.ceil(mlen / float(HLEN)))):
422 | output += Hash(seed, IntToBytes(i))
423 | return output[:mlen]
424 |
425 |
426 | def fingerprint(key):
427 | '''generate the human readable form of the fingerprint as used in OTR'''
428 | return '{0:040x}'.format(bytes_to_long(DSAKey(key).fingerprint()))
429 |
430 |
431 | def check_and_set(key, k, v):
432 | '''
433 | Check if a key is already in the keydict, check its contents against the
434 | supplied value. If the key does not exist, then create a new entry in
435 | keydict. If the key exists and has a different value, throw an exception.
436 | '''
437 | if k in key:
438 | if key[k] != v:
439 | if 'name' in key:
440 | name = key['name']
441 | else:
442 | name = '(unknown)'
443 | # this should be an Exception so that the GUI can catch it to handle it
444 | print('"' + k + '" values for "' + name + '" did not match: \n\t"' + str(key[k])
445 | + '" != "' + str(v) + '"')
446 | else:
447 | key[k] = v
448 |
449 | def merge_keys(key1, key2):
450 | '''merge the second key data into the first, checking for conflicts'''
451 | for k, v in key2.items():
452 | check_and_set(key1, k, v)
453 |
454 |
455 | def merge_keydicts(kd1, kd2):
456 | '''
457 | given two keydicts, merge the second one into the first one and report errors
458 | '''
459 | for name, key in kd2.items():
460 | if name in kd1:
461 | merge_keys(kd1[name], key)
462 | else:
463 | kd1[name] = key
464 |
465 |
466 | def _get_pids():
467 | '''python-psutil's API changed in v3.0'''
468 | try:
469 | return psutil.pids() # v3.0+
470 | except AttributeError:
471 | return psutil.get_pid_list() # <= 2.2.1
472 |
473 |
474 | def which_apps_are_running(apps):
475 | '''
476 | Check the process list to see if any of the specified apps are running.
477 | It returns a tuple of running apps.
478 | '''
479 | running = []
480 | for pid in _get_pids():
481 | try:
482 | p = psutil.Process(pid)
483 | except Exception as e:
484 | print(e)
485 | continue
486 | for app in apps:
487 | if app == p.name:
488 | running.append(app)
489 | print('found: ' + p.name)
490 | else:
491 | r = re.compile('.*' + app + '.*', re.IGNORECASE)
492 | try:
493 | for arg in p.cmdline:
494 | m = r.match(arg)
495 | if m and (p.name == 'python' or p.name == 'java'):
496 | running.append(app)
497 | break
498 | except:
499 | pass
500 | return tuple(running)
501 |
502 |
503 | def killall(app):
504 | '''
505 | terminates all instances of an app
506 | '''
507 | for pid in _get_pids():
508 | p = psutil.Process(pid)
509 | if app == p.name:
510 | print('killing: ' + p.name)
511 | os.kill(pid, signal.SIGKILL)
512 | else:
513 | r = re.compile('.*' + app + '.*', re.IGNORECASE)
514 | try:
515 | for arg in p.cmdline:
516 | m = r.match(arg)
517 | if m and (p.name == 'python' or p.name == 'java'):
518 | print('killing: ' + p.name)
519 | os.kill(pid, signal.SIGKILL)
520 | break
521 | except:
522 | pass
523 |
524 |
525 | def _fullcopy(src, dst):
526 | '''
527 | A simple full file copy that ignores perms, since MTP doesn't play well
528 | with them. shutil.copy() tries to dup perms...
529 | '''
530 | with open(src) as f:
531 | content = f.readlines()
532 | with open(dst, 'w') as f:
533 | f.writelines(content)
534 |
535 |
536 | def make_conffile_backup(filename):
537 | '''makes a in-place backup of the given config file name'''
538 | realpath = os.path.realpath(filename) # eliminate symlinks
539 | s = os.stat(realpath)
540 | timestamp = s.st_mtime
541 | _fullcopy(realpath, realpath + '.' + str(timestamp))
542 |
543 |
544 | def find_gvfs_destdir():
545 | '''find the MTP subfolder in gvfs to copy the keystore to'''
546 | if os.path.exists(mtp.gvfs_mountpoint):
547 | foundit = False
548 | # this assumes that gvfs is mounting the MTP device
549 | if os.path.isdir(os.path.join(mtp.gvfs_mountpoint, 'Internal storage')):
550 | mtpdir = os.path.join(mtp.gvfs_mountpoint, 'Internal storage')
551 | foundit = True
552 | elif os.path.isdir(os.path.join(mtp.gvfs_mountpoint, 'SD card')):
553 | mtpdir = os.path.join(mtp.gvfs_mountpoint, 'SD card')
554 | foundit = True
555 | else:
556 | # if no standard names, try the first dir we find
557 | files = os.listdir(mtp.gvfs_mountpoint)
558 | if len(files) > 0:
559 | for f in files:
560 | fp = os.path.join(mtp.gvfs_mountpoint, f)
561 | if os.path.isdir(fp):
562 | mtpdir = fp
563 | foundit = True
564 | if foundit:
565 | return mtpdir
566 |
567 |
568 | def can_sync_to_device():
569 | '''checks if an MTP device is mounted, i.e. an Android 4.x device'''
570 | mtp.devicename = ''
571 | if sys.platform == 'win32':
572 | # Right now the win32 'sync' method is to prompt the user to manually
573 | # copy the file over, so we always return true.
574 | # https://dev.guardianproject.info/issues/2126
575 | mtp.devicename = 'Copy the otr_keystore.ofcaes file to your device!'
576 | return True
577 |
578 | gvfs_destdir = find_gvfs_destdir()
579 | if gvfs_destdir and os.path.exists(gvfs_destdir):
580 | # this assumes that gvfs is mounting the MTP device. gvfs is
581 | # part of GNOME, but is probably included in other systems too
582 | mtp.devicename = gvfs_destdir
583 | return True
584 |
585 | # if all else fails, try pymtp. works on GNU/Linux and Mac OS X at least
586 | try:
587 | devices = mtp.detect_devices()
588 | if len(devices) > 0:
589 | e = devices[0].device_entry
590 | mtp.devicename = e.vendor + ' ' + e.product
591 | return True
592 | else:
593 | return False
594 | except Exception as e:
595 | print('except ' + str(e))
596 | return False
597 |
598 |
599 | def get_keystore_savedir():
600 | '''get a temp place to write out the encrypted keystore'''
601 | # first cache the encrypted file store in a local temp dir, then we can
602 | # separately handle copying it via MTP, gvfs, wmdlib, KIO, etc.
603 | return tempfile.mkdtemp(prefix='.keysync-')
604 |
605 |
606 | def sync_file_to_device(filename):
607 | '''sync the keystore file to the device via whatever the relevant method is'''
608 |
609 |
610 | #------------------------------------------------------------------------------#
611 | # for testing from the command line:
612 | def main(argv):
613 | import pprint
614 |
615 | key = dict()
616 | key['name'] = 'key'
617 | key['test'] = 'yes, testing'
618 | try:
619 | check_and_set(key, 'test', 'no, this should break')
620 | except Exception as e:
621 | print('Exception: ', end=' ')
622 | print(e)
623 | print(key['test'])
624 | check_and_set(key, 'test', 'yes, testing')
625 | print(key['test'])
626 | check_and_set(key, 'new', 'this is a new value')
627 |
628 | key2 = dict()
629 | key2['name'] = 'key'
630 | key2['yat'] = 'yet another test'
631 | merge_keys(key, key2)
632 | print('key: ', end=' ')
633 | pprint.pprint(key)
634 |
635 | # now make trouble again
636 | key2['test'] = 'yet another breakage'
637 | try:
638 | merge_keys(key, key2)
639 | except Exception as e:
640 | print('Exception: ', end=' ')
641 | print(e)
642 |
643 | # now let's try dicts of dicts aka 'keydict'
644 | keydict = dict()
645 | keydict['key'] = key
646 | key3 = dict()
647 | key3['name'] = 'key'
648 | key3['protocol'] = 'prpl-jabber'
649 | key4 = dict()
650 | key4['name'] = 'key4'
651 | key4['protocol'] = 'prpl-jabber'
652 | key4['fingerprint'] = 'gotone'
653 | key4['teststate'] = 'this one should not be merged'
654 | key5 = dict()
655 | key5['name'] = 'key'
656 | key5['protocol'] = 'prpl-jabber'
657 | key5['moreinfo'] = 'even more!'
658 | keydict2 = dict()
659 | keydict2['key'] = key3
660 | keydict2['key'] = key5
661 | keydict2['key4'] = key4
662 | merge_keydicts(keydict, keydict2)
663 | pprint.pprint(keydict)
664 |
665 | # one more test
666 | print('---------------------------')
667 | key6 = dict()
668 | key6['name'] = 'key'
669 | keydict3 = dict()
670 | keydict3['key'] = key6
671 | pprint.pprint(keydict3['key'])
672 | merge_keys(keydict3['key'], key3)
673 | pprint.pprint(keydict3['key'])
674 | merge_keys(keydict3['key'], key5)
675 | pprint.pprint(keydict3['key'])
676 |
677 | sys.path.insert(0, os.path.abspath('..'))
678 | import otrapps
679 | print('\n---------------------------')
680 | print('Which supported apps are currently running:')
681 | print((which_apps_are_running(otrapps.__all__)))
682 |
683 | print('\n---------------------------')
684 | print('make backup conf file: ')
685 | tmpdir = tempfile.mkdtemp(prefix='.keysync-util-test-')
686 | testfile = os.path.join(tmpdir, 'keysync-util-conffile-backup-test')
687 | with open(testfile, 'w') as f:
688 | f.write('this is just a test!\n')
689 | make_conffile_backup(testfile)
690 | print('Backed up "%s"' % testfile)
691 |
692 | if can_sync_to_device():
693 | print('\n---------------------------')
694 | print('MTP is mounted here:', end=' ')
695 | print(mtp.devicename)
696 |
697 |
698 |
699 | if __name__ == "__main__":
700 | main(sys.argv[1:])
701 |
--------------------------------------------------------------------------------
/otrapps/xchat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | '''a module for reading and writing Xchat's OTR key data'''
4 |
5 | from __future__ import print_function
6 | import os
7 | import sys
8 |
9 | if __name__ == '__main__':
10 | sys.path.insert(0, "../") # so the main() test suite can find otrapps module
11 | import otrapps.util
12 | from otrapps.otr_private_key import OtrPrivateKeys
13 | from otrapps.otr_fingerprints import OtrFingerprints
14 |
15 | class XchatProperties():
16 |
17 | path = os.path.expanduser('~/.xchat2/otr')
18 | keyfile = 'otr.key'
19 | fingerprintfile = 'otr.fp'
20 | files = (keyfile, fingerprintfile)
21 |
22 | @staticmethod
23 | def parse(settingsdir=None):
24 | if settingsdir == None:
25 | settingsdir = XchatProperties.path
26 |
27 | kf = os.path.join(settingsdir, XchatProperties.keyfile)
28 | if os.path.exists(kf):
29 | keydict = OtrPrivateKeys.parse(kf)
30 | else:
31 | keydict = dict()
32 |
33 | fpf = os.path.join(settingsdir, XchatProperties.fingerprintfile)
34 | if os.path.exists(fpf):
35 | otrapps.util.merge_keydicts(keydict, OtrFingerprints.parse(fpf))
36 |
37 | return keydict
38 |
39 | @staticmethod
40 | def write(keydict, savedir):
41 | if not os.path.exists(savedir):
42 | raise Exception('"' + savedir + '" does not exist!')
43 |
44 | kf = os.path.join(savedir, XchatProperties.keyfile)
45 | OtrPrivateKeys.write(keydict, kf)
46 |
47 | accounts = []
48 | # look for all private keys and use them for the accounts list
49 | for name, key in keydict.items():
50 | if 'x' in key:
51 | accounts.append(name)
52 | fpf = os.path.join(savedir, XchatProperties.fingerprintfile)
53 | OtrFingerprints.write(keydict, fpf, accounts)
54 |
55 |
56 |
57 | if __name__ == '__main__':
58 |
59 | import pprint
60 |
61 | print('Xchat stores its files in ' + XchatProperties.path)
62 |
63 | if len(sys.argv) == 2:
64 | settingsdir = sys.argv[1]
65 | else:
66 | settingsdir = '../tests/xchat'
67 |
68 | keydict = XchatProperties.parse(settingsdir)
69 | pprint.pprint(keydict)
70 |
71 | XchatProperties.write(keydict, '/tmp')
72 |
--------------------------------------------------------------------------------
/py2app-python2.6.patch:
--------------------------------------------------------------------------------
1 | # HG changeset patch
2 | # User Ronald Oussoren
3 | # Date 1362420069 -3600
4 | # Branch branch-0.7
5 | # Node ID 70438e65f35b38fff433c883942732bfbe7bb14b
6 | # Parent da87652fa58b23646d0ab08b46b0faac88315d0f
7 | Fixes python 2.6 breakage
8 |
9 | fixes #96
10 |
11 | diff --git a/py2app/build_app.py b/py2app/build_app.py
12 | --- a/py2app/build_app.py
13 | +++ b/py2app/build_app.py
14 | @@ -18,12 +18,6 @@
15 | from py2app.apptemplate.setup import main as script_executable
16 | from py2app.util import mergecopy, make_exec
17 |
18 | -
19 | -try:
20 | - import sysconfig
21 | -except ImportError:
22 | - sysconfig = None
23 | -
24 | try:
25 | from cStringIO import StringIO
26 | except ImportError:
27 | @@ -499,8 +493,8 @@
28 | yield runtime
29 |
30 | def run(self):
31 | - if sysconfig.get_config_var('PYTHONFRAMEWORK') is None:
32 | - if not sysconfig.get_config_var('Py_ENABLE_SHARED'):
33 | + if get_config_var('PYTHONFRAMEWORK') is None:
34 | + if not get_config_var('Py_ENABLE_SHARED'):
35 | raise DistutilsPlatformError("This python does not have a shared library or framework")
36 |
37 | else:
38 | @@ -1132,11 +1126,8 @@
39 | # XXX - In this particular case we know exactly what we can
40 | # get away with.. should this be extended to the general
41 | # case? Per-framework recipes?
42 | - includedir = None
43 | - configdir = None
44 | - if sysconfig is not None:
45 | - includedir = sysconfig.get_config_var('CONFINCLUDEPY')
46 | - configdir = sysconfig.get_config_var('LIBPL')
47 | + includedir = get_config_var('CONFINCLUDEPY')
48 | + configdir = get_config_var('LIBPL')
49 |
50 |
51 | if includedir is None:
52 | @@ -1516,9 +1507,8 @@
53 |
54 | includedir = None
55 | configdir = None
56 | - if sysconfig is not None:
57 | - includedir = sysconfig.get_config_var('CONFINCLUDEPY')
58 | - configdir = sysconfig.get_config_var('LIBPL')
59 | + includedir = get_config_var('CONFINCLUDEPY')
60 | + configdir = get_config_var('LIBPL')
61 |
62 |
63 | if includedir is None:
64 | @@ -1582,11 +1572,8 @@
65 | os.unlink(site_path)
66 |
67 |
68 | - includedir = None
69 | - configdir = None
70 | - if sysconfig is not None:
71 | - includedir = sysconfig.get_config_var('CONFINCLUDEPY')
72 | - configdir = sysconfig.get_config_var('LIBPL')
73 | + includedir = get_config_var('CONFINCLUDEPY')
74 | + configdir = get_config_var('LIBPL')
75 |
76 |
77 | if includedir is None:
78 | @@ -1650,11 +1637,8 @@
79 | os.unlink(site_path)
80 |
81 |
82 | - includedir = None
83 | - configdir = None
84 | - if sysconfig is not None:
85 | - includedir = sysconfig.get_config_var('CONFINCLUDEPY')
86 | - configdir = sysconfig.get_config_var('LIBPL')
87 | + includedir = get_config_var('CONFINCLUDEPY')
88 | + configdir = get_config_var('LIBPL')
89 |
90 |
91 | if includedir is None:
92 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup
5 | import os
6 | import sys
7 |
8 | def read(fname):
9 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
10 |
11 | dependencies = [
12 | 'BeautifulSoup4',
13 | 'psutil',
14 | 'python-potr',
15 | 'pyasn1',
16 | 'pycrypto',
17 | 'pyjavaproperties',
18 | 'pyparsing',
19 | 'pgpdump',
20 | 'qrcode >= 4.0.1',
21 | 'six',
22 | ]
23 |
24 | # argparse and ordereddict are included in Python starting in 2.7
25 | if sys.version_info[0] == 2 and sys.version_info[1] < 7:
26 | dependencies.append('argparse')
27 | dependencies.append('ordereddict')
28 |
29 | if sys.platform == 'darwin':
30 | dependencies.append('PIL')
31 | dependencies.append('pymtp>=0.0.6')
32 | extra_options = dict(
33 | setup_requires=['py2app'],
34 | app=['keysync-gui'],
35 | # Cross-platform applications generally expect sys.argv to
36 | # be used for opening files.
37 | options=dict(
38 | py2app=dict(
39 | argv_emulation=True,
40 | semi_standalone=False,
41 | use_pythonpath=False,
42 | site_packages=True,
43 | frameworks='/sw/lib/libmtp.9.dylib',
44 | iconfile='icons/keysync.icns',
45 | plist={
46 | 'CFBundleIdentifier': 'info.guardianproject.keysync',
47 | 'CFBundleName': 'KeySync',
48 | 'CFBundleLocalizations': ['en'],
49 | 'PyRuntimeLocations': ['/System/Library/Frameworks/Python.framework/Versions/2.6/Python'],
50 | },
51 | ),
52 | ),
53 | )
54 | elif sys.platform == 'win32':
55 | dependencies.append('pyinstaller')
56 | dependencies.append('pywin32')
57 | # PIL doesn't build on Windows, so use Pillow instead, pegged at
58 | # 2.1.0 until pyinstaller supports newer version
59 | dependencies.append('Pillow==2.1.0')
60 | extra_options = dict()
61 | else:
62 | dependencies.append('Pillow>=2.1.0')
63 | dependencies.append('pymtp>=0.0.6')
64 | extra_options = dict()
65 |
66 |
67 | setup(name='keysync',
68 | version='0.2.2',
69 | description='syncs OTR keys between different IM programs',
70 | long_description=read('README.md'),
71 | author='The Guardian Project',
72 | author_email='support@guardianproject.info',
73 | url='https://guardianproject.info/apps/keysync',
74 | packages=['otrapps'],
75 | scripts=['keysync', 'keysync-gui'],
76 | data_files=[
77 | ('share/man/man1', ['man/keysync.1']),
78 | ('share/icons/hicolor/128x128/apps', ['icons/128x128/keysync.png']),
79 | ('share/icons/hicolor/256x256/apps', ['icons/keysync.png']),
80 | ('share/keysync',
81 | ['icons/add.png', 'icons/adium.png', 'icons/chatsecure.png',
82 | 'icons/gajim.png', 'icons/gnupg.png', 'icons/irssi.png',
83 | 'icons/jitsi.png', 'icons/keysync.png', 'icons/kopete.png',
84 | 'icons/pidgin.png', 'icons/xchat.png']),
85 | ('share/applications', ['keysync.desktop'])
86 | ],
87 | license='GPLv3+',
88 | classifiers=[
89 | 'Development Status :: 3 - Alpha',
90 | 'Intended Audience :: End Users/Desktop',
91 | 'Intended Audience :: Developers',
92 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
93 | 'Operating System :: POSIX',
94 | 'Operating System :: MacOS :: MacOS X',
95 | 'Operating System :: Microsoft :: Windows',
96 | 'Topic :: Communications :: Chat',
97 | 'Topic :: Software Development :: Libraries',
98 | 'Topic :: Software Development :: Libraries :: Python Modules',
99 | 'Topic :: Utilities',
100 | ],
101 | install_requires=dependencies,
102 | **extra_options
103 | )
104 |
--------------------------------------------------------------------------------
/tests/SAMENESS/adium/Accounts.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Accounts
6 |
7 |
8 | ObjectID
9 | 1
10 | Service
11 | Jabber
12 | Type
13 | libpurple-Jabber
14 | UID
15 | gptest@limun.org
16 |
17 |
18 | TopAccountID
19 | 2
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/SAMENESS/adium/otr.fingerprints:
--------------------------------------------------------------------------------
1 | fakename@jabber.ccc.de 1 libpurple-Jabber c298712572dfeead3e7624840f9120916da7f8a8 verified
2 | gptest@jabber.ccc.de 1 libpurple-Jabber a2cd015572d256703e7624840f91209160185410 verified
3 | gptest@jabber.org 1 libpurple-jabber 39e90112c19d45cecb968ff981d32e9c9a5fe284
4 | gptest@jabber.org 1 libpurple-Jabber f83fe8c909c4deb51c234574bab3e4f66af215f8 verified
5 | gptest@limun.org 1 libpurple-Jabber 39e90112c19d45cecb968ff981d32e9c9a5fe284
6 | gptest@slipjack.com 1 libpurple-Jabber 8d0f48cb59348f3aa596dc7b1f5048c14368cf5d verified
7 | hans@eds.org 1 libpurple-Jabber 2f66e8c909c4deb51cbd4a02b9e6af4d6af215f8 verified
8 | hans@eds.org 1 libpurple-Jabber f83fe8c909c4deb51c234574bab3e4f66af215f8
9 | n8fr8@jabber.com 1 libpurple-Jabber 92967e4743bc730e0a3396be1a6ea365cc38f0f3
10 |
--------------------------------------------------------------------------------
/tests/SAMENESS/adium/otr.private_key:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "1")
4 | (protocol libpurple-Jabber)
5 | (private-key
6 | (dsa
7 | (p #008D3EE66820824BDB81FCFEE853A2148C600D22C4D5D66ED7535A2F1F890A130239AFC3619FE2B5886EE2BEE109170D8D56B79AEB2217D490F92AA5A82F1B562CB19E51F3C38C717B4A0AA0306A9CBDF62977566AE041F3E2D60CE02C82ED9F350A38A016E9CB3C93C4E5F9A8B14ED738E9CB0D076EEB02009D0CBBF7AF36370B#)
8 | (q #00A7C84E6CB16B190F2FC962EDA3D77926490F902F#)
9 | (g #707219B8AE0783C234DF2426AA6E69F30738AC112FB0B53CC54776959AAF67626BB895E18AA8B735DD3E19BE780B5B7193DB2D64159B4757B0CB23AC555B70F25AF1474FE4D9C44059F8BFCA1BEBC845C4696A558ABA0C916368D3961EEBB8130E4651B618384E687CF7A9917637D97BE51D44217937F7D8A9A2C9ED850B0BAC#)
10 | (y #329D74BDCA8399977150519EDBC018988D43393834F77E36D577772B9434B2EE08B572BDBB7F4A23F5242FFCDBD90A4A8D405C8366CAB785455B431B888DB412BC37713648D6F5C919E501E949BA8B719BE3EF31B6244D483BCCAE042C1E6E6DF462E5A5064BED031A55BC1E3DAAF38895A1B92A20C6D82D78F90C24CABBC566#)
11 | (x #7A477B8980503487CEE95628EBAC52A19274BD57#)
12 | )
13 | )
14 | )
15 | )
16 |
--------------------------------------------------------------------------------
/tests/SAMENESS/pidgin/otr.fingerprints:
--------------------------------------------------------------------------------
1 | fakename@jabber.ccc.de gptest@limun.org/ prpl-jabber c298712572dfeead3e7624840f9120916da7f8a8 verified
2 | gptest@jabber.ccc.de gptest@limun.org/ prpl-jabber a2cd015572d256703e7624840f91209160185410 verified
3 | gptest@jabber.org gptest@limun.org/ prpl-jabber 39e90112c19d45cecb968ff981d32e9c9a5fe284
4 | gptest@jabber.org gptest@limun.org/ prpl-jabber f83fe8c909c4deb51c234574bab3e4f66af215f8 verified
5 | gptest@limun.org gptest@limun.org/ prpl-jabber 39e90112c19d45cecb968ff981d32e9c9a5fe284
6 | gptest@slipjack.com gptest@limun.org/ prpl-jabber 8d0f48cb59348f3aa596dc7b1f5048c14368cf5d verified
7 | hans@eds.org gptest@limun.org/ prpl-jabber 2f66e8c909c4deb51cbd4a02b9e6af4d6af215f8 verified
8 | hans@eds.org gptest@limun.org/ prpl-jabber f83fe8c909c4deb51c234574bab3e4f66af215f8
9 | n8fr8@jabber.com gptest@limun.org/ prpl-jabber 92967e4743bc730e0a3396be1a6ea365cc38f0f3
10 |
--------------------------------------------------------------------------------
/tests/SAMENESS/pidgin/otr.private_key:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "gptest@limun.org/")
4 | (protocol prpl-jabber)
5 | (private-key
6 | (dsa
7 | (p #008D3EE66820824BDB81FCFEE853A2148C600D22C4D5D66ED7535A2F1F890A130239AFC3619FE2B5886EE2BEE109170D8D56B79AEB2217D490F92AA5A82F1B562CB19E51F3C38C717B4A0AA0306A9CBDF62977566AE041F3E2D60CE02C82ED9F350A38A016E9CB3C93C4E5F9A8B14ED738E9CB0D076EEB02009D0CBBF7AF36370B#)
8 | (q #00A7C84E6CB16B190F2FC962EDA3D77926490F902F#)
9 | (g #707219B8AE0783C234DF2426AA6E69F30738AC112FB0B53CC54776959AAF67626BB895E18AA8B735DD3E19BE780B5B7193DB2D64159B4757B0CB23AC555B70F25AF1474FE4D9C44059F8BFCA1BEBC845C4696A558ABA0C916368D3961EEBB8130E4651B618384E687CF7A9917637D97BE51D44217937F7D8A9A2C9ED850B0BAC#)
10 | (y #329D74BDCA8399977150519EDBC018988D43393834F77E36D577772B9434B2EE08B572BDBB7F4A23F5242FFCDBD90A4A8D405C8366CAB785455B431B888DB412BC37713648D6F5C919E501E949BA8B719BE3EF31B6244D483BCCAE042C1E6E6DF462E5A5064BED031A55BC1E3DAAF38895A1B92A20C6D82D78F90C24CABBC566#)
11 | (x #7A477B8980503487CEE95628EBAC52A19274BD57#)
12 | )
13 | )
14 | )
15 | )
16 |
--------------------------------------------------------------------------------
/tests/adium/Accounts.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Accounts
6 |
7 |
8 | ObjectID
9 | 9
10 | Service
11 | Bonjour
12 | Type
13 | bonjour-libezv
14 | UID
15 | GPTest
16 |
17 |
18 | ObjectID
19 | 1
20 | Service
21 | Jabber
22 | Type
23 | libpurple-Jabber
24 | UID
25 | gptest@jabber.org
26 |
27 |
28 | ObjectID
29 | 2
30 | Service
31 | GTalk
32 | Type
33 | libpurple-jabber-gtalk
34 | UID
35 | gptest@slipjack.com
36 |
37 |
38 | TopAccountID
39 | 10
40 |
41 |
42 |
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Login Preferences.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Login Preferences.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/AccountPrefs.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/AccountPrefs.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Accounts.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Accounts
6 |
7 |
8 | ObjectID
9 | 1
10 | Service
11 | Jabber
12 | Type
13 | libpurple-Jabber
14 | UID
15 | gptest@jabber.org
16 |
17 |
18 | ObjectID
19 | 2
20 | Service
21 | GTalk
22 | Type
23 | libpurple-jabber-gtalk
24 | UID
25 | gptest@slipjack.com
26 |
27 |
28 | TopAccountID
29 | 3
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/ByObjectPrefs.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/ByObjectPrefs.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Contact Alerts.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Contact Alerts.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Contact List.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Contact List.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Event Presets.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Event Presets.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/General.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/General.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/List Layout.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/List Layout.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/List Theme.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/List Theme.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Logging.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/Logging.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/OTR.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/OTR.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/URL Handling Group.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/URL Handling Group.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/WebKit Message Display.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/adium/Library/Application Support/Adium 2.0/Users/Default/WebKit Message Display.plist
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/libpurple/accounts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | prpl-jabber
6 | gptest@jabber.org/palatschinken-3
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | <vCard xmlns='vcard-temp'/>
33 |
34 | 0
35 |
36 | 0
37 |
38 |
39 | 0
40 | proxy.eu.jabber.org
41 | 1
42 | jabber.org
43 | 5222
44 | opportunistic_tls
45 |
46 |
47 | 1
48 |
49 |
50 |
51 |
52 | prpl-jabber
53 | gptest@slipjack.com/Adium
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | <vCard xmlns='vcard-temp'><FN>Guardian Project Test</FN></vCard>
80 |
81 | 0
82 |
83 | 0
84 | talk.google.com
85 |
86 | 0
87 | proxy.eu.jabber.org
88 | 1
89 | talk.google.com
90 | 5222
91 | opportunistic_tls
92 |
93 |
94 | 1
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/libpurple/blist.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | gptest@slipjack.com
9 |
10 |
11 |
12 |
13 | hans@eds.org
14 | hans@eds.org
15 | 14c6c6f5962a46bebb7f866af4f246bc899f954c
16 | 14c6c6f5962a46bebb7f866af4f246bc899f954c.png
17 |
18 |
19 |
20 |
21 | hans@eds.org
22 | 14c6c6f5962a46bebb7f866af4f246bc899f954c
23 | 14c6c6f5962a46bebb7f866af4f246bc899f954c.png
24 |
25 |
26 |
27 |
28 | update@identi.ca
29 | update
30 |
31 |
32 |
33 |
34 | walker@slipjack.com
35 | walker
36 | 6fb356c6b00475b847823999b6269d16cc56ae89
37 | 6fb356c6b00475b847823999b6269d16cc56ae89.png
38 |
39 |
40 |
41 |
42 | gptest@jabber.org
43 |
44 |
45 |
46 |
47 | pdgsocmentor@gmail.com
48 |
49 |
50 |
51 |
52 |
53 |
54 | guardian.djh@gmail.com
55 | guardian.djh@gmail.com
56 |
57 |
58 |
59 |
60 | gptest@jabber.ccc.de
61 | gptest@jabber.ccc.de
62 |
63 |
64 |
65 |
66 | gp.test@jabber.org
67 | gp.test@jabber.org
68 |
69 |
70 |
71 |
72 | pdgsocmentor@gmail.com
73 | pdgsocmentor@gmail.com
74 |
75 |
76 |
77 |
78 | guardiantest@jabber.org
79 | guardiantest@jabber.org
80 |
81 |
82 |
83 |
84 | gptestlaptop@jabber.org
85 | gptestlaptop@jabber.org
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/libpurple/prefs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/tests/adium/Library/Application Support/Adium 2.0/Users/Default/libpurple/xmpp-caps.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/tests/adium/otr.fingerprints:
--------------------------------------------------------------------------------
1 | gptest@jabber.org 2 libpurple-jabber-gtalk 39e90112c19d45cecb968ff981d32e9c9a5fe284 verified
2 | gptest@jabber.org 1 libpurple-Jabber f83fe8c909c4deb51c234574bab3e4f66af215f8 verified
3 | gptest@slipjack.com 1 libpurple-Jabber 8d0f48cb59348f3aa596dc7b1f5048c14368cf5d verified
4 | hans@eds.org 1 libpurple-Jabber ff66e8c909c4deb51cbd4a02b9e6af4d6af215f8 verified
5 |
--------------------------------------------------------------------------------
/tests/adium/otr.private_key:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "1")
4 | (protocol libpurple-Jabber)
5 | (private-key
6 | (dsa
7 | (p #008D3EE66820824BDB81FCFEE853A2148C600D22C4D5D66ED7535A2F1F890A130239AFC3619FE2B5886EE2BEE109170D8D56B79AEB2217D490F92AA5A82F1B562CB19E51F3C38C717B4A0AA0306A9CBDF62977566AE041F3E2D60CE02C82ED9F350A38A016E9CB3C93C4E5F9A8B14ED738E9CB0D076EEB02009D0CBBF7AF36370B#)
8 | (q #00A7C84E6CB16B190F2FC962EDA3D77926490F902F#)
9 | (g #707219B8AE0783C234DF2426AA6E69F30738AC112FB0B53CC54776959AAF67626BB895E18AA8B735DD3E19BE780B5B7193DB2D64159B4757B0CB23AC555B70F25AF1474FE4D9C44059F8BFCA1BEBC845C4696A558ABA0C916368D3961EEBB8130E4651B618384E687CF7A9917637D97BE51D44217937F7D8A9A2C9ED850B0BAC#)
10 | (y #329D74BDCA8399977150519EDBC018988D43393834F77E36D577772B9434B2EE08B572BDBB7F4A23F5242FFCDBD90A4A8D405C8366CAB785455B431B888DB412BC37713648D6F5C919E501E949BA8B719BE3EF31B6244D483BCCAE042C1E6E6DF462E5A5064BED031A55BC1E3DAAF38895A1B92A20C6D82D78F90C24CABBC566#)
11 | (x #7A477B8980503487CEE95628EBAC52A19274BD57#)
12 | )
13 | )
14 | )
15 | (account
16 | (name "2")
17 | (protocol libpurple-jabber-gtalk)
18 | (private-key
19 | (dsa
20 | (p #00F24843F9447B62138AE49BF83188D1353ADA5CAC118890CFDEC01BF349D75E887B19C221665C7857CAD583AF656C67FB04A99FD8F8D69D09C9529C6C14D426F1E3924DC9243AF2970E3E4B04A23489A09E8A90E7E81EBA763AD4F0636B8A43415B6FC16A02C3624CE76272FA00783C8DB850D3A996B58136F7A0EB80AE0BC613#)
21 | (q #00D16B2607FCBC0EDC639F763A54F34475B1CC8473#)
22 | (g #00B15AFEF5F96EFEE41006F136C23A18849DA8133069A879D083F7C7AA362E187DAE3ED0C4F372D0D4E3AAE567008A1872A6E85D8F84E53A3FE1B352AF0B4E2F0CB033A6D34285ECD3E4A93653BDE99C3A8D840D9D35F82AC2FA8539DB6C7F7A1DAD77FEECD62803757FF1E2DE4CEC4A5A2AD643271514DDEEEF3D008F66FBF9DB#)
23 | (y #01F9BE7DA0E4E84774048058B53202B2704BF688A306092ED533A55E68EABA814C8D62F45AAD8FF30C3055DCA461B7DBA6B78938FC4D69780A830C6457CC107F3D275C21D00E53147C14162176C77169D3BCA586DC30F15F4B482160E276869AA336F38AF7FC3686A764AB5A02C751D921A42B8B9AE8E06918059CD73C424154#)
24 | (x #00943480B228FC0D3D7ADFC91F680FC415E1306333#)
25 | )
26 | )
27 | )
28 | )
29 |
--------------------------------------------------------------------------------
/tests/chatsecure/otr_keystore:
--------------------------------------------------------------------------------
1 | #Tue Nov 15 14:39:30 EST 2011
2 | gptest@jabber.org.privateKey=MIIBSgIBADCCASsGByqGSM44BAEwggEeAoGBAMtPbcgvf2CAHN4djUb+gCPw/e8Xpeyc9GknS9zs\nJjSCg9vgiKBVlQBceiKAkK8SVVEaA671SS0XO575OK/sAc4j0n2t9QJP1wyGCOhV79WbwhPPEVhs\ncpAHakr9IAW6WdSnwhL/seZLYRKiVGpxXJffwN+sYjH00PulKNxmz2+DAhUAxh9yFSC1uuGk6IR0\ntnVAfsPUt7cCgYBGfHU40n0HgKIkVe3XtX0G3CbiGbR++qaEjNqnfWynggqeeVkYliLaDlVrR4B0\nnLrHZLEcUMO38YKmrwug02acp9P65IcjZ2yaioPBSmV7R6pMGOdJFR3V7Pd5R2+NcUdJd2xSffLf\nrChM82SKqa7b3DOPHkSoIdp/vJiRgikZrwQWAhRE5snYBaoR84hWVdxlumAYkBRUEA\=\=
3 | gptest@jabber.org.publicKey=MIIBtjCCASsGByqGSM44BAEwggEeAoGBAMtPbcgvf2CAHN4djUb+gCPw/e8Xpeyc9GknS9zsJjSC\ng9vgiKBVlQBceiKAkK8SVVEaA671SS0XO575OK/sAc4j0n2t9QJP1wyGCOhV79WbwhPPEVhscpAH\nakr9IAW6WdSnwhL/seZLYRKiVGpxXJffwN+sYjH00PulKNxmz2+DAhUAxh9yFSC1uuGk6IR0tnVA\nfsPUt7cCgYBGfHU40n0HgKIkVe3XtX0G3CbiGbR++qaEjNqnfWynggqeeVkYliLaDlVrR4B0nLrH\nZLEcUMO38YKmrwug02acp9P65IcjZ2yaioPBSmV7R6pMGOdJFR3V7Pd5R2+NcUdJd2xSffLfrChM\n82SKqa7b3DOPHkSoIdp/vJiRgikZrwOBhAACgYAVb/mCnKb7Zl12kPXYTTkCvN4JSvxxhAmb7Nea\nXno2JVd5X/4ubp3M5QGQWvf72FXwUnSILRz6T8gRaEYtuSO3/lY4q5vOAOnVQU6KjH97SKMutwHT\nl9d+zbuoBc4YMASUZa+vKqRZ3a+d15WdlBjtEzB2NbBbnbCJKjfGSmOCbg\=\=
4 | hans@eds.org.FF66E8C909C4DEB51CBD4A02B9E6AF4D6AF215F8.publicKey.verified=true
5 | hans@eds.org.publicKey=MIIBtjCCASsGByqGSM44BAEwggEeAoGBAIaQ8aS9ZyrLhulUFl80zqdsU1XcYjQ5t+kgi52LSd9J\n0k4k7RrVeAnHHxcbyF7usVpkD+KG9V5VrUgYRDLZSAsGPcjITQcEqiv1zkJqog9InoPciOv+qVVA\nwKEwC5tGLEQ8eb9r7O0RQVLg8o7vSTs8yHBg77BDn+0UQsDaTUuTAhUA20hNBdXSrUtNMkkoFgLC\nNjEQq38CgYARy0ipvVMrbQG8+9rsRk7budGuq78EMVHYX18RgDGpwUG28tmwQTc77Q2plwqvw1yp\nRK43gdIq6M2SkD3BpBSHFyK4lp8m+YOSOTyA8DZ6TsdXtolfCKVOvoFBk5jN07QtT4FVkmgomAFy\nIVyX2DoHcnfXGvjEH0ecyh7LkA8nYgOBhAACgYBuKX5DVjpVF/8RqiqDN6RernvCMEjn4OXN6gBN\nmTZqrJg8Mi/gzOofFe40sZ4TzmGZX1lM3nfkjBpsvNkJ/W4QCDVeSTE90RM3B0ohXEwY2KWQ3QrI\nQvU33hRyXJYOVX31nQbQRHy1fgWDuEGCM01RUtrCtIdoxm2Fe2qMt4nINg\=\=
6 |
--------------------------------------------------------------------------------
/tests/chatsecure/otr_keystore.ofcaes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/chatsecure/otr_keystore.ofcaes
--------------------------------------------------------------------------------
/tests/gajim/guardianproject.info.fpr:
--------------------------------------------------------------------------------
1 | hans@guardianproject.info gptest@guardianproject.info xmpp df953988f5c05788c0327186d06d3bb4ddaaec7f smp
2 | miron@hyper.to gptest@guardianproject.info xmpp 3fed5b9b61a7669b4f140639df65af0233aec76c
3 | steve.wyshywaniuk@gmail.com gptest@guardianproject.info xmpp 367dd4fd52639242eb00a0e37f5d290934b4d37f
4 | baghdadbot@gmail.com gptest@guardianproject.info xmpp 30e756646687d75b61434667883779cd2ad3db1d
5 |
--------------------------------------------------------------------------------
/tests/gajim/guardianproject.info.key3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/gajim/guardianproject.info.key3
--------------------------------------------------------------------------------
/tests/gnupg/pubring.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/gnupg/pubring.gpg
--------------------------------------------------------------------------------
/tests/gnupg/random_seed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/gnupg/random_seed
--------------------------------------------------------------------------------
/tests/gnupg/secring.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/gnupg/secring.gpg
--------------------------------------------------------------------------------
/tests/gnupg/trustdb.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/tests/gnupg/trustdb.gpg
--------------------------------------------------------------------------------
/tests/irssi/otr.fp:
--------------------------------------------------------------------------------
1 | bigbear test@example.org IRC fffffffffffffcdfdddddeeeeee8aaaaaad00000
2 | goldy test@example.org IRC 0000111122223444455566666777778889990aab
3 | blue test@example.org IRC bbbbbbbbbb223444455566666777778889990aab smp
4 | testymctest test@example.org IRC eeeeee1122223444455566666777778889990aab manual
5 |
--------------------------------------------------------------------------------
/tests/irssi/otr.key:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "test@example.org")
4 | (protocol IRC)
5 | (private-key
6 | (dsa
7 | (p #00E35B4CA8D0AED9CF4B1775A0C97789160791D9F2ED8A11EF1F120EDF0E1143232A4C336132C4D759C7A1C093EEF57E879C42BCF2E316A12D47B6189CC9F53F950172BB6D2417EDB2351F87516CFF87F3D01298D62D41607D237E8FD6B32360D2D1DA51C0D16C284F4180C6655887BFFEA7EA5755751C23F26EB8185E1C82A173#)
8 | (q #00A761D47128AD0FC90DB212364A63DD7E685D3C67#)
9 | (g #00CDCB59DF8B48DE44E1E7B3D4A18AFED11396CD7BC9BD5C9314996D48B5110D2F4BF6C3B2F0F2B47DDA2AFE6718177EB4E91E74B7C1E40F86186160337E5BA5CE763C87DAFCCA673540E3A9DF24A5A3B2CD75DB91A18B30AD14864F04B0A924556E6842FFB88932D0AE588705AC4FDABF686940934DD731917D63AF21FF19CA8B#)
10 | (y #00B074FD79609810E2B187A09D403DF8F865D99C717C9E3D0A461D0C37E8957A85B1048830965D72EC376B0C2D2567D60837D5BB6261FB6A4B9BBD6B96DDD5D4E571FBF919AE866DB6438E3D821C501B6E255E89210F0DB90EB9D435ED5B9E62F2644823608249620CEFEC9E5E80B598A1C9A1EDE8E7F1DE5DED9F3A3AD749F7F2#)
11 | (x #0DF19E6EF1D1E7D51818E085F7DDB8CB9F77CA1A#)
12 | )
13 | )
14 | )
15 | )
16 |
--------------------------------------------------------------------------------
/tests/jitsi/contactlist.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | wifey
14 |
15 | xcap.resolved=false;
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | gptest@jabber.org
28 |
29 |
30 |
31 | nathanfreitas@gmail.com
32 |
33 |
34 |
35 | fakename(mac.com)@aol.com
36 |
37 |
38 |
39 | hans@eds.org
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | gp.test@jabber.org
59 |
60 |
61 |
62 | gptest@jabber.ccc.de
63 |
64 |
65 |
66 | gptestlaptop@jabber.org
67 |
68 |
69 |
70 | guardiantest@jabber.org
71 |
72 |
73 |
74 | guardianproject@gmail.com
75 |
76 |
77 |
78 | gptest@limun.org
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Wifey
87 |
88 |
89 |
90 | horrendous@gmail.com
91 |
92 |
93 |
94 | gptest@jabber.com
95 |
96 |
97 |
98 | maximum@googlemail.com
99 |
100 |
101 |
102 | hans@guardianproject.info
103 |
104 |
105 |
106 | hans@eds.org
107 |
108 |
109 |
110 | gptest@limum.org
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/tests/keyczar/privatekey-v0.5b.json:
--------------------------------------------------------------------------------
1 | {"publicKey":{"x509":"MIIBuDCCASwGByqGSM44BAEwggEfAoGBAP1_U4EddRIpUt9KnC7s5O
2 | f2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq_xfW6MPbLm1Vs14E7gB00b_JmYLdrmVClpJ-f6A
3 | R7ECLCT7up1_63xhv4O1fnxqimFQ8E-4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAh
4 | UAl2BQjxUjC8yykrmCouuEC_BYHPUCgYEA9-GghdabPd7LvKtcNrhXuXmUr7v6OuqC-VdMCz0Hgm
5 | dRWVeOutRZT-ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN_C_ohNW
6 | Lx-2J6ASQ7zKTxvqhRkImog9_hWuWfBpKLZl6Ae1UlZAFMO_7PSSoDgYUAAoGBAMRSRR0U9NUcWJ
7 | qziTTRgMykYcPOWe7qlAwoPdPdVoYFUKuXCgZNgo15jLrqLdCYi6YDw2ueMBKpUjVW01SM8ispW0
8 | KlSd7HNGMPtTYdq5h7j-HrYgEQwuPbnbZnJLsPwRE2lg-1q-GMwLDUdBlAMajWvNwqT-dbnxbB6M
9 | HSVrAR"},
10 | "pkcs8":"MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1_U4EddRIpUt9KnC7s5Of2EbdSPO9
11 | EAMMeP4C2USZpRV1AIlH7WT2NWPq_xfW6MPbLm1Vs14E7gB00b_JmYLdrmVClpJ-f6AR7ECLCT7u
12 | p1_63xhv4O1fnxqimFQ8E-4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxU
13 | jC8yykrmCouuEC_BYHPUCgYEA9-GghdabPd7LvKtcNrhXuXmUr7v6OuqC-VdMCz0HgmdRWVeOutR
14 | ZT-ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN_C_ohNWLx-2J6ASQ
15 | 7zKTxvqhRkImog9_hWuWfBpKLZl6Ae1UlZAFMO_7PSSoEFgIUIDVT7v0qg5FgzH3acadcaJ9Av9M"}
16 |
--------------------------------------------------------------------------------
/tests/keyczar/privatekey-v0.6b.json:
--------------------------------------------------------------------------------
1 | {
2 | "publicKey": {
3 | "q": "AJJfsQZrhUV8p6TmpPqa084JwX9j",
4 | "p": "AIAAAAAAAAXxHhQxJRZ-PPj2BDrHHLV8c8pX6nyOLAW3Bc7CX_SfBiGH2VyImoz6JlAOZi
5 | 6x_XspxdUvpTjV7J9uO9hwnF31m3SQjdkZW2DQDb5OS1rW_4MGrTJCktKtlZz7f8_5AoO8
6 | yHSY2XWNDqrpBEiNvaTX1ttQ59nREiR1",
7 | "y": "AGlQuRpbat4drE_fcdSZrEVfS6Fme3tNfUoJVRec1pUhoSo9PBHKFx3lbBmI8Vnub8vuY1
8 | nM2yTadOZ8H4-TYxB5JNMVTK7vLNdVcWvUUF9zRZCwps1bl0_Al29X0I1iQYJN6Klxi_Qb
9 | KaSf5PhfXLVom9bJYp7_TwZCouaab296",
10 | "g": "AES5hk-DKXP__t6yDsXIdykf7lhSKHqQCW5H2V5dMg8JkoFBSP7 mIvaCHT4IxoxdM2AIp
11 | Wgcoi5XSrd_hD2sjNa1JHTb9BUh31dHJLym6rTsV12ClN6f78Cjt0oKFIRI__yWn9KM-vL
12 | Esjpd10VHlPfbEgKYePCnXFt7Y78G0wGr",
13 | "size": 1024
14 | },
15 | "x": "AGLJry5Q0CZo9cH6XRYd2ZZZppwg",
16 | "size": 1024,
17 | }
18 |
--------------------------------------------------------------------------------
/tests/keyczar/publickey-v0.5b.json:
--------------------------------------------------------------------------------
1 | {"x509":"MIIBuDCCASwGByqGSM44BAEwggEfAoGBAP1_U4EddRIpUt9KnC7s5O
2 | f2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq_xfW6MPbLm1Vs14E7gB00b_JmYLdrmVClpJ-f6A
3 | R7ECLCT7up1_63xhv4O1fnxqimFQ8E-4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAh
4 | UAl2BQjxUjC8yykrmCouuEC_BYHPUCgYEA9-GghdabPd7LvKtcNrhXuXmUr7v6OuqC-VdMCz0Hgm
5 | dRWVeOutRZT-ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN_C_ohNW
6 | Lx-2J6ASQ7zKTxvqhRkImog9_hWuWfBpKLZl6Ae1UlZAFMO_7PSSoDgYUAAoGBAMRSRR0U9NUcWJ
7 | qziTTRgMykYcPOWe7qlAwoPdPdVoYFUKuXCgZNgo15jLrqLdCYi6YDw2ueMBKpUjVW01SM8ispW0
8 | KlSd7HNGMPtTYdq5h7j-HrYgEQwuPbnbZnJLsPwRE2lg-1q-GMwLDUdBlAMajWvNwqT-dbnxbB6M
9 | HSVrAR"}
10 |
--------------------------------------------------------------------------------
/tests/keyczar/publickey-v0.6b.json:
--------------------------------------------------------------------------------
1 | {
2 | "q": "AJJfsQZrhUV8p6TmpPqa084JwX9j",
3 | "p": "AIAAAAAAAAXxHhQxJRZ-PPj2BDrHHLV8c8pX6nyOLAW3Bc7CX_SfBiGH2VyImoz6JlAOZi
4 | 6x_XspxdUvpTjV7J9uO9hwnF31m3SQjdkZW2DQDb5OS1rW_4MGrTJCktKtlZz7f8_5AoO8
5 | yHSY2XWNDqrpBEiNvaTX1ttQ59nREiR1",
6 | "y": "AGlQuRpbat4drE_fcdSZrEVfS6Fme3tNfUoJVRec1pUhoSo9PBHKFx3lbBmI8Vnub8vuY1
7 | nM2yTadOZ8H4-TYxB5JNMVTK7vLNdVcWvUUF9zRZCwps1bl0_Al29X0I1iQYJN6Klxi_Qb
8 | KaSf5PhfXLVom9bJYp7_TwZCouaab296",
9 | "g": "AES5hk-DKXP__t6yDsXIdykf7lhSKHqQCW5H2V5dMg8JkoFBSP7 mIvaCHT4IxoxdM2AIp
10 | Wgcoi5XSrd_hD2sjNa1JHTb9BUh31dHJLym6rTsV12ClN6f78Cjt0oKFIRI__yWn9KM-vL
11 | Esjpd10VHlPfbEgKYePCnXFt7Y78G0wGr",
12 | "size": 1024
13 | }
14 |
--------------------------------------------------------------------------------
/tests/kopete/fingerprints:
--------------------------------------------------------------------------------
1 | gptest@jabber.ccc.de gptest@jabber.org/ prpl-jabber a2cd015572d256703e7624840f91209160185410
2 | gptest@jabber.ccc.de gptest@limun.org/ prpl-jabber a2cd015572d256703e7624840f91209160185410 verified
3 | hans@eds.org gptest@jabber.org/ prpl-jabber ff66e8c909c4deb51cbd4a02b9e6af4d6af215f8
4 | hans@eds.org gptest@limun.org/ prpl-jabber f83fe8c909c4deb51c234574bab3e4f66af215f8
5 | n8fr8@foo_com gptest@mycomputer prpl-bonjour 92967e4743bc730e0a3396be1a6ea365cc38f0f3
6 | gptest@jabber_ccc_de gptest@mycomputer prpl-bonjour a2cd015572d256703e7624840f91209160185410 verified
7 |
--------------------------------------------------------------------------------
/tests/kopete/privkeys:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "gptest@limun.org/")
4 | (protocol prpl-jabber)
5 | (private-key
6 | (dsa
7 | (p #00B20F6CADBA15D628E861586099F9C18E68C822D3C02E21B57B7464D5A664BF40E6EB45F1931E9CD3E098890BF2E09662DFBE2B530CBDE8FF1D542333C02DFEAA904A970C29A7403F9328B2BC6D8EA6233E484B4E9B28D63B83CB1FC13DFDF493D80DEE4AB52DDDC3C19AC9144C16E46B491327C09E688AE582B9F8D56E271A2B#)
8 | (q #00EAB4FEDFB2C033131204EADDFD9EFA21B50D0AE9#)
9 | (g #00AEC459E479F4A750672567E3B69F3461A54480222580EABF14DA0D520EB0F6D7A74B2287A5A4628C8F966FB5D2AD77C09F4053B9D08FA001BA167CA18CC05177B864FE2EED8C759C4475E2A6B4284B6BB0A2CB856EBD45BB299E30709F09D260672CC850E0A12AD8C507B34080377E9C0E401A3F84B0783EA87112B1DBDA9B0A#)
10 | (y #29A9DE8FD266F19188246B34B613F4717DF106CD8B2179C69CF4172196B0FD562DBB3069428B9315C819758A3CBF0977A7B2054600C43554DF12AFE496FEF79DF5A14300E3F5F2A0E26E8EBB576A52BD423929718D4EA689BD46A021BCD96A5BAE8F83E9C0FF46BE63BE0239A5FC1EE901AA56E2E98E4CDA7D069570E5BC3615#)
11 | (x #00B8787BA6DEEEC7496EACCA84FF723F6E9EA5F6A5#)
12 | )
13 | )
14 | )
15 | (account
16 | (name "gptest@jabber.org/")
17 | (protocol prpl-jabber)
18 | (private-key
19 | (dsa
20 | (p #00CD96479A2F404FB600F9C85EE3DCD69FDF93F217AEE54A1286069983BA7731D0C73C7CB3B8CFA482A0AF6FF906E470EB4EF7F4D201D087AEDBF0086710F3039CBF42358C1BFA7D86A36332E21D32BE31538571CBC8D4F281DDD1076BA2B29B549ED29B3C19C341AEF83C80157E87FF2930B5E15C84A09AFCE28A06E06A62BCEF#)
21 | (q #00D5B4647E688974D1B6B199E1A59AB2F985DBCE01#)
22 | (g #0093E333135FCBCE68FC6E410B304482F2F95D82BF53534C3636EADB0C22241CF35BD294B096070DC08138EFE73B03C88FD444595974E9455274F695147AF9D46B85286B4CFEF3D00BCA1D3BEB8C7EFFBA08132E1A1E4D9F115B863C52C72971F4695758354FC3BE3C4A45AF6A47747B59733905C33EE86ED68D9D90494877AE33#)
23 | (y #362C06B9CDD67FC4E7A7A62289D6C1E8BA061A024946A5ACC1A7DC70F6B03D99A1D3B3215D20BC4F8458EEC3F31E1391E9B519FDCDCC3CF0FBA38F8A7213551B32D59DE655F506633FB6B0EA94C4174D227DB614EF6723AB057B40CF36E2A414D0A8DCF223EE7EDD851793C4DC92282C79503030045D49A0ABCC3C6CC4080909#)
24 | (x #24FE542B1C7DC8337F6F8030C7D639B7BF091B40#)
25 | )
26 | )
27 | )
28 | (account
29 | (name "gptest@mycomputer")
30 | (protocol prpl-bonjour)
31 | (private-key
32 | (dsa
33 | (p #00B727869A051E6F00EA571BEC0A3EE8D9DBE91CBEA9AF8CE1122B18F9EBEBE6B2F4CA2D0A481D0EC9363C56F389EA7AC1F631798989797FE4868AAA18E005D3A7FAA631047F73EC9291246369495CB9ADD4E6447BF959B2FA6AA03DF5B781A40A40B7693AF5B1B466DB1BE14CFB92C5680D6CA9ADD1A4F134C02A0C4CA32AFEC7#)
34 | (q #008952F4EA8C624566E2B720734562EF2EA9ADE76D#)
35 | (g #2570F44667EC9FF45456EEEBC0A000FA9D24CD5DF1EBB59C897E07D4F98CB6798011235BC5075BE96AADE338D02CA31CCF6393ED2399CB4263429170B0C841A17BE2E2B43914C004AAAE05CA544723215D99D809E0C28C13D5DFFF8D381323D54C4B785E8C922DC7A4A78F9324161BAC2A9745E667594C89BEDE7997B9E2545F#)
36 | (y #009EEBE6739180FC47E744F1ECB4321B11255787890123578902350C052CE18985F1F6F37986A5D8EDBDB524264879128793489172475E60A20EACFDEB168D05921E242B5A3A78912389ABBADF742B4BAABE35345E3D9E5F13BBF5BB8806CEFABE9B227219724889AB897234D4295CFE7EF1DDCE997BA504304BB87F4EA18A4C1A#)
37 | (x #072349EAF19604CCE2FEDDBEA2342CCF93C71224#)
38 | )
39 | )
40 | )
41 | )
42 |
--------------------------------------------------------------------------------
/tests/openssl/parse-private-key-PKCS#8.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import pyjavaproperties
4 | import imp
5 |
6 | errors = imp.load_source('errors', '../../otrapps/errors.py')
7 | util = imp.load_source('util', '../../otrapps/util.py')
8 |
9 | filename = 'dsa-key.properties'
10 | p = pyjavaproperties.Properties()
11 | p.load(open(filename))
12 | for item in p.items():
13 | if item[0] == 'privateKey':
14 | privdict = util.ParsePkcs8(item[1])
15 | print 'privdict: ',
16 | print privdict
17 | elif item[0] == 'publicKey':
18 | pubdict = util.ParseX509(item[1])
19 | print 'pubdict: ',
20 | print pubdict
21 |
--------------------------------------------------------------------------------
/tests/openssl/useful-commands.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # this script uses openssl to generate a standard OTR DSA key or 3072 bits
4 |
5 | openssl_key=private-key-openssl.pem
6 | x509_key=public-key-PKCS\#1-X.509.pem
7 | pkcs8_key=private-key-PKCS\#8.pem
8 | propfile=dsa-key.properties
9 |
10 | # generate a new unencrypted DSA key
11 | openssl dsaparam -out dsaparam.pem 3072
12 | openssl gendsa -out $openssl_key dsaparam.pem
13 |
14 | # convert public key to PKCS#1/X.509 format
15 | openssl dsa -in $openssl_key -pubout -out $x509_key
16 |
17 | # convert private key to a PKCS#8 format
18 | openssl pkcs8 -nocrypt -in $openssl_key -topk8 -out $pkcs8_key
19 |
20 | # convert PKCS#8 private key to a java properties file
21 | printf "privateKey=" > $propfile
22 | echo `cat $pkcs8_key | grep -v -- '-----'` | sed 's, ,,g' | sed 's,=,\\=,g' >> $propfile
23 |
24 | # convert PKCS #1/X.509 public key to a java properties file
25 | printf "publicKey=" >> $propfile
26 | echo `cat $x509_key | grep -v -- '-----'` | sed 's, ,,g' | sed 's,=,\\=,g' >> $propfile
27 |
--------------------------------------------------------------------------------
/tests/pidgin/otr.fingerprints:
--------------------------------------------------------------------------------
1 | gptest@jabber.ccc.de gptest@jabber.org/ prpl-jabber a2cd015572d256703e7624840f91209160185410
2 | gptest@jabber.ccc.de gptest@limun.org/ prpl-jabber a2cd015572d256703e7624840f91209160185410 verified
3 | hans@eds.org gptest@jabber.org/ prpl-jabber ff66e8c909c4deb51cbd4a02b9e6af4d6af215f8
4 | hans@eds.org gptest@limun.org/ prpl-jabber f83fe8c909c4deb51c234574bab3e4f66af215f8
5 | n8fr8@foo_com gptest@mycomputer prpl-bonjour 92967e4743bc730e0a3396be1a6ea365cc38f0f3
6 | gptest@jabber_ccc_de gptest@mycomputer prpl-bonjour a2cd015572d256703e7624840f91209160185410 verified
7 |
--------------------------------------------------------------------------------
/tests/pidgin/otr.private_key:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "gptest@limun.org/")
4 | (protocol prpl-jabber)
5 | (private-key
6 | (dsa
7 | (p #00B20F6CADBA15D628E861586099F9C18E68C822D3C02E21B57B7464D5A664BF40E6EB45F1931E9CD3E098890BF2E09662DFBE2B530CBDE8FF1D542333C02DFEAA904A970C29A7403F9328B2BC6D8EA6233E484B4E9B28D63B83CB1FC13DFDF493D80DEE4AB52DDDC3C19AC9144C16E46B491327C09E688AE582B9F8D56E271A2B#)
8 | (q #00EAB4FEDFB2C033131204EADDFD9EFA21B50D0AE9#)
9 | (g #00AEC459E479F4A750672567E3B69F3461A54480222580EABF14DA0D520EB0F6D7A74B2287A5A4628C8F966FB5D2AD77C09F4053B9D08FA001BA167CA18CC05177B864FE2EED8C759C4475E2A6B4284B6BB0A2CB856EBD45BB299E30709F09D260672CC850E0A12AD8C507B34080377E9C0E401A3F84B0783EA87112B1DBDA9B0A#)
10 | (y #29A9DE8FD266F19188246B34B613F4717DF106CD8B2179C69CF4172196B0FD562DBB3069428B9315C819758A3CBF0977A7B2054600C43554DF12AFE496FEF79DF5A14300E3F5F2A0E26E8EBB576A52BD423929718D4EA689BD46A021BCD96A5BAE8F83E9C0FF46BE63BE0239A5FC1EE901AA56E2E98E4CDA7D069570E5BC3615#)
11 | (x #00B8787BA6DEEEC7496EACCA84FF723F6E9EA5F6A5#)
12 | )
13 | )
14 | )
15 | (account
16 | (name "gptest@jabber.org/")
17 | (protocol prpl-jabber)
18 | (private-key
19 | (dsa
20 | (p #00CD96479A2F404FB600F9C85EE3DCD69FDF93F217AEE54A1286069983BA7731D0C73C7CB3B8CFA482A0AF6FF906E470EB4EF7F4D201D087AEDBF0086710F3039CBF42358C1BFA7D86A36332E21D32BE31538571CBC8D4F281DDD1076BA2B29B549ED29B3C19C341AEF83C80157E87FF2930B5E15C84A09AFCE28A06E06A62BCEF#)
21 | (q #00D5B4647E688974D1B6B199E1A59AB2F985DBCE01#)
22 | (g #0093E333135FCBCE68FC6E410B304482F2F95D82BF53534C3636EADB0C22241CF35BD294B096070DC08138EFE73B03C88FD444595974E9455274F695147AF9D46B85286B4CFEF3D00BCA1D3BEB8C7EFFBA08132E1A1E4D9F115B863C52C72971F4695758354FC3BE3C4A45AF6A47747B59733905C33EE86ED68D9D90494877AE33#)
23 | (y #362C06B9CDD67FC4E7A7A62289D6C1E8BA061A024946A5ACC1A7DC70F6B03D99A1D3B3215D20BC4F8458EEC3F31E1391E9B519FDCDCC3CF0FBA38F8A7213551B32D59DE655F506633FB6B0EA94C4174D227DB614EF6723AB057B40CF36E2A414D0A8DCF223EE7EDD851793C4DC92282C79503030045D49A0ABCC3C6CC4080909#)
24 | (x #24FE542B1C7DC8337F6F8030C7D639B7BF091B40#)
25 | )
26 | )
27 | )
28 | (account
29 | (name "gptest@mycomputer")
30 | (protocol prpl-bonjour)
31 | (private-key
32 | (dsa
33 | (p #00B727869A051E6F00EA571BEC0A3EE8D9DBE91CBEA9AF8CE1122B18F9EBEBE6B2F4CA2D0A481D0EC9363C56F389EA7AC1F631798989797FE4868AAA18E005D3A7FAA631047F73EC9291246369495CB9ADD4E6447BF959B2FA6AA03DF5B781A40A40B7693AF5B1B466DB1BE14CFB92C5680D6CA9ADD1A4F134C02A0C4CA32AFEC7#)
34 | (q #008952F4EA8C624566E2B720734562EF2EA9ADE76D#)
35 | (g #2570F44667EC9FF45456EEEBC0A000FA9D24CD5DF1EBB59C897E07D4F98CB6798011235BC5075BE96AADE338D02CA31CCF6393ED2399CB4263429170B0C841A17BE2E2B43914C004AAAE05CA544723215D99D809E0C28C13D5DFFF8D381323D54C4B785E8C922DC7A4A78F9324161BAC2A9745E667594C89BEDE7997B9E2545F#)
36 | (y #009EEBE6739180FC47E744F1ECB4321B11255787890123578902350C052CE18985F1F6F37986A5D8EDBDB524264879128793489172475E60A20EACFDEB168D05921E242B5A3A78912389ABBADF742B4BAABE35345E3D9E5F13BBF5BB8806CEFABE9B227219724889AB897234D4295CFE7EF1DDCE997BA504304BB87F4EA18A4C1A#)
37 | (x #072349EAF19604CCE2FEDDBEA2342CCF93C71224#)
38 | )
39 | )
40 | )
41 | )
42 |
--------------------------------------------------------------------------------
/tests/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | copy_accounts_files () {
6 | app=$1
7 | tests="$2"
8 | outdir="$3"
9 | test -d $outdir || mkdir -p $outdir
10 | if [ $app = 'adium' ]; then
11 | cp $tests/adium/Accounts.plist $outdir/
12 | elif [ $app = 'jitsi' ]; then
13 | cp $tests/jitsi/contactlist.xml \
14 | $tests/jitsi/sip-communicator.properties \
15 | $outdir/
16 | elif [ $app = 'pidgin' ]; then
17 | cp $tests/pidgin/accounts.xml $outdir/
18 | elif [ $app = 'gajim' ]; then
19 | cp $tests/gajim/config $outdir/
20 | fi
21 | }
22 |
23 |
24 | tmpdir=`mktemp -d /tmp/keysync-XXXXXXXXXXXXXX`
25 | testbase=`pwd`
26 | projectbase=$(dirname $testbase)
27 |
28 | # allow the location of the script to be overridden
29 | if [ -z $keysync ]; then
30 | keysync="./keysync"
31 | fi
32 |
33 |
34 | echo '========================================================================'
35 | echo "Run each SAMENESS test"
36 | echo '========================================================================'
37 | cd $projectbase
38 | for inapp in adium pidgin; do
39 | for outapp in adium pidgin; do
40 | echo ''
41 | echo ''
42 | echo '------------------------------------------------------------------------'
43 | echo "Run $inapp-$outapp SAMENESS tests"
44 | echo '------------------------------------------------------------------------'
45 | tests=$testbase/SAMENESS
46 | outdir=$tmpdir/SAMENESS-$inapp-$outapp
47 | copy_accounts_files $outapp $tests $outdir
48 | $keysync --test $tests -i $inapp -o $outapp --output-folder $outdir
49 | for f in $tests/$outapp/*; do
50 | echo '--------------------------------'
51 | echo $f
52 | # remove '|| true' once these tests actually pass:
53 | diff -u $f $outdir/$(basename $f) || true
54 | # nice way to see the diff:
55 | # meld $f $outdir/$(basename $f)
56 | done
57 | done
58 | done
59 |
60 |
61 | echo '========================================================================'
62 | echo "Run each python file's __main__ tests"
63 | echo '========================================================================'
64 | cd $projectbase/otrapps
65 | for app in adium chatsecure gajim gnupg irssi jitsi kopete pidgin xchat util; do
66 | echo ''
67 | echo ''
68 | echo '------------------------------------------------------------------------'
69 | echo "Run $app's __main__ tests"
70 | echo '------------------------------------------------------------------------'
71 | python ./$app.py
72 | done
73 |
74 | echo '========================================================================'
75 | echo "Merge all test files into an app's format"
76 | echo '========================================================================'
77 | cd $projectbase
78 | for app in adium chatsecure gajim irssi jitsi kopete pidgin xchat; do
79 | echo '------------------------------------------------------------------------'
80 | echo "Merge all test files into $app's format"
81 | echo '------------------------------------------------------------------------'
82 | tests=$testbase
83 | outdir=$tmpdir/merge-into-$app
84 | mkdir $outdir
85 | copy_accounts_files $app $tests $outdir
86 | $keysync --test $tests \
87 | -i adium -i gnupg -i irssi -i jitsi -i pidgin -i xchat \
88 | -o $app \
89 | --output-folder $outdir
90 | done
91 |
92 |
93 | echo '========================================================================'
94 | echo "Convert each app to each other app"
95 | echo '========================================================================'
96 | cd $projectbase
97 | for inapp in adium chatsecure gajim gnupg irssi jitsi kopete pidgin xchat; do
98 | for outapp in adium chatsecure gajim gnupg irssi jitsi kopete pidgin xchat; do
99 | echo '------------------------------------------------------------------------'
100 | echo "Convert $inapp to $outapp "
101 | echo '------------------------------------------------------------------------'
102 | tests=$testbase
103 | outdir=$tmpdir/each-$inapp-to-$outapp
104 | mkdir $outdir
105 | copy_accounts_files $outapp $tests $outdir
106 | $keysync --test $tests --input $inapp --output $outapp --output-folder $outdir
107 | done
108 | done
109 |
110 |
111 | echo '========================================================================'
112 | echo "decrypt ChatSecure file to all apps"
113 | echo '========================================================================'
114 | cd $projectbase
115 | inapp=chatsecure
116 | for outapp in adium gajim gnupg irssi jitsi kopete pidgin xchat; do
117 | echo '------------------------------------------------------------------------'
118 | echo "Convert $inapp to $outapp "
119 | echo '------------------------------------------------------------------------'
120 | tests=$testbase
121 | outdir=$tmpdir/decrypt-$inapp-to-$outapp
122 | mkdir $outdir
123 | cp $testbase/chatsecure/otr_keystore.ofcaes $outdir/
124 | copy_accounts_files $outapp $tests $outdir
125 | echo "6QpT40Omhp6YRX73BzxnPlSvvr7ZsPP6VaS4aqWOyqE=" | \
126 | $keysync --test $tests --input $inapp --output $outapp --output-folder $outdir
127 | done
128 |
129 |
130 | echo $tmpdir
131 | ls -l $tmpdir
132 |
--------------------------------------------------------------------------------
/tests/xchat/otr.fp:
--------------------------------------------------------------------------------
1 | bigbear test@example.org IRC fffffffffffffcdfdddddeeeeee8aaaaaad00000
2 | goldy test@example.org IRC 0000111122223444455566666777778889990aab
3 | blue test@example.org IRC bbbbbbbbbb223444455566666777778889990aab smp
4 | testymctest test@example.org IRC eeeeee1122223444455566666777778889990aab manual
5 |
--------------------------------------------------------------------------------
/tests/xchat/otr.key:
--------------------------------------------------------------------------------
1 | (privkeys
2 | (account
3 | (name "test@example.org")
4 | (protocol IRC)
5 | (private-key
6 | (dsa
7 | (p #00E35B4CA8D0AED9CF4B1775A0C97789160791D9F2ED8A11EF1F120EDF0E1143232A4C336132C4D759C7A1C093EEF57E879C42BCF2E316A12D47B6189CC9F53F950172BB6D2417EDB2351F87516CFF87F3D01298D62D41607D237E8FD6B32360D2D1DA51C0D16C284F4180C6655887BFFEA7EA5755751C23F26EB8185E1C82A173#)
8 | (q #00A761D47128AD0FC90DB212364A63DD7E685D3C67#)
9 | (g #00CDCB59DF8B48DE44E1E7B3D4A18AFED11396CD7BC9BD5C9314996D48B5110D2F4BF6C3B2F0F2B47DDA2AFE6718177EB4E91E74B7C1E40F86186160337E5BA5CE763C87DAFCCA673540E3A9DF24A5A3B2CD75DB91A18B30AD14864F04B0A924556E6842FFB88932D0AE588705AC4FDABF686940934DD731917D63AF21FF19CA8B#)
10 | (y #00B074FD79609810E2B187A09D403DF8F865D99C717C9E3D0A461D0C37E8957A85B1048830965D72EC376B0C2D2567D60837D5BB6261FB6A4B9BBD6B96DDD5D4E571FBF919AE866DB6438E3D821C501B6E255E89210F0DB90EB9D435ED5B9E62F2644823608249620CEFEC9E5E80B598A1C9A1EDE8E7F1DE5DED9F3A3AD749F7F2#)
11 | (x #0DF19E6EF1D1E7D51818E085F7DDB8CB9F77CA1A#)
12 | )
13 | )
14 | )
15 | )
16 |
--------------------------------------------------------------------------------
/win32/README.md:
--------------------------------------------------------------------------------
1 | # Building On Windows
2 |
3 | Either a Windows machine or a Linux box with wine installed will do to produce a w32 binary.
4 |
5 | 
6 |
7 | ## Download Prerequisites
8 |
9 | **Automatic Download** (Recommended)
10 |
11 | 1. Execute the`fetch-and-verify.sh` script
12 | 3. Copy all the files to your windows computer
13 | 4. Download the [VS 2008 redistributable][vsredist]
14 |
15 | **Manual Download**
16 |
17 | * [Git][git]
18 | * [Python 2.7][py27] ([sig][pysig])
19 | * [Python Windows 32 Bindings][pywin32]
20 | * [PyInstaller Development Version][pyinst]
21 | * [Setuptools Bootstrap][setuptools]
22 | * [MingW Installer][mingw]
23 | * [OpenSSL-Win32][openssl]
24 | * [MS VS 2008 redistributable][vsredist]
25 | * [PyCrypto Source][pycrypto] ([sig][pycryptosig])
26 | * [Pure Python OTR Source][potr]
27 | * [Pidgin][pidgin] ([sig][pidginsig])
28 | * [Pidgin OTR Plugin][pidgin-otr] ([sig][pidgin-otrsig])
29 |
30 | **Builds on Linux**
31 |
32 | Install wine:
33 |
34 | * Debian/Ubuntu: `sudo apt-get install wine`
35 | * Fedora: `sudo yum install wine`
36 |
37 |
38 | ## Install Prerequisites
39 |
40 | ### MingW
41 |
42 | 1. Execute the installer
43 | 2. Follow the wizard
44 | 3. When prompted in the installer choose this options:
45 |
46 | ```
47 | C compiler
48 | C++ compiler
49 | Msys basic system
50 | MinGW Developer Toolkit
51 | ```
52 |
53 | ### Git
54 |
55 | 1. Execute the installer
56 | 2. Choose to add git to the PATH
57 |
58 | ### OpenSSL
59 |
60 | 1. Execute the vcredist_x86.exe package
61 | 2. Install it completely
62 | 3. Execute the OpenSSL installer
63 | 4. When prompted choose "Copy OpenSSL DLLs to: The OpenSSL binaries (/bin) directory"
64 |
65 | ### Python
66 |
67 | 1. Execute the Python installer, use default options
68 | * Linux+Wine: you must use `msiexec /i python-2.7.5.msi`
69 |
70 | ## Post Python Install Configuration
71 |
72 | **Configure the PATH environment variable**
73 |
74 | Windows:
75 |
76 | 1. Right-click on My Computer and select properties
77 | 2. Go to the 'Advanced' section and select 'Environment variables'
78 | 3. Select 'Path' from the 'System Variables' and click the 'Edit button'
79 | 4. Append the following to your PATH variable:
80 |
81 | ```
82 | C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\Python27;C:\Python27\Scripts;C:\OpenSSL-Win32\bin;
83 | ```
84 |
85 | Linux+Wine:
86 |
87 | 1. Open regedit using the `regedit` command
88 | 2. Edit `HKEY_CURRENT_USER/Environment`
89 | 3. Add a new String value called `PATH` using the value above
90 |
91 | **Set Python to use MinGW**
92 |
93 | 1. Create the following file: C:\Python27\Lib\distutils\distutils.cfg
94 | 2. Append:
95 |
96 | ```
97 | [build]
98 | compiler=mingw32
99 | ```
100 | 3. Save and close
101 |
102 | **Fix -mno-cygwin error**
103 |
104 | To prevent the following error when building Python modules, we must edit a distutils file.
105 |
106 | gcc: error: unrecognized command line option '-mno-cygwin'
107 |
108 | 1. Edit the file `C:\Python27\Lib\distutils\cygwinccompiler.py`
109 | 2. Look for the class: `Mingw32CCompiler` around line 297
110 | 3. Find the block that beings with 'self.set_executables('
111 | 4. Delete all the '-mno-cygwin' flags
112 | 5. Save and close
113 |
114 |
115 | ## Install Python Dependencies
116 |
117 | Note: your $HOME when using MinGW shell is in C:\MinGW\msys\1.0\home\USERNAME
118 |
119 | ### setuptools
120 |
121 | 1. Open your MinGW Shell
122 | * Windows: Start -> MinGW -> MinGW Shell
123 | * Linux+Wine: `wine cmd.exe /C C:\MinGW/msys/1.0/msys.bat`
124 | 2. cd to where you downloaded [ez_setup.py][setuptools]
125 | * Windows: Access the normal windows FS using the /c/ path (i.e., /c/ corresponds to C:\)
126 | * Linux+Wine: Access your native root fs with /z/
127 | 3. Execute:
128 |
129 | ```bash
130 | python ez_setup.py
131 | ```
132 |
133 | ### Python W32 Bindings
134 |
135 | 1. Execute the Python installer, use default options
136 |
137 | *Note*: If using a virtual environment (virtualenv), then you must open a
138 | command shell that has your virtualenv activated and execute:
139 |
140 | easy_install /c/path/to/pywin32-218.win32-py2.7.exe
141 |
142 | Otherwise pywin32 will be installed into the system site-packages dir, and not
143 | be available to your virtualenv.
144 |
145 |
146 | ### PyCrypto
147 |
148 | For some reason we must install PyCrypto this way, instead of relying on it to
149 | be installed via setup.py along with the rest of the dependencies.
150 |
151 | 1. cd to your extracted pycrypto dir: `cd pycrypto-2.6`
152 | 2. `python seutp.py install`
153 |
154 |
155 | ### KeySync
156 |
157 | 1. `cd keysync`
158 | 2. `python setup.py install`
159 |
160 | This will download, compile, and install all the remaining python modules.
161 |
162 | ## Verify Everything Works
163 |
164 | At this point, all the dependencies for keysync should be installed
165 | and functioning, so keysync should work. You can test it by running keysync-gui:
166 |
167 | You'll probably want to install Pidgin, pidgin-otr, and configure an account
168 | before running keysync.
169 |
170 | ```bash
171 | cd keysync
172 | python keysync-gui
173 | ```
174 | Do not proceed unless the GUI pops up and the app functions as expected.
175 |
176 | ## Create Windows Executable with PyInstaller
177 |
178 | Note: Due to [bug 651](http://www.pyinstaller.org/ticket/651) the development
179 | version of PyInstaller must be used.
180 |
181 | KeySync's setup.py script automatically installs pyinstaller, so all you need
182 | to do is execute pyinstaller and point it at the script you want to turn into
183 | an EXE. Build the package:
184 |
185 | ```bash
186 | pyinstaller --onefile keysync-gui.spec
187 | ```
188 |
189 | You can omit the `--onefile` parameter, see the [PyInstaller
190 | Manual](http://htmlpreview.github.io/?https://github.com/pyinstaller/pyinstaller/blob/develop/doc/Manual.html)
191 | for what exactly the consequences of both modes are.
192 |
193 | If the process succeeds, then check the `dist/` directory
194 | inside the pyinstaller directory.
195 |
196 | # Security Considerations
197 |
198 | Unfortunately most of the dependencies and toolchain used above are not
199 | available to download over HTTPS nor do they have signatures to verify. Even
200 | MinGW lacks any sort of secure download, so securely building the dependencies from
201 | source may not be possible.
202 |
203 | Also, according to [this ticket](http://sourceforge.net/p/pywin32/bugs/519/)
204 | the Python Win32 bindings are not easily buildable with MinGW.
205 |
206 | [git]: http://git-scm.com/download/win
207 | [py27]: http://www.python.org/ftp/python/2.7.5/python-2.7.5.msi
208 | [pysig]: http://www.python.org/ftp/python/2.7.5/python-2.7.5.msi.asc
209 | [pywin32]: http://downloads.sourceforge.net/project/pywin32/pywin32/Build%20218/pywin32-218.win32-py2.7.exe?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpyw#
210 | [pyinst]: https://github.com/pyinstaller/pyinstaller/tarball/develop
211 | [setuptools]: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
212 | [mingw]: http://downloads.sourceforge.net/project/mingw/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe?r=&use_mirror=superb#
213 | [openssl]: https://slproweb.com/download/Win32OpenSSL-1_0_1e.exe
214 | [vsredist]: http://www.microsoft.com/en-us/download/details.aspx?id=29
215 | [pycrypto]: https://pypi.python.org/packages/source/p/pycrypto/pycrypto-2.6.tar.gz#md5=88dad0a270d1fe83a39e0467a66a22bb
216 | [pycryptosig]: https://pypi.python.org/packages/source/p/pycrypto/pycrypto-2.6.tar.gz.asc
217 | [potr]: https://github.com/afflux/pure-python-otr/archive/1.0.0beta6.zip
218 | [pidgin]: http://downloads.sourceforge.net/project/pidgin/Pidgin/2.10.7/pidgin-2.10.7-offline.exe?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpidgin%2Ffil#
219 | [pidginsig]: http://downloads.sourceforge.net/project/pidgin/Pidgin/2.10.7/pidgin-2.10.7-offline.exe.asc?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpidgin%2#
220 | [pidgin-otr]: http://www.cypherpunks.ca/otr/binaries/windows/pidgin-otr-4.0.0-1.exe
221 | [pidgin-otrsig]: http://www.cypherpunks.ca/otr/binaries/windows/pidgin-otr-4.0.0-1.exe.asc
222 |
223 |
224 |
--------------------------------------------------------------------------------
/win32/cypherpunks-otr-pubkey.asc:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 | Version: SKS 1.1.0
3 |
4 | mQGiBEGuU5kRBACNWahvxEOQ1QN0+ds1ji5JR0VtAyPhOQn3m1FSexgyzvNzVClYpx/7nvli
5 | mKabImUHQRaOEln+7/lFz3aoMyHQUJaa8ftc6GwgpBCOQkk8itPUv2TgjkQb/DrCtIhGgRay
6 | tph35i8gCHlU7Dy7HPIfCrfhtDgxHAOLAJEx+qWvlwCg1sgrsDjCQ9w24m1z/zMgEeHcwsME
7 | AIqt6SqLlQqsNrj+cLyFjOLUN1u/v4HMxq2HK04qiusNye+/RwNI0suZX2hPy9NE6COfOJnP
8 | +j+Tyl22Xgeq1YFt+NJXUeV4iJ/vpT86stoC0GDNyV7MMSee0QS+S70vpOK73EQd2CH9LDks
9 | VEuEhWeUUWETs7brRFpU55WO/Fy7A/4uv/jypgnAWGdq6908MTU3PCjQ/nOYH55xKELaasAZ
10 | 3Zsqe+EYn87JTyaL2NQvguWX0zVZCzDlM1MtQizEOZbWeiOhyYCzqIVNf9Ao5SSWu2czrLx6
11 | E93kI57EezGhgOaZozZ9/l37F/pENHcu4t15JIcBD4YSdjUZqGgPSM4sY7QhT1RSIERldiBU
12 | ZWFtIDxvdHJAY3lwaGVycHVua3MuY2E+iEYEEBECAAYFAkGvc7sACgkQyyhygoBySXsh6ACg
13 | 1DGTgtga5e1ci0GBdYV4RoTC3xQAoJkIq/VjnuFtigVmHrBBu2nLW0a+iF4EExECAB4FAkGu
14 | U5kCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQ3tZOuyuofFwKRgCdHe4ozbAEpC7B67N7
15 | Dro51LJP8W0AoItbsNlIkaCp0bZIVFyrJ+ycLnoZiJwEEAEBAAYFAkGuV2QACgkQRlGJMStI
16 | 9vXhYgP/cEtYz+DBC/FWtI8TYScwZQCZh15l82lUao7fTi98++URArAUl0CyCdRnS9qjiS5a
17 | VAbG6fcbgZyGtBLF/8d4+KrfBJgyLk/I6/n5M1A9zh0E7dXjIcP6ngwD40Jrr+DhV98FirK3
18 | qWbpasWplgh9LqQbmtsd6kjbO5xeF3Wc6/OJAhwEEAECAAYFAk3mCdcACgkQUn55uqO1OZhC
19 | 3A//dRx6enzMg8J1X3x5can77DbjmxF10gk6FstNbPGqp7EyLNo00yUGO0dfGYGQexR7jXEl
20 | jGrpkTj9IFu9CNJDV4g2GL7w7hQ0L3WCUf+MVTBmWLKK3km3qh4qkmlBejg5pSt/3mBNypSE
21 | sugAw1EGlLOdCwUhMLWbPRGCM8XhHG8K33cWUaizxyGUB+XEjNIOWF/NVVAhdasmC5M2Eo5/
22 | Tx/lD08bQUVDX1o4dcwbADWXtaa6QtucJEAbs9AggiGUOpeOyEcq5NZ5NtmsTYsvvjwBLdkw
23 | gtjMdSfJe3+1oaWsEXuseu14Z7IGL6A6Deu9A/FgabNc1hx9y5XdH+uVe27t/JBmV7/o+5t8
24 | 9DdzQl0Iv9YBZn6CeknAN2UEjvISot01ijIfZsmjARE9Yib0epgIRKKXTFrgeRmNcHYZiiJP
25 | q9kdJyajXvanD+QC55SetFqJwZ/0t4MLv/xAcSx4qR5YdCh240Pb6HO8sDj8egFIsIqwLhQq
26 | 7OuaF+2Wq9tAd1hZvUZNVkHdHtTfJYCUjx50FyvWkz/mtlB90gXIUT+Wk+cojEAW/1kO3CZZ
27 | ICbO7/68p8BUeANa95uH5IfFpJZaqLipwoF+Z/PPKnUbB0rOf8F+3fFETBFM1UfHMTRm8jcw
28 | /kSx/QnJ+k7XSc6C3/rHwoUOd5Q/sCeQA5/hLJg=
29 | =cyu2
30 | -----END PGP PUBLIC KEY BLOCK-----
31 |
--------------------------------------------------------------------------------
/win32/fetch-and-verify.sh:
--------------------------------------------------------------------------------
1 | mkdir -p files/
2 | cd files/
3 |
4 | # git
5 | wget -c https://msysgit.googlecode.com/files/Git-1.8.4-preview20130916.exe
6 |
7 | # python
8 | wget -c http://www.python.org/ftp/python/2.7.5/python-2.7.5.msi
9 | wget -c http://www.python.org/ftp/python/2.7.5/python-2.7.5.msi.asc
10 | wget -c "https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py"
11 |
12 | # pywin32
13 | wget -c "http://downloads.sourceforge.net/project/pywin32/pywin32/Build%20218/pywin32-218.win32-py2.7.exe?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpywin32%2Ffiles%2Fpywin32%2FBuild%2520218%2F&use_mirror=iweb" -O pywin32-218.win32-py2.7.exe
14 |
15 | # pyinstaller
16 | wget -c https://github.com/pyinstaller/pyinstaller/tarball/develop -O pyinstaller-git.tar.gz
17 |
18 | # mingw
19 | wget -c "http://downloads.sourceforge.net/project/mingw/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe?r=&use_mirror=superb-dca3" -O mingw-get-inst-20120426.exe
20 |
21 | # openssl
22 | wget -c https://slproweb.com/download/Win32OpenSSL-1_0_1e.exe
23 |
24 | # pycrypto
25 | wget -c "https://pypi.python.org/packages/source/p/pycrypto/pycrypto-2.6.tar.gz#md5=88dad0a270d1fe83a39e0467a66a22bb" -O pycrypto-2.6.tar.gz
26 | wget -c "https://pypi.python.org/packages/source/p/pycrypto/pycrypto-2.6.tar.gz.asc"
27 |
28 | # python pure otr
29 | wget -c https://github.com/afflux/pure-python-otr/archive/1.0.0beta6.zip -O python-potr-1.0.0beta6.zip
30 |
31 | # pidgin
32 | wget -c "http://downloads.sourceforge.net/project/pidgin/Pidgin/2.10.7/pidgin-2.10.7-offline.exe.asc?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpidgin%2Ffiles%2FPidgin%2F2.10.7%2F&use_mirror=superb-dca3" -O pidgin-2.10.7-offline.exe.asc
33 | wget -c "http://downloads.sourceforge.net/project/pidgin/Pidgin/2.10.7/pidgin-2.10.7-offline.exe?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpidgin%2Ffiles%2FPidgin%2F2.10.7%2F&use_mirror=superb-dca3" -O pidgin-2.10.7-offline.exe
34 |
35 | # pidgin OTR
36 | wget -c http://www.cypherpunks.ca/otr/binaries/windows/pidgin-otr-4.0.0-1.exe
37 | wget -c http://www.cypherpunks.ca/otr/binaries/windows/pidgin-otr-4.0.0-1.exe.asc
38 |
39 | mkdir -p gpgkeys/
40 | gpg --homedir gpgkeys/ --import ../*asc
41 |
42 | for sig in `find -iname "*.asc"`; do
43 | echo "Verifying $sig"
44 | gpg --homedir gpgkeys/ --verify $sig
45 | done
46 |
--------------------------------------------------------------------------------
/win32/pidgin-windows-pubkeys.asc:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | mQINBFBqbQ8BEADJ4spu7liM5w0hPJ6kNcdp9L5lhIDChZgShPRJAZCNwx0RLk+Y
4 | pJF2nTHKn/7d/YZXU9i8n3knmZAkzM+rMe4T4TIBWrvFg3euEDXXEMNUwNQ4Dhak
5 | 4YKwYPh8DbxTQuOEZpy/qSpRXdFlfR6kwdVvywUQR6jcdEoWlDt2MgGXnwY9P+qf
6 | vgS3AJvU6FuuDUH/KEowrqUu3py4bTYppkhxxstctWRPuVeZ9Il4JNRfrVQGTlHE
7 | h8prmKGN9jW+3OiAK5B+hXigYIvBZoqPTzcoyuf1PFXLUVtQHJkMRvljvCtq0ty4
8 | zoiIwliF0rvjmoJwwFZXJYWapWt63zn3g6kId9Puj8PHe7l5MMw/rvrPzX9UEyVY
9 | lNNkqv9fMckqJnfpOhp3pFvBF19y6hkKx5Lpd1qJbUF4Kcg79Cljj2bfzbjB8REq
10 | ZlTAd639yJ0Zsz7QV7B/5cAdvpFnj1gIWi/wFlQW6MK6AxFS9ab9OTu4+qsMyrVk
11 | SxJQcwd5yLTjQcIGDDlBycuDW7c3ekRmYz7flwKn3oKQb3dyloH5jjRamRLu3QZ/
12 | h3tBMW1uhAgGDqCcJ+1VAt4D4FNeal7bSOyQOJ4Q5eYlOB37SKS8SpXNNoPkjCXk
13 | U8oPnpBumEv17615CB1zNVFe16jePzUHaHUAa6rs2EvTRu1njuckF4HuPwARAQAB
14 | tCNEYW5pZWwgQXRhbGxhaCA8ZGF0YWxsYWhAcGlkZ2luLmltPohGBBARAgAGBQJQ
15 | am/9AAoJEGumGXVpsjMZSKwAnjeJW4chLMMYp54e3umtKBFoJSwEAJ9ta2v29TNB
16 | S+UkDR62A0MWKaKKcYkBHAQQAQIABgUCUcMqzwAKCRB/60XQbNy0IhXiB/0a+fzo
17 | 1GAW9YiuuXDhBV6mCG58Ne1/kHlvsiIrKyCpr62d4D0hM+rX5U9LuZHPRntlEqKN
18 | RIIPve7/arLQIYg16TcNCVaZ/v/p5dZRUw8O9NTWw9wwOqMaPxLN0DVa7FrcwNh9
19 | 72Lh05aJRP210/05w2vVJMEdbNOyIedGxKPRlxcsHomP3XENPBXewIqu3in0BkpW
20 | Y1hilrSFDt7/hkS+dUh8e8bYfcvUtP6uVAhjhOT6XLA4mQiZWt6muyKhu6U/kC0P
21 | tvs7CnbHG7ez8iMoJTYCVHXcJP8VWTEh3dOid5JmbpMwXvuH6wFO/t3Dfj4puh+O
22 | fJivTpRk+Vdx2CwLiQI7BBMBAgAlAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIX
23 | gAUCUGputwIZAQAKCRCGcj/u3okFdM83EAC8PKmuOzLOTHQBmVjA3VU9JMTGdEql
24 | xjDWatWVGNS8vjz8FT1+DtCLIprUFbgcn2bAqDBh3FhyAQ0Nd3rwAwKW1OL2uWmo
25 | veSq6Mazpiu3tspxWcW8AdeNSEIqrK77iFyv7jDX0iZywX3BTiZfbtN8je87KCJk
26 | vRj6/ssKM2Zplt3i5CyhRRphPzmaotLIzXzY3PBthOmYYcMbs1CnVwdI/n7DwpcE
27 | zzTHbwuswcztLSoh9yhj1ksokalbDMWqOvU4UgZ4aTDfuQ427oJBgUl/Q0CFhiJe
28 | BrXMUmnnseBGfgMK4I98mMqRYikRlH44lo6aCTq9+orgSxcYE2pD3yF8W7rzukq+
29 | JinSYUFEIm5j+GNLIe1T3Jo8eKKs82bMoZIOursq54ZniVntqVZ+Um/G+Nh5kLUZ
30 | SjnU2XHh/oP+JPh64IG5LOe9zb82vIK2/bNl0Fc+h7iBTcxZgUBZsHQPFhKUi1Fx
31 | qvLE57Hv2PbLbptlvt2FDTAYwmgZYuHVZwLZsFv/UNB+F7j/6/bPly3Pujg6emBR
32 | qrtWC3GefGKSdgbuZ16IpEWgtPtDXoehwJe2NDsVlz3J3NWyCPvFnqHfvafQPwGj
33 | WZRvv1Gn7iT3lPIzq5BFO/A6nYqnUbLtWTfNLIGvndyou+etYgBOEDPlWu7uj5pf
34 | Nd2uL0ipS0RxSbQpRGFuaWVsIEF0YWxsYWggPGRhbmllbC5hdGFsbGFoQGdtYWls
35 | LmNvbT6IRgQQEQIABgUCUGpv/QAKCRBrphl1abIzGSOiAJ0SL2NsifMZUB3MLrZt
36 | hQaDq9/TqgCffJIfXuttdgEScIJWxHHYQSTuU0qJARwEEAECAAYFAlHDKs8ACgkQ
37 | f+tF0GzctCJF8gf8CQFzOmuUCRBQtqzw3FQay3XWYmEZxO4sUC0rqXy5HnTktJKH
38 | ZbNh17mNmC9Hq+IlWdhlCzBRS38qkevsBltcCHfQYUAtdCaSFVtVN8zHolt7GbRX
39 | X7/mG3sNdFkPkxwxTW2IYR59ytDeCH/Dygp61vfvObHHdP/+bMSXUkJHnK5Pr+xK
40 | GONUlVjesSbg1BvtA/m0dp7vcgCE8E7f/vkbx7WrwEo3/jKogrPyfvAHMJj5XRlZ
41 | /Cb6KbFZUsVsY4Bb1dBLOX0OwO/t56Q3Ts+070QweNh6rL7HWTvh9Tvac92lTaMr
42 | OQ1lU7TqD0ppQeCYLzFA7knhnmx5y+rz1e8IiYkCOAQTAQIAIgUCUGpuQAIbAwYL
43 | CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQhnI/7t6JBXTAtg//e9w5WT5VVLLa
44 | pXjDjVt1KoswvJTsbQujZ3+LWr8CaVYzmwpgyvBQlGRuE01xA5QIQUmj55p4qJP5
45 | 9DI63rVmYoyOYaHkQ4SiPBcyLVbsL2L2AUFvqB9KvHh62LXh5nSuSC/bcTA9PCCb
46 | iUe6YChbFgZIgEyJOFG8A0qIoVPVCbmoqNKvT72+8Nk3Ndho92ehWS8aahjTEUl2
47 | +LoNovdmfzlnw5zY/cSTCZoZXeENWOfnQ7Cfy8L3eJi/Ewfhp9swlpmvQCrGJOiU
48 | ZYANHGkk3rKSkuRCYrOPKVEGuH+WZAp7KfRO+1kFAJEhoYK+eHllNyoVwnMSXL2G
49 | J0iyukorWip90wc3dCqDGIFLymU+V0LN81i4y2u7i2OcSgmeT+lXYeYJhxvjMbZu
50 | fJ8Vc8ciWvcFl2q4XYNDdAc2WBsE1fwO1CGcz1lgU9ZoUwkuKu1HUldchEjZm8iu
51 | 4I6Bonri6rMsnNa9VPgnZprRtA+ntckLkyRtpZOc4rk/uHz13UAt27a5wGOa3iy+
52 | TbOOSXA5Dq5XxZN5eqSuHxK+tbcG8lP2po2FBrd0uZ4X+eC5+NCiqYnEZJiCsLhO
53 | wBuJJ67AZ07jDDY3Ju51w9321fFJOm1YbSBdnQfge4PmLWJ1/7FmwQHqLYBLrc2l
54 | bgkVpRsLXbzUcOl69oblK0o6qf1bbaK0KURhbmllbCBBdGFsbGFoIDxkYW5pZWxf
55 | YXRhbGxhaEB5YWhvby5jb20+iEYEEBECAAYFAlBqb/0ACgkQa6YZdWmyMxkVfgCg
56 | iuI9umRQde5MpEU399FK6Sb16AAAn0JW9fEZ6p14OS4bxQ8E19ArF2wGiQEcBBAB
57 | AgAGBQJRwyrPAAoJEH/rRdBs3LQiabQH+wfhELiER+vj3KLazm+K9tQNgwRuOmBp
58 | ieRxgZg64WNXzaAL+8ERbxzGrX7e0bvi5e4bXyU/tudgMiQULVO4lSmogBMKKvvV
59 | r5KzWR9Zl3jXYdBdVZJouwcguCm85yLipgCz9opTeI6LEuWryJ/+GLNSAv29RMGK
60 | D9a274NqWfknGtCUAWv8E1gFECi+YzKrU77IYvUsBuHNCr39IsWE8GdATo9Afegc
61 | +BIigksGTH3MnDiS8G3IrQxMTrQN0YcXr/4eh7rWPdjbmDyGE7wL6Fchxa0hodpm
62 | OZKSqWaWb6225/huCbStfuWRPTH8zCJJwy2+IIorNqXrvwgAuoJWAvyJAjgEEwEC
63 | ACIFAlBqblwCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEIZyP+7eiQV0
64 | JO4P/35fs1y5/i6VPpMJ75Yp69TXn7z0LEEsh2J3N9ssSVkKNwHUvohYQovZDtcz
65 | scznSdpWcUN+aNf8vQ5j63Ifc4W3N/1fdmltMYXYQr1GhfVlyRE7o1AizzyjIQvq
66 | bKvzku7nC1xnWvhJ9JwDXSeqSnaG5E9qNv23jBSckJD7I7Gg7DkleIegHd+DAUAN
67 | jPSMEZM9LuaV7SNLW+2yNMDj0N46ZTq8X524BRpz/XpL8FP3tgXG3DEsaVlDhTJg
68 | GCB/rw+lkbcpbe40yDJaaojUe1VjUelq4RyiyJBIgTN/uFFbsQeS77C405QsvfFv
69 | eWtHIXhTDyrYCOv1QHDBeACbJBOoK50C37xLt5nO/mrq8CSZg40mp7FgtOXmA9S0
70 | LztbM5WbUPkZNw+G7aFexG1eIo4UgQ60eOGd7TvlJgVpR5fW3WxrINKmELbVrivZ
71 | zsCbFpcV0wFrZKQvGDhqrEqky1p3qDvSNWTTPXtSb2Ip3Mo+PlCODxvFGuuD1ccd
72 | XZi2Zx0nptcr8JW7Nni8SyZFGXNlUvh72W5Whw3OYYHyVx7OgAuATPLPeEMNpsli
73 | PPHS+jgdKEqCewKCf0LbF02KqqdUqM6rNbH3DvnGKnxDTh5trfqFexAJFDzvrC95
74 | aOY/Uc6UKdSQTVwGsR3rU+UbjtVbVaUmK/Cx2ut6VG9e4ea/uQINBFBqbQ8BEACt
75 | oK9RWmFiz0xX4/Ef4f7BfqUY/RMaS5BAlww2hkiaDNODkEtoQcJin3qcMbjczzPA
76 | 36YiRB4uDQYhQcOA6L/GEGVwwrqi4vM05ILCYNU9YfVnLUjzkKx24xWRJMrEASRy
77 | p2mAq3j1P7jCIm3QBnISheZcfW6mQJB7az3Cpbtt9GlrYk9Z1m9EEbNSt0uF70yQ
78 | CwA2LlDhPVsDOGNuYn2ia+ij50QRh/0A2t9FKLQVO5B7QCFcGuRa27xVPP8nRxdy
79 | mvT3L6Me78mtTGso1bGqsTQHHiTdNDiocozUD73qqtPgwQ/evUhJPxGqGCrzaLxP
80 | m8LfpKtIXylmIp/PAXUOQ7Dv1y3El2Xsck0DCxLdb0GiTUeGDGFQX6G0CUuRLqhf
81 | u7VvpQuzKuNw0dZHb3mKaXBCSzvUajM5kWwqMT1SBNsvXKCqGRhFXP5C+c+tMmca
82 | CTB2ES6zpKtKq8zyxT+YfGkeyhObLrA9ul4Wus/gNImUL4lmCDbk8qfI0DUbPQLp
83 | IMn2KS+qSiSnIcf1JJGdSTeprYiQtescY2hasrN7l24BwmOCo7ba2v16P/0n5tVO
84 | wSSMDY0HLE1YmBuKj7T1g71pOumlSLwU7p/Xi1Ae0qHIgmsPfWCwTR+L7S47ZtS/
85 | P4m+vYVLRtavLEWRJL08Lr6mIkU2FIaczxLDEzrx5QARAQABiQIfBBgBAgAJBQJQ
86 | am0PAhsMAAoJEIZyP+7eiQV0iJ4P/0dR1gN/VS7RvGTn30XlsszfhZCpXYX7Gt1d
87 | QBPITeZQyXTckWpCCxky1xy4xWGBvglUZzpcANJ8aql3V3ARGSmOGIgiJE7OftOj
88 | 7HmsukDr91PREDrFwbsAvSatuiNx5J6GA9tsOm1vMF2Q8E0y46Gh6gdgzHO6eKWL
89 | KjC/wIevDWzRJ5nLKrNwGrubbJp6c7Gp3DtwQgpdN4mxQtZrcwyhuWlzdGu7ZT2I
90 | /AhV65rgSyxLJr4LDBkwaiZy2ypqGhyArkP09hu6BtUNsqAt+dPT8yTV8Gext8kk
91 | tZAaH5RyWAsBgiD2TZRcPobFDYUk/RkNbpA33orAXs68hYfJHXrMhmXzp7/4RLVG
92 | MD4rBLYUFRzaDZR5doLAOEMPZQyzO2Ad6nX5usONrU3Dkn5mZEnNzNCmCpRo2GS2
93 | WrKoDHhIV7ljYm9lFWXKgMS1Buc8ya9p1LmmcKdba6ZqenMcfAss9GtNA9qeDXTw
94 | mMMNRfkpbAhiISuamZdDMHs7VLTnvtBWHIiKs0ojxJt8PUuVzl38JY+A79zbZJn6
95 | cZ451IufJFeK6uQG28trhXZIrDnbi/OXDX4y7a2k5P/kY75CEtZHAFryeYxwbIri
96 | QCmUUmsHX16NEEHk551fqRnfjCTo+Jgn+fYQSYt9JuC+bA9JKh5vqtIQgE+Kbpxb
97 | Un75lhEA
98 | =1DUj
99 | -----END PGP PUBLIC KEY BLOCK-----
100 |
101 |
--------------------------------------------------------------------------------
/win32/w32-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guardianproject/keysync/96990dc739a421b6072cc63da94d916e56941acf/win32/w32-screenshot.png
--------------------------------------------------------------------------------