xref: /trunk/test/odf-validate/decrypt.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
1*0e16a31dSJim Jagielski#**************************************************************
2*0e16a31dSJim Jagielski#
3*0e16a31dSJim Jagielski#  Licensed to the Apache Software Foundation (ASF) under one
4*0e16a31dSJim Jagielski#  or more contributor license agreements.  See the NOTICE file
5*0e16a31dSJim Jagielski#  distributed with this work for additional information
6*0e16a31dSJim Jagielski#  regarding copyright ownership.  The ASF licenses this file
7*0e16a31dSJim Jagielski#  to you under the Apache License, Version 2.0 (the
8*0e16a31dSJim Jagielski#  "License"); you may not use this file except in compliance
9*0e16a31dSJim Jagielski#  with the License.  You may obtain a copy of the License at
10*0e16a31dSJim Jagielski#
11*0e16a31dSJim Jagielski#    http://www.apache.org/licenses/LICENSE-2.0
12*0e16a31dSJim Jagielski#
13*0e16a31dSJim Jagielski#  Unless required by applicable law or agreed to in writing,
14*0e16a31dSJim Jagielski#  software distributed under the License is distributed on an
15*0e16a31dSJim Jagielski#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16*0e16a31dSJim Jagielski#  KIND, either express or implied.  See the License for the
17*0e16a31dSJim Jagielski#  specific language governing permissions and limitations
18*0e16a31dSJim Jagielski#  under the License.
19*0e16a31dSJim Jagielski#
20*0e16a31dSJim Jagielski#**************************************************************
21*0e16a31dSJim Jagielski
22*0e16a31dSJim Jagielski# usage: decrypt.py <in-dir> <out-dir> <password>      (decrypt.py --selftest runs the cipher known-answer tests)
23*0e16a31dSJim Jagielski# Writes a plain copy of every package in <in-dir> into <out-dir> for the schema checkers: encrypted
24*0e16a31dSJim Jagielski# streams are decrypted and inflated, the manifest loses its encryption-data elements and the manifest:size
25*0e16a31dSJim Jagielski# of those entries. Do not run pkgcheck.py on the copies. A checksum mismatch (wrong password) or any other
26*0e16a31dSJim Jagielski# failure writes nothing for that package and makes the exit status 1.
27*0e16a31dSJim Jagielski# Implements what main/package writes and reads: PBKDF2-HMAC-SHA1 over the start key (SHA1 or SHA256 of the
28*0e16a31dSJim Jagielski# UTF-8 password), Blowfish with 64 bit feedback (rtl_cipher_CFB over 8 byte blocks) or AES-CBC with W3C padding.
29*0e16a31dSJim Jagielskiimport sys, os, io, re, zlib, zipfile, hashlib, base64, struct
30*0e16a31dSJim Jagielskifrom xml.etree import ElementTree as ET
31*0e16a31dSJim Jagielski
32*0e16a31dSJim JagielskiM = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
33*0e16a31dSJim JagielskiQ = "{%s}" % M
34*0e16a31dSJim JagielskiET.register_namespace("manifest", M)
35*0e16a31dSJim JagielskiMASK = 0xFFFFFFFF
36*0e16a31dSJim Jagielski
37*0e16a31dSJim Jagielski
38*0e16a31dSJim Jagielskiclass DecryptError(Exception):
39*0e16a31dSJim Jagielski    pass
40*0e16a31dSJim Jagielski
41*0e16a31dSJim Jagielski
42*0e16a31dSJim Jagielskidef pi_hex_words(n):
43*0e16a31dSJim Jagielski    bits = 32 * n + 64
44*0e16a31dSJim Jagielski    one = 1 << (bits + 16)
45*0e16a31dSJim Jagielski
46*0e16a31dSJim Jagielski    def arctan_inv(x):
47*0e16a31dSJim Jagielski        x2, term, total, k = x * x, one // x, 0, 0
48*0e16a31dSJim Jagielski        while term:
49*0e16a31dSJim Jagielski            total += term // (2 * k + 1) if k % 2 == 0 else -(term // (2 * k + 1))
50*0e16a31dSJim Jagielski            term //= x2
51*0e16a31dSJim Jagielski            k += 1
52*0e16a31dSJim Jagielski        return total
53*0e16a31dSJim Jagielski    pi = 16 * arctan_inv(5) - 4 * arctan_inv(239)
54*0e16a31dSJim Jagielski    frac = (pi - (3 * one)) >> 16
55*0e16a31dSJim Jagielski    frac >>= 64
56*0e16a31dSJim Jagielski    return [(frac >> (32 * (n - 1 - i))) & MASK for i in range(n)]
57*0e16a31dSJim Jagielski
58*0e16a31dSJim Jagielski
59*0e16a31dSJim Jagielski_BF_INIT = []
60*0e16a31dSJim Jagielski
61*0e16a31dSJim Jagielski
62*0e16a31dSJim Jagielskiclass Blowfish:
63*0e16a31dSJim Jagielski    def __init__(self, key):
64*0e16a31dSJim Jagielski        if not _BF_INIT:
65*0e16a31dSJim Jagielski            _BF_INIT.extend(pi_hex_words(18 + 1024))
66*0e16a31dSJim Jagielski            assert _BF_INIT[0] == 0x243F6A88 and _BF_INIT[18] == 0xD1310BA6 and _BF_INIT[-1] == 0x3AC372E6
67*0e16a31dSJim Jagielski        self.P = _BF_INIT[:18]
68*0e16a31dSJim Jagielski        self.S = [_BF_INIT[18 + 256 * i:18 + 256 * (i + 1)] for i in range(4)]
69*0e16a31dSJim Jagielski        k = 0
70*0e16a31dSJim Jagielski        for i in range(18):
71*0e16a31dSJim Jagielski            d = 0
72*0e16a31dSJim Jagielski            for _ in range(4):
73*0e16a31dSJim Jagielski                d = (d << 8) | key[k]
74*0e16a31dSJim Jagielski                k = (k + 1) % len(key)
75*0e16a31dSJim Jagielski            self.P[i] ^= d
76*0e16a31dSJim Jagielski        l = r = 0
77*0e16a31dSJim Jagielski        for i in range(0, 18, 2):
78*0e16a31dSJim Jagielski            l, r = self.encrypt(l, r)
79*0e16a31dSJim Jagielski            self.P[i], self.P[i + 1] = l, r
80*0e16a31dSJim Jagielski        for i in range(4):
81*0e16a31dSJim Jagielski            for j in range(0, 256, 2):
82*0e16a31dSJim Jagielski                l, r = self.encrypt(l, r)
83*0e16a31dSJim Jagielski                self.S[i][j], self.S[i][j + 1] = l, r
84*0e16a31dSJim Jagielski
85*0e16a31dSJim Jagielski    def encrypt(self, l, r):
86*0e16a31dSJim Jagielski        P, S0, S1, S2, S3 = self.P, *self.S
87*0e16a31dSJim Jagielski        for i in range(16):
88*0e16a31dSJim Jagielski            l ^= P[i]
89*0e16a31dSJim Jagielski            r ^= ((((S0[l >> 24] + S1[(l >> 16) & 255]) & MASK) ^ S2[(l >> 8) & 255]) + S3[l & 255]) & MASK
90*0e16a31dSJim Jagielski            l, r = r, l
91*0e16a31dSJim Jagielski        l, r = r, l
92*0e16a31dSJim Jagielski        return l ^ P[17], r ^ P[16]
93*0e16a31dSJim Jagielski
94*0e16a31dSJim Jagielski    def encrypt_block(self, b):
95*0e16a31dSJim Jagielski        return struct.pack(">II", *self.encrypt(*struct.unpack(">II", b)))
96*0e16a31dSJim Jagielski
97*0e16a31dSJim Jagielski
98*0e16a31dSJim Jagielskidef blowfish_cfb64_decrypt(key, iv, data):
99*0e16a31dSJim Jagielski    bf = Blowfish(key)
100*0e16a31dSJim Jagielski    reg = (iv + bytes(8))[:8]
101*0e16a31dSJim Jagielski    out = bytearray()
102*0e16a31dSJim Jagielski    for i in range(0, len(data), 8):
103*0e16a31dSJim Jagielski        ct = data[i:i + 8]
104*0e16a31dSJim Jagielski        ks = bf.encrypt_block(reg)
105*0e16a31dSJim Jagielski        out += bytes(a ^ b for a, b in zip(ct, ks))
106*0e16a31dSJim Jagielski        reg = ct
107*0e16a31dSJim Jagielski    return bytes(out)
108*0e16a31dSJim Jagielski
109*0e16a31dSJim Jagielski
110*0e16a31dSJim Jagielskidef _aes_tables():
111*0e16a31dSJim Jagielski    def xt(a):
112*0e16a31dSJim Jagielski        a <<= 1
113*0e16a31dSJim Jagielski        return (a ^ 0x11B) & 255 if a & 256 else a
114*0e16a31dSJim Jagielski
115*0e16a31dSJim Jagielski    def mul(a, b):
116*0e16a31dSJim Jagielski        r = 0
117*0e16a31dSJim Jagielski        while b:
118*0e16a31dSJim Jagielski            if b & 1:
119*0e16a31dSJim Jagielski                r ^= a
120*0e16a31dSJim Jagielski            a, b = xt(a), b >> 1
121*0e16a31dSJim Jagielski        return r
122*0e16a31dSJim Jagielski    inv = [0] * 256
123*0e16a31dSJim Jagielski    for a in range(1, 256):
124*0e16a31dSJim Jagielski        for b in range(1, 256):
125*0e16a31dSJim Jagielski            if mul(a, b) == 1:
126*0e16a31dSJim Jagielski                inv[a] = b
127*0e16a31dSJim Jagielski                break
128*0e16a31dSJim Jagielski    sbox = []
129*0e16a31dSJim Jagielski    for a in range(256):
130*0e16a31dSJim Jagielski        x = inv[a]
131*0e16a31dSJim Jagielski        s = x ^ (x << 1 | x >> 7) & 255 ^ (x << 2 | x >> 6) & 255 ^ (x << 3 | x >> 5) & 255 ^ (x << 4 | x >> 4) & 255 ^ 0x63
132*0e16a31dSJim Jagielski        sbox.append(s & 255)
133*0e16a31dSJim Jagielski    isbox = [0] * 256
134*0e16a31dSJim Jagielski    for i, s in enumerate(sbox):
135*0e16a31dSJim Jagielski        isbox[s] = i
136*0e16a31dSJim Jagielski    return sbox, isbox, {n: [mul(a, n) for a in range(256)] for n in (2, 9, 11, 13, 14)}
137*0e16a31dSJim Jagielski
138*0e16a31dSJim Jagielski
139*0e16a31dSJim Jagielski_AES = []
140*0e16a31dSJim Jagielski
141*0e16a31dSJim Jagielski
142*0e16a31dSJim Jagielskidef aes_schedule(key):
143*0e16a31dSJim Jagielski    if not _AES:
144*0e16a31dSJim Jagielski        _AES.extend(_aes_tables())
145*0e16a31dSJim Jagielski    sbox, mt = _AES[0], _AES[2]
146*0e16a31dSJim Jagielski    if len(key) not in (16, 24, 32):
147*0e16a31dSJim Jagielski        raise DecryptError("AES key size %d" % len(key))
148*0e16a31dSJim Jagielski    nk = len(key) // 4
149*0e16a31dSJim Jagielski    nr = nk + 6
150*0e16a31dSJim Jagielski    w = [list(key[4 * i:4 * i + 4]) for i in range(nk)]
151*0e16a31dSJim Jagielski    rcon = 1
152*0e16a31dSJim Jagielski    for i in range(nk, 4 * (nr + 1)):
153*0e16a31dSJim Jagielski        t = list(w[i - 1])
154*0e16a31dSJim Jagielski        if i % nk == 0:
155*0e16a31dSJim Jagielski            t = [sbox[b] for b in t[1:] + t[:1]]
156*0e16a31dSJim Jagielski            t[0] ^= rcon
157*0e16a31dSJim Jagielski            rcon = mt[2][rcon]
158*0e16a31dSJim Jagielski        elif nk > 6 and i % nk == 4:
159*0e16a31dSJim Jagielski            t = [sbox[b] for b in t]
160*0e16a31dSJim Jagielski        w.append([a ^ b for a, b in zip(w[i - nk], t)])
161*0e16a31dSJim Jagielski    return [sum(w[4 * r:4 * r + 4], []) for r in range(nr + 1)]
162*0e16a31dSJim Jagielski
163*0e16a31dSJim Jagielski
164*0e16a31dSJim Jagielskidef aes_decrypt_block(rk, blk):
165*0e16a31dSJim Jagielski    isbox, mt = _AES[1], _AES[2]
166*0e16a31dSJim Jagielski    m9, m11, m13, m14 = mt[9], mt[11], mt[13], mt[14]
167*0e16a31dSJim Jagielski    nr = len(rk) - 1
168*0e16a31dSJim Jagielski    s = [a ^ b for a, b in zip(blk, rk[nr])]
169*0e16a31dSJim Jagielski    for r in range(nr - 1, -1, -1):
170*0e16a31dSJim Jagielski        s = [s[0], s[13], s[10], s[7], s[4], s[1], s[14], s[11], s[8], s[5], s[2], s[15], s[12], s[9], s[6], s[3]]
171*0e16a31dSJim Jagielski        s = [isbox[b] ^ k for b, k in zip(s, rk[r])]
172*0e16a31dSJim Jagielski        if r:
173*0e16a31dSJim Jagielski            t = []
174*0e16a31dSJim Jagielski            for c in range(0, 16, 4):
175*0e16a31dSJim Jagielski                a0, a1, a2, a3 = s[c:c + 4]
176*0e16a31dSJim Jagielski                t += [m14[a0] ^ m11[a1] ^ m13[a2] ^ m9[a3], m9[a0] ^ m14[a1] ^ m11[a2] ^ m13[a3],
177*0e16a31dSJim Jagielski                      m13[a0] ^ m9[a1] ^ m14[a2] ^ m11[a3], m11[a0] ^ m13[a1] ^ m9[a2] ^ m14[a3]]
178*0e16a31dSJim Jagielski            s = t
179*0e16a31dSJim Jagielski    return bytes(s)
180*0e16a31dSJim Jagielski
181*0e16a31dSJim Jagielski
182*0e16a31dSJim Jagielskidef aes_cbc_decrypt(key, iv, data):
183*0e16a31dSJim Jagielski    rk = aes_schedule(key)
184*0e16a31dSJim Jagielski    if len(data) % 16 or not data:
185*0e16a31dSJim Jagielski        raise DecryptError("AES data is not a multiple of 16 bytes")
186*0e16a31dSJim Jagielski    out, prev = bytearray(), iv
187*0e16a31dSJim Jagielski    for off in range(0, len(data), 16):
188*0e16a31dSJim Jagielski        blk = data[off:off + 16]
189*0e16a31dSJim Jagielski        out += bytes(a ^ b for a, b in zip(aes_decrypt_block(rk, blk), prev))
190*0e16a31dSJim Jagielski        prev = blk
191*0e16a31dSJim Jagielski    n = out[-1]
192*0e16a31dSJim Jagielski    if not 1 <= n <= 16:
193*0e16a31dSJim Jagielski        raise DecryptError("bad AES padding byte %d" % n)
194*0e16a31dSJim Jagielski    return bytes(out[:-n])
195*0e16a31dSJim Jagielski
196*0e16a31dSJim Jagielski
197*0e16a31dSJim Jagielskidef sha1_rtl(data):
198*0e16a31dSJim Jagielski    """SHA-1 as main/sal rtl_digest_SHA1 computes it: input lengths of 52..55 mod 64 get one extra padding block."""
199*0e16a31dSJim Jagielski    h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]
200*0e16a31dSJim Jagielski    r = len(data) % 64
201*0e16a31dSJim Jagielski    m = data + b"\x80" + bytes((55 - r) % 64) + (bytes(64) if 52 <= r <= 55 else b"") + struct.pack(">Q", len(data) * 8)
202*0e16a31dSJim Jagielski
203*0e16a31dSJim Jagielski    def rol(x, n):
204*0e16a31dSJim Jagielski        return ((x << n) | (x >> (32 - n))) & MASK
205*0e16a31dSJim Jagielski    for o in range(0, len(m), 64):
206*0e16a31dSJim Jagielski        w = list(struct.unpack(">16I", m[o:o + 64]))
207*0e16a31dSJim Jagielski        for i in range(16, 80):
208*0e16a31dSJim Jagielski            w.append(rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1))
209*0e16a31dSJim Jagielski        a, b, c, d, e = h
210*0e16a31dSJim Jagielski        for i in range(80):
211*0e16a31dSJim Jagielski            if i < 20:
212*0e16a31dSJim Jagielski                f, k = (b & c) | (~b & d), 0x5A827999
213*0e16a31dSJim Jagielski            elif i < 40:
214*0e16a31dSJim Jagielski                f, k = b ^ c ^ d, 0x6ED9EBA1
215*0e16a31dSJim Jagielski            elif i < 60:
216*0e16a31dSJim Jagielski                f, k = (b & c) | (b & d) | (c & d), 0x8F1BBCDC
217*0e16a31dSJim Jagielski            else:
218*0e16a31dSJim Jagielski                f, k = b ^ c ^ d, 0xCA62C1D6
219*0e16a31dSJim Jagielski            a, b, c, d, e = (rol(a, 5) + f + e + k + w[i]) & MASK, a, rol(b, 30), c, d
220*0e16a31dSJim Jagielski        h = [(x + y) & MASK for x, y in zip(h, (a, b, c, d, e))]
221*0e16a31dSJim Jagielski    return struct.pack(">5I", *h)
222*0e16a31dSJim Jagielski
223*0e16a31dSJim Jagielski
224*0e16a31dSJim Jagielskidef b64(v, what):
225*0e16a31dSJim Jagielski    try:
226*0e16a31dSJim Jagielski        return base64.b64decode(v or "", validate=True)
227*0e16a31dSJim Jagielski    except Exception:
228*0e16a31dSJim Jagielski        raise DecryptError("%s is not base64: %r" % (what, v))
229*0e16a31dSJim Jagielski
230*0e16a31dSJim Jagielski
231*0e16a31dSJim Jagielskidef entry_params(fe):
232*0e16a31dSJim Jagielski    ed = fe.find(Q + "encryption-data")
233*0e16a31dSJim Jagielski    alg, kd, sk = ed.find(Q + "algorithm"), ed.find(Q + "key-derivation"), ed.find(Q + "start-key-generation")
234*0e16a31dSJim Jagielski    if alg is None or kd is None:
235*0e16a31dSJim Jagielski        raise DecryptError("encryption-data lacks algorithm or key-derivation")
236*0e16a31dSJim Jagielski    p = {"checksum": b64(ed.get(Q + "checksum"), "checksum") if ed.get(Q + "checksum") else None}
237*0e16a31dSJim Jagielski    ctype = ed.get(Q + "checksum-type")
238*0e16a31dSJim Jagielski    p["check"] = {"SHA1/1K": "sha1", M + "#sha1-1k": "sha1", M + "#sha256-1k": "sha256"}.get(ctype)
239*0e16a31dSJim Jagielski    if p["check"] is None:
240*0e16a31dSJim Jagielski        raise DecryptError("checksum-type %r" % ctype)
241*0e16a31dSJim Jagielski    name = alg.get(Q + "algorithm-name")
242*0e16a31dSJim Jagielski    keylen = {"http://www.w3.org/2001/04/xmlenc#aes128-cbc": 16, "http://www.w3.org/2001/04/xmlenc#aes192-cbc": 24,
243*0e16a31dSJim Jagielski              "http://www.w3.org/2001/04/xmlenc#aes256-cbc": 32}.get(name)
244*0e16a31dSJim Jagielski    if name in ("Blowfish CFB", M + "#blowfish"):
245*0e16a31dSJim Jagielski        p["cipher"], keylen = "blowfish", 16
246*0e16a31dSJim Jagielski    elif keylen:
247*0e16a31dSJim Jagielski        p["cipher"] = "aes"
248*0e16a31dSJim Jagielski    else:
249*0e16a31dSJim Jagielski        raise DecryptError("algorithm-name %r" % name)
250*0e16a31dSJim Jagielski    p["iv"] = b64(alg.get(Q + "initialisation-vector"), "initialisation-vector")
251*0e16a31dSJim Jagielski    if kd.get(Q + "key-derivation-name") not in ("PBKDF2", M + "#pbkdf2"):
252*0e16a31dSJim Jagielski        raise DecryptError("key-derivation-name %r" % kd.get(Q + "key-derivation-name"))
253*0e16a31dSJim Jagielski    p["salt"] = b64(kd.get(Q + "salt"), "salt")
254*0e16a31dSJim Jagielski    p["iter"] = int(kd.get(Q + "iteration-count"))
255*0e16a31dSJim Jagielski    p["keylen"] = int(kd.get(Q + "key-size") or keylen)
256*0e16a31dSJim Jagielski    sname = sk.get(Q + "start-key-generation-name") if sk is not None else "SHA1"
257*0e16a31dSJim Jagielski    p["start"] = {"SHA1": "sha1", "http://www.w3.org/2000/09/xmldsig#sha1": "sha1",
258*0e16a31dSJim Jagielski                  "http://www.w3.org/2000/09/xmldsig#sha256": "sha256"}.get(sname)
259*0e16a31dSJim Jagielski    if p["start"] is None:
260*0e16a31dSJim Jagielski        raise DecryptError("start-key-generation-name %r" % sname)
261*0e16a31dSJim Jagielski    return p
262*0e16a31dSJim Jagielski
263*0e16a31dSJim Jagielski
264*0e16a31dSJim Jagielskidef raw_entry(path, zi):
265*0e16a31dSJim Jagielski    with open(path, "rb") as f:
266*0e16a31dSJim Jagielski        f.seek(zi.header_offset)
267*0e16a31dSJim Jagielski        h = f.read(30)
268*0e16a31dSJim Jagielski        if h[:4] != b"PK\x03\x04":
269*0e16a31dSJim Jagielski            raise DecryptError("no local header for %s" % zi.filename)
270*0e16a31dSJim Jagielski        n, e = struct.unpack("<HH", h[26:30])
271*0e16a31dSJim Jagielski        f.seek(zi.header_offset + 30 + n + e)
272*0e16a31dSJim Jagielski        return f.read(zi.compress_size)
273*0e16a31dSJim Jagielski
274*0e16a31dSJim Jagielski
275*0e16a31dSJim Jagielskidef decrypt_entry(raw, p, password, name, notes):
276*0e16a31dSJim Jagielski    start = hashlib.new(p["start"], password.encode("utf-8")).digest()
277*0e16a31dSJim Jagielski    key = hashlib.pbkdf2_hmac("sha1", start, p["salt"], p["iter"], p["keylen"])
278*0e16a31dSJim Jagielski    try:
279*0e16a31dSJim Jagielski        plain = blowfish_cfb64_decrypt(key, p["iv"], raw) if p["cipher"] == "blowfish" else aes_cbc_decrypt(key, p["iv"], raw)
280*0e16a31dSJim Jagielski    except DecryptError as e:
281*0e16a31dSJim Jagielski        raise DecryptError("%s: %s (wrong password?)" % (name, e))
282*0e16a31dSJim Jagielski    if p["checksum"] is not None and hashlib.new(p["check"], plain[:1024]).digest() != p["checksum"]:
283*0e16a31dSJim Jagielski        if p["check"] == "sha1" and sha1_rtl(plain[:1024]) == p["checksum"]:
284*0e16a31dSJim Jagielski            notes.append("%s: checksum is the non-standard SHA-1 of main/sal (hashed length %d, mod 64 = %d)"
285*0e16a31dSJim Jagielski                         % (name, len(plain[:1024]), len(plain[:1024]) % 64))
286*0e16a31dSJim Jagielski        else:
287*0e16a31dSJim Jagielski            raise DecryptError("%s: checksum mismatch (wrong password?)" % name)
288*0e16a31dSJim Jagielski    try:
289*0e16a31dSJim Jagielski        d = zlib.decompressobj(-15)
290*0e16a31dSJim Jagielski        out = d.decompress(plain) + d.flush()
291*0e16a31dSJim Jagielski    except zlib.error as e:
292*0e16a31dSJim Jagielski        raise DecryptError("%s: decrypted data does not inflate (%s)" % (name, e))
293*0e16a31dSJim Jagielski    if not d.eof:
294*0e16a31dSJim Jagielski        raise DecryptError("%s: deflate stream is truncated" % name)
295*0e16a31dSJim Jagielski    return out
296*0e16a31dSJim Jagielski
297*0e16a31dSJim Jagielski
298*0e16a31dSJim Jagielskidef decrypt_package(src, dst, password, notes):
299*0e16a31dSJim Jagielski    """Returns the number of encrypted entries decrypted; 0 means the package was copied unchanged."""
300*0e16a31dSJim Jagielski    z = zipfile.ZipFile(src)
301*0e16a31dSJim Jagielski    mani = ET.fromstring(z.read("META-INF/manifest.xml"))
302*0e16a31dSJim Jagielski    enc = {}
303*0e16a31dSJim Jagielski    for fe in mani.findall(Q + "file-entry"):
304*0e16a31dSJim Jagielski        if fe.find(Q + "encryption-data") is not None:
305*0e16a31dSJim Jagielski            enc[fe.get(Q + "full-path")] = fe
306*0e16a31dSJim Jagielski    data = {}
307*0e16a31dSJim Jagielski    for zi in z.infolist():
308*0e16a31dSJim Jagielski        n = zi.filename
309*0e16a31dSJim Jagielski        if n in enc:
310*0e16a31dSJim Jagielski            if zi.compress_type != zipfile.ZIP_STORED:
311*0e16a31dSJim Jagielski                raise DecryptError("%s: encrypted entry is not STORED" % n)
312*0e16a31dSJim Jagielski            plain = decrypt_entry(raw_entry(src, zi), entry_params(enc[n]), password, n, notes)
313*0e16a31dSJim Jagielski            size = enc[n].get(Q + "size")
314*0e16a31dSJim Jagielski            if size is not None and int(size) != len(plain):
315*0e16a31dSJim Jagielski                raise DecryptError("%s: manifest:size %s, decrypted %d bytes" % (n, size, len(plain)))
316*0e16a31dSJim Jagielski            data[n] = plain
317*0e16a31dSJim Jagielski        else:
318*0e16a31dSJim Jagielski            data[n] = z.read(n)
319*0e16a31dSJim Jagielski    for fe in enc.values():
320*0e16a31dSJim Jagielski        fe.remove(fe.find(Q + "encryption-data"))
321*0e16a31dSJim Jagielski        fe.attrib.pop(Q + "size", None)
322*0e16a31dSJim Jagielski        fe.text = None
323*0e16a31dSJim Jagielski    missing = [p for p in enc if p not in data]
324*0e16a31dSJim Jagielski    if missing:
325*0e16a31dSJim Jagielski        raise DecryptError("manifest lists encrypted entries not in the zip: %s" % missing)
326*0e16a31dSJim Jagielski    if enc:
327*0e16a31dSJim Jagielski        data["META-INF/manifest.xml"] = ET.tostring(mani, encoding="UTF-8", xml_declaration=True)
328*0e16a31dSJim Jagielski    tmp = dst + ".tmp"
329*0e16a31dSJim Jagielski    with zipfile.ZipFile(tmp, "w") as o:
330*0e16a31dSJim Jagielski        names = [zi.filename for zi in z.infolist()]
331*0e16a31dSJim Jagielski        for n in sorted(names, key=lambda n: n != "mimetype"):
332*0e16a31dSJim Jagielski            zi = zipfile.ZipInfo(n, date_time=z.getinfo(n).date_time)
333*0e16a31dSJim Jagielski            zi.compress_type = zipfile.ZIP_STORED if n == "mimetype" or n.endswith("/") else zipfile.ZIP_DEFLATED
334*0e16a31dSJim Jagielski            zi.external_attr = z.getinfo(n).external_attr
335*0e16a31dSJim Jagielski            o.writestr(zi, data[n])
336*0e16a31dSJim Jagielski    os.replace(tmp, dst)
337*0e16a31dSJim Jagielski    return len(enc)
338*0e16a31dSJim Jagielski
339*0e16a31dSJim Jagielski
340*0e16a31dSJim Jagielskidef selftest():
341*0e16a31dSJim Jagielski    bad = 0
342*0e16a31dSJim Jagielski
343*0e16a31dSJim Jagielski    def check(name, got, want):
344*0e16a31dSJim Jagielski        nonlocal bad
345*0e16a31dSJim Jagielski        print(("ok   " if got == want else "FAIL ") + name)
346*0e16a31dSJim Jagielski        bad += got != want
347*0e16a31dSJim Jagielski    for k, pt, ct in (("0000000000000000", "0000000000000000", "4ef997456198dd78"),
348*0e16a31dSJim Jagielski                      ("ffffffffffffffff", "ffffffffffffffff", "51866fd5b85ecb8a"),
349*0e16a31dSJim Jagielski                      ("3000000000000000", "1000000000000001", "7d856f9a613063f2")):
350*0e16a31dSJim Jagielski        check("blowfish key " + k, Blowfish(bytes.fromhex(k)).encrypt_block(bytes.fromhex(pt)).hex(), ct)
351*0e16a31dSJim Jagielski    pt = "00112233445566778899aabbccddeeff"
352*0e16a31dSJim Jagielski    for k, ct in (("000102030405060708090a0b0c0d0e0f", "69c4e0d86a7b0430d8cdb78070b4c55a"),
353*0e16a31dSJim Jagielski                  ("000102030405060708090a0b0c0d0e0f1011121314151617", "dda97ca4864cdfe06eaf70a0ec0d7191"),
354*0e16a31dSJim Jagielski                  ("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "8ea2b7ca516745bfeafc49904b496089")):
355*0e16a31dSJim Jagielski        check("aes-%d block" % (len(k) * 2), aes_decrypt_block(aes_schedule(bytes.fromhex(k)), bytes.fromhex(ct)).hex(), pt)
356*0e16a31dSJim Jagielski    msgs = [(bytes(range(256)) * 2)[:n] for n in range(0, 300) if not 52 <= n % 64 <= 55]
357*0e16a31dSJim Jagielski    check("sha1_rtl equals SHA-1 outside 52..55 mod 64", all(sha1_rtl(m) == hashlib.sha1(m).digest() for m in msgs), True)
358*0e16a31dSJim Jagielski    check("sha1_rtl differs from SHA-1 at 52..55 mod 64",
359*0e16a31dSJim Jagielski          all(sha1_rtl(bytes(n)) != hashlib.sha1(bytes(n)).digest() for n in (52, 53, 54, 55, 116, 886)), True)
360*0e16a31dSJim Jagielski    check("pbkdf2-hmac-sha1 RFC 6070", hashlib.pbkdf2_hmac("sha1", b"password", b"salt", 2, 20).hex(),
361*0e16a31dSJim Jagielski          "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957")
362*0e16a31dSJim Jagielski    return bad == 0
363*0e16a31dSJim Jagielski
364*0e16a31dSJim Jagielski
365*0e16a31dSJim Jagielskidef main(argv):
366*0e16a31dSJim Jagielski    if len(argv) == 2 and argv[1] == "--selftest":
367*0e16a31dSJim Jagielski        return 0 if selftest() else 1
368*0e16a31dSJim Jagielski    if len(argv) != 4:
369*0e16a31dSJim Jagielski        print("usage: decrypt.py <in-dir> <out-dir> <password>", file=sys.stderr)
370*0e16a31dSJim Jagielski        return 2
371*0e16a31dSJim Jagielski    src, dst, password = argv[1:]
372*0e16a31dSJim Jagielski    os.makedirs(dst, exist_ok=True)
373*0e16a31dSJim Jagielski    done = copied = 0
374*0e16a31dSJim Jagielski    failed, quirks = [], 0
375*0e16a31dSJim Jagielski    for name in sorted(os.listdir(src)):
376*0e16a31dSJim Jagielski        path = os.path.join(src, name)
377*0e16a31dSJim Jagielski        if name.startswith(".") or not os.path.isfile(path) or not zipfile.is_zipfile(path):
378*0e16a31dSJim Jagielski            print("skipped (not a zip package): %s" % name)
379*0e16a31dSJim Jagielski            continue
380*0e16a31dSJim Jagielski        notes = []
381*0e16a31dSJim Jagielski        try:
382*0e16a31dSJim Jagielski            n = decrypt_package(path, os.path.join(dst, name), password, notes)
383*0e16a31dSJim Jagielski        except (DecryptError, zipfile.BadZipFile, KeyError, ET.ParseError, ValueError) as e:
384*0e16a31dSJim Jagielski            failed.append(name)
385*0e16a31dSJim Jagielski            print("FAILED %s: %s" % (name, e), file=sys.stderr)
386*0e16a31dSJim Jagielski            continue
387*0e16a31dSJim Jagielski        for note in notes:
388*0e16a31dSJim Jagielski            quirks += 1
389*0e16a31dSJim Jagielski            print("note %s: %s" % (name, note))
390*0e16a31dSJim Jagielski        if n:
391*0e16a31dSJim Jagielski            done += 1
392*0e16a31dSJim Jagielski            print("decrypted %s (%d encrypted entries)" % (name, n))
393*0e16a31dSJim Jagielski        else:
394*0e16a31dSJim Jagielski            copied += 1
395*0e16a31dSJim Jagielski            print("copied %s (no encrypted entries)" % name)
396*0e16a31dSJim Jagielski    print("%d decrypted, %d copied unchanged, %d failed, %d non-standard SHA-1 checksums" % (done, copied, len(failed), quirks))
397*0e16a31dSJim Jagielski    return 1 if failed else 0
398*0e16a31dSJim Jagielski
399*0e16a31dSJim Jagielski
400*0e16a31dSJim Jagielskiif __name__ == "__main__":
401*0e16a31dSJim Jagielski    sys.exit(main(sys.argv))
402