Added Android code
[wl-app.git] / Android / r2-streamer / r2-parser / src / main / java / org / readium / r2_streamer / parser / EncryptionDecoder.java
1 package org.readium.r2_streamer.parser;
2
3 import org.readium.r2_streamer.model.publication.Encryption;
4
5 import java.io.ByteArrayInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.UnsupportedEncodingException;
9 import java.security.MessageDigest;
10 import java.security.NoSuchAlgorithmException;
11 import java.util.Formatter;
12 import java.util.HashMap;
13
14 /**
15  * Created by gautam chibde on 18/5/17.
16  */
17
18 public final class EncryptionDecoder {
19     private final static String TAG = EncryptionDecoder.class.getName();
20
21     private static HashMap<String, Integer> decoders = new HashMap<>();
22
23     static {
24         EncryptionDecoder.decoders.put("http://www.idpf.org/2008/embedding", 1040);
25         EncryptionDecoder.decoders.put("http://ns.adobe.com/pdf/enc#RC", 1024);
26     }
27
28     /**
29      * Decode obfuscated font from a InputStream, if the encryption is known.
30      *
31      * @param identifier The associated publication Identifier.
32      * @param inStream   font input stream
33      * @param encryption {@link Encryption} object contains encryption type
34      * @return The InputStream containing the unencrypted resource.
35      */
36     public static InputStream decode(String identifier, InputStream inStream, Encryption encryption) {
37         if (identifier == null) {
38             System.out.println(TAG + " Couldn't get the publication identifier.");
39             return inStream;
40         }
41
42         if (!decoders.containsKey(encryption.getAlgorithm())) {
43             System.out.println(TAG + " " + encryption.getProfile() + " is encrypted but decoder cant handle it");
44             return inStream;
45         }
46         return decodeFont(identifier, inStream, decoders.get(encryption.getAlgorithm()), encryption.getAlgorithm());
47     }
48
49     /**
50      * Decode the given inputStream first X characters, depending of the obfuscation type.
51      *
52      * @param identifier The associated publication Identifier.
53      * @param inStream   The input stream containing the data of an obfuscated font
54      * @param length     The ObfuscationLength depending of the obfuscation type.
55      * @param algorithm  type of algorithm
56      * @return The Deobfuscated InputStream.
57      */
58     private static InputStream decodeFont(String identifier, InputStream inStream, Integer length, String algorithm) {
59
60         byte[] publicationKey = null;
61         switch (algorithm) {
62             case "http://ns.adobe.com/pdf/enc#RC":
63                 publicationKey = getHashKeyAdobe(identifier);
64                 break;
65             case "http://www.idpf.org/2008/embedding":
66                 publicationKey = getHashKeyIdpf(identifier);
67                 break;
68         }
69         return deobfuscate(inStream, publicationKey, length);
70     }
71
72     /**
73      * Receive an obfuscated InputStream and return deabfuscated InputStream
74      *
75      * @param inStream       The input stream containing the data of an obfuscated font
76      * @param publicationKey The publicationKey used to decode the X first characters.
77      * @param length         The number of characters obfuscated at the first of the file.
78      * @return The Deobfuscated InputStream.
79      */
80     private static InputStream deobfuscate(InputStream inStream, byte[] publicationKey, Integer length) {
81         if (publicationKey == null) {
82             return inStream;
83         }
84         try {
85             byte[] bytes = new byte[inStream.available()];
86             int size = inStream.read(bytes);
87             int count = size > length ? length : size;
88             int pubKeyLength = publicationKey.length;
89             int i = 0;
90             while (i < count) {
91                 bytes[i] = (byte) (bytes[i] ^ (publicationKey[i % pubKeyLength]));
92                 i++;
93             }
94             return new ByteArrayInputStream(bytes);
95         } catch (IOException e) {
96             System.out.println(TAG + ".deobfuscate() " + e);
97         }
98
99         return null;
100     }
101
102     /**
103      * Create an EPUB font obfuscation key from one or more strings according to the rules
104      * defined in the EPUB 3 spec, 4.3 Generating the Obfuscation Key
105      * (http://www.idpf.org/epub/30/spec/epub30-ocf.html#fobfus-keygen)
106      * <p>
107      * Squeezes out any whitespace in each UID and then concatenates the result
108      * using single space characters as the separator.
109      *
110      * @param identifier The string to convert into a key.
111      * @return obfuscation key string
112      */
113     private static byte[] getHashKeyIdpf(String identifier) {
114         try {
115             MessageDigest crypt = MessageDigest.getInstance("SHA-1");
116             crypt.reset();
117             crypt.update(identifier.getBytes("UTF-8"));
118             return hexToBytes(byteToHex(crypt.digest()));
119         } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
120             return null;
121         }
122     }
123
124     /**
125      * Generate the Hashkey used to salt the 1024 starting character of the Adobe font files.
126      *
127      * @param pubId The publication Identifier.
128      * @return The key's bytes array.
129      */
130     private static byte[] getHashKeyAdobe(String pubId) {
131         String cleanPubId = pubId.replaceAll("urn:uuid", "");
132         cleanPubId = cleanPubId.replaceAll("-", "");
133         cleanPubId = cleanPubId.replaceAll(":","");
134         return hexToBytes(cleanPubId);
135     }
136
137     /**
138      * Convert hexadecimal String to Bytes (UInt8) array.
139      *
140      * @param hexa The hexadecimal String
141      * @return The key's bytes array.
142      */
143     private static byte[] hexToBytes(String hexa) {
144         char[] hex = hexa.toCharArray();
145         int length = hex.length / 2;
146         byte[] raw = new byte[length];
147         for (int i = 0; i < length; i++) {
148             int high = Character.digit(hex[i * 2], 16);
149             int low = Character.digit(hex[i * 2 + 1], 16);
150             int value = (high << 4) | low;
151             if (value > 127)
152                 value -= 256;
153             raw[i] = (byte) value;
154         }
155         return raw;
156     }
157
158     /**
159      * Convert Bytes array to hexadecimal String.
160      *
161      * @param hash bytes array.
162      * @return The hexadecimal String.
163      */
164     private static String byteToHex(byte[] hash) {
165         Formatter formatter = new Formatter();
166         for (byte b : hash) {
167             formatter.format("%02x", b);
168         }
169         String result = formatter.toString();
170         formatter.close();
171         return result;
172     }
173 }