__main__.py 13.3 KB
Newer Older
1
2
#! /usr/bin/env python

3
4
from __future__ import print_function

5
6
7
8
9
10
11
12
import argparse
import json
import os
import sys
import yaml

from . import *

13
14
15
16
17
def read_base64_file(filename):
    """Read a base64 file, dropping any CR/LF characters"""
    with open(filename, "rb") as f:
        return f.read().translate(None, "\r\n")

18
def build_arg_parser():
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
    parser = argparse.ArgumentParser()
    parser.add_argument("--key", help="Account encryption key", default="")
    commands = parser.add_subparsers()

    create_account = commands.add_parser("create_account", help="Create a new account")
    create_account.add_argument("account_file", help="Local account file")

    def do_create_account(args):
        if os.path.exists(args.account_file):
            sys.stderr.write("Account %r file already exists" % (
                args.account_file,
            ))
            sys.exit(1)
        account = Account()
        account.create()
        with open(args.account_file, "wb") as f:
            f.write(account.pickle(args.key))

    create_account.set_defaults(func=do_create_account)

    keys = commands.add_parser("keys", help="List public keys for an account")
    keys.add_argument("account_file", help="Local account file")
    keys.add_argument("--json", action="store_true", help="Output as JSON")

    def do_keys(args):
        account = Account()
45
        account.unpickle(args.key, read_base64_file(args.account_file))
46
47
48
49
50
51
52
53
54
55
56
57
58
59
        result = {
            "account_keys": account.identity_keys(),
            "one_time_keys": account.one_time_keys(),
        }
        try:
            if args.json:
                json.dump(result, sys.stdout, indent=4)
            else:
                yaml.safe_dump(result, sys.stdout, default_flow_style=False)
        except:
            pass

    keys.set_defaults(func=do_keys)

60
61
    def do_id_key(args):
        account = Account()
62
        account.unpickle(args.key, read_base64_file(args.account_file))
63
64
65
66
67
68
69
70
        print(account.identity_keys()['curve25519'])

    id_key = commands.add_parser("identity_key", help="Get the identity key for an account")
    id_key.add_argument("account_file", help="Local account file")
    id_key.set_defaults(func=do_id_key)

    def do_one_time_key(args):
        account = Account()
71
        account.unpickle(args.key, read_base64_file(args.account_file))
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
        keys = account.one_time_keys()['curve25519'].values()
        key_num = args.key_num
        if key_num < 1 or key_num > len(keys):
            print(
                "Invalid key number %i: %i keys available" %
                   (key_num, len(keys)),
                file=sys.stderr
            )
            sys.exit(1)
        print (keys[key_num-1])

    one_time_key = commands.add_parser("one_time_key",
                                       help="Get a one-time key for the account")
    one_time_key.add_argument("account_file", help="Local account file")
    one_time_key.add_argument("--key-num", "-n", type=int, default=1,
                              help="Index of key to retrieve (default: 1)")
    one_time_key.set_defaults(func=do_one_time_key)


91
92
93
94
95
96
97
    sign = commands.add_parser("sign", help="Sign a message")
    sign.add_argument("account_file", help="Local account file")
    sign.add_argument("message_file", help="Message to sign")
    sign.add_argument("signature_file", help="Signature to output")

    def do_sign(args):
        account = Account()
98
        account.unpickle(args.key, read_base64_file(args.account_file))
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
        with open_in(args.message_file) as f:
             message = f.read()
        signature = account.sign(message)
        with open_out(args.signature_file) as f:
             f.write(signature)

    sign.set_defaults(func=do_sign)


    generate_keys = commands.add_parser("generate_keys", help="Generate one time keys")
    generate_keys.add_argument("account_file", help="Local account file")
    generate_keys.add_argument("count", type=int, help="Number of keys to generate")

    def do_generate_keys(args):
        account = Account()
114
        account.unpickle(args.key, read_base64_file(args.account_file))
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
        account.generate_one_time_keys(args.count)
        with open(args.account_file, "wb") as f:
            f.write(account.pickle(args.key))

    generate_keys.set_defaults(func=do_generate_keys)


    outbound = commands.add_parser("outbound", help="Create an outbound session")
    outbound.add_argument("account_file", help="Local account file")
    outbound.add_argument("session_file", help="Local session file")
    outbound.add_argument("identity_key", help="Remote identity key")
    outbound.add_argument("one_time_key", help="Remote one time key")

    def do_outbound(args):
        if os.path.exists(args.session_file):
            sys.stderr.write("Session %r file already exists" % (
                args.session_file,
            ))
            sys.exit(1)
        account = Account()
135
        account.unpickle(args.key, read_base64_file(args.account_file))
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        session = Session()
        session.create_outbound(
            account, args.identity_key, args.one_time_key
        )
        with open(args.session_file, "wb") as f:
            f.write(session.pickle(args.key))

    outbound.set_defaults(func=do_outbound)

    def open_in(path):
        if path == "-":
            return sys.stdin
        else:
            return open(path, "rb")

    def open_out(path):
        if path == "-":
            return sys.stdout
        else:
            return open(path, "wb")

    inbound = commands.add_parser("inbound", help="Create an inbound session")
    inbound.add_argument("account_file", help="Local account file")
    inbound.add_argument("session_file", help="Local session file")
    inbound.add_argument("message_file", help="Message", default="-")
    inbound.add_argument("plaintext_file", help="Plaintext", default="-")

    def do_inbound(args):
        if os.path.exists(args.session_file):
            sys.stderr.write("Session %r file already exists" % (
                args.session_file,
            ))
            sys.exit(1)
        account = Account()
170
        account.unpickle(args.key, read_base64_file(args.account_file))
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
        with open_in(args.message_file) as f:
            message_type = f.read(8)
            message = f.read()
        if message_type != "PRE_KEY ":
            sys.stderr.write("Expecting a PRE_KEY message")
            sys.exit(1)
        session = Session()
        session.create_inbound(account, message)
        plaintext = session.decrypt(0, message)
        with open(args.session_file, "wb") as f:
            f.write(session.pickle(args.key))
        with open_out(args.plaintext_file) as f:
            f.write(plaintext)

    inbound.set_defaults(func=do_inbound)

    session_id = commands.add_parser("session_id", help="Session ID")
    session_id.add_argument("session_file", help="Local session file")

    def do_session_id(args):
        session = Session()
192
        session.unpickle(args.key, read_base64_file(args.session_file))
193
194
195
196
197
198
199
200
201
202
203
        sys.stdout.write(session.session_id() + "\n")

    session_id.set_defaults(func=do_session_id)

    encrypt = commands.add_parser("encrypt", help="Encrypt a message")
    encrypt.add_argument("session_file", help="Local session file")
    encrypt.add_argument("plaintext_file", help="Plaintext", default="-")
    encrypt.add_argument("message_file", help="Message", default="-")

    def do_encrypt(args):
        session = Session()
204
        session.unpickle(args.key, read_base64_file(args.session_file))
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
        with open_in(args.plaintext_file) as f:
            plaintext = f.read()
        message_type, message = session.encrypt(plaintext)
        with open(args.session_file, "wb") as f:
            f.write(session.pickle(args.key))
        with open_out(args.message_file) as f:
            f.write(["PRE_KEY ", "MESSAGE "][message_type])
            f.write(message)

    encrypt.set_defaults(func=do_encrypt)

    decrypt = commands.add_parser("decrypt", help="Decrypt a message")
    decrypt.add_argument("session_file", help="Local session file")
    decrypt.add_argument("message_file", help="Message", default="-")
    decrypt.add_argument("plaintext_file", help="Plaintext", default="-")

    def do_decrypt(args):
        session = Session()
223
        session.unpickle(args.key, read_base64_file(args.session_file))
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
        with open_in(args.message_file) as f:
            message_type = f.read(8)
            message = f.read()
        if message_type not in {"PRE_KEY ", "MESSAGE "}:
            sys.stderr.write("Expecting a PRE_KEY or MESSAGE message")
            sys.exit(1)
        message_type = 1 if message_type == "MESSAGE " else 0
        plaintext = session.decrypt(message_type, message)
        with open(args.session_file, "wb") as f:
            f.write(session.pickle(args.key))
        with open_out(args.plaintext_file) as f:
            f.write(plaintext)

    decrypt.set_defaults(func=do_decrypt)

239
240
241
242
    outbound_group = commands.add_parser("outbound_group", help="Create an outbound group session")
    outbound_group.add_argument("session_file", help="Local group session file")
    outbound_group.set_defaults(func=do_outbound_group)

243
244
245
246
247
248
249
    group_credentials = commands.add_parser("group_credentials", help="Export the current outbound group session credentials")
    group_credentials.add_argument("session_file", help="Local outbound group session file")
    group_credentials.add_argument("credentials_file", help="File to write credentials to (default stdout)",
                                   type=argparse.FileType('w'), nargs='?',
                                   default=sys.stdout)
    group_credentials.set_defaults(func=do_group_credentials)

250
    group_encrypt = commands.add_parser("group_encrypt", help="Encrypt a group message")
251
252
253
254
255
256
257
    group_encrypt.add_argument("session_file", help="Local outbound group session file")
    group_encrypt.add_argument("plaintext_file", help="Plaintext file (default stdin)",
                               type=argparse.FileType('rb'), nargs='?',
                               default=sys.stdin)
    group_encrypt.add_argument("message_file", help="Message file (default stdout)",
                               type=argparse.FileType('w'), nargs='?',
                               default=sys.stdout)
258
259
    group_encrypt.set_defaults(func=do_group_encrypt)

260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
    inbound_group = commands.add_parser(
        "inbound_group",
        help=("Create an inbound group session based on credentials from an "+
              "outbound group session"))
    inbound_group.add_argument("session_file", help="Local inbound group session file")
    inbound_group.add_argument("credentials_file",
                               help="File to read credentials from (default stdin)",
                               type=argparse.FileType('r'), nargs='?',
                               default=sys.stdin)
    inbound_group.set_defaults(func=do_inbound_group)

    group_decrypt = commands.add_parser("group_decrypt", help="Decrypt a group message")
    group_decrypt.add_argument("session_file", help="Local inbound group session file")
    group_decrypt.add_argument("message_file", help="Message file (default stdin)",
                               type=argparse.FileType('r'), nargs='?',
                               default=sys.stdin)
    group_decrypt.add_argument("plaintext_file", help="Plaintext file (default stdout)",
                               type=argparse.FileType('wb'), nargs='?',
                               default=sys.stdout)
    group_decrypt.set_defaults(func=do_group_decrypt)
280
281
282
283
284
285
286
287
288
289
290
291
292
293
    return parser

def do_outbound_group(args):
    if os.path.exists(args.session_file):
        sys.stderr.write("Session %r file already exists" % (
            args.session_file,
        ))
        sys.exit(1)
    session = OutboundGroupSession()
    with open(args.session_file, "wb") as f:
            f.write(session.pickle(args.key))

def do_group_encrypt(args):
    session = OutboundGroupSession()
294
    session.unpickle(args.key, read_base64_file(args.session_file))
295
296
297
298
299
300
    plaintext = args.plaintext_file.read()
    message = session.encrypt(plaintext)
    with open(args.session_file, "wb") as f:
        f.write(session.pickle(args.key))
    args.message_file.write(message)

301
302
def do_group_credentials(args):
    session = OutboundGroupSession()
303
    session.unpickle(args.key, read_base64_file(args.session_file))
304
305
306
307
308
309
310
311
312
313
314
315
316
    result = {
        'message_index': session.message_index(),
        'session_key': session.session_key(),
    }
    json.dump(result, args.credentials_file, indent=4)

def do_inbound_group(args):
    if os.path.exists(args.session_file):
        sys.stderr.write("Session %r file already exists\n" % (
            args.session_file,
        ))
        sys.exit(1)
    credentials = json.load(args.credentials_file)
317
    for k in ('session_key', ):
318
319
320
321
322
        if not k in credentials:
            sys.stderr.write("Credentials file is missing %s\n" % k)
            sys.exit(1);

    session = InboundGroupSession()
323
    session.init(credentials['session_key'])
324
325
326
327
328
    with open(args.session_file, "wb") as f:
        f.write(session.pickle(args.key))

def do_group_decrypt(args):
    session = InboundGroupSession()
329
    session.unpickle(args.key, read_base64_file(args.session_file))
330
    message = args.message_file.read()
331
    plaintext, message_index = session.decrypt(message)
332
333
334
335
    with open(args.session_file, "wb") as f:
        f.write(session.pickle(args.key))
    args.plaintext_file.write(plaintext)

336
337
if __name__ == '__main__':
    parser = build_arg_parser()
338
339
    args = parser.parse_args()
    args.func(args)