001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.crypto;
019
020import static org.syncany.crypto.CipherParams.CRYPTO_PROVIDER;
021import static org.syncany.crypto.CipherParams.CRYPTO_PROVIDER_ID;
022import static org.syncany.crypto.CipherParams.KEY_DERIVATION_DIGEST;
023import static org.syncany.crypto.CipherParams.KEY_DERIVATION_INFO;
024import static org.syncany.crypto.CipherParams.MASTER_KEY_DERIVATION_FUNCTION;
025import static org.syncany.crypto.CipherParams.MASTER_KEY_DERIVATION_ROUNDS;
026import static org.syncany.crypto.CipherParams.MASTER_KEY_SALT_SIZE;
027import static org.syncany.crypto.CipherParams.MASTER_KEY_SIZE;
028
029import java.io.ByteArrayOutputStream;
030import java.io.File;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.OutputStream;
034import java.io.RandomAccessFile;
035import java.lang.reflect.Field;
036import java.lang.reflect.Modifier;
037import java.math.BigInteger;
038import java.security.InvalidKeyException;
039import java.security.KeyPair;
040import java.security.KeyPairGenerator;
041import java.security.KeyStore;
042import java.security.NoSuchAlgorithmException;
043import java.security.NoSuchProviderException;
044import java.security.SecureRandom;
045import java.security.Security;
046import java.security.SignatureException;
047import java.security.cert.CertificateException;
048import java.security.cert.X509Certificate;
049import java.security.spec.InvalidKeySpecException;
050import java.security.spec.KeySpec;
051import java.util.Arrays;
052import java.util.Date;
053import java.util.List;
054import java.util.concurrent.atomic.AtomicBoolean;
055import java.util.logging.Level;
056import java.util.logging.Logger;
057
058import javax.crypto.Cipher;
059import javax.crypto.SecretKey;
060import javax.crypto.SecretKeyFactory;
061import javax.crypto.spec.PBEKeySpec;
062import javax.crypto.spec.SecretKeySpec;
063import javax.net.ssl.KeyManager;
064import javax.net.ssl.KeyManagerFactory;
065import javax.net.ssl.SSLContext;
066import javax.net.ssl.TrustManager;
067import javax.net.ssl.TrustManagerFactory;
068
069import org.bouncycastle.asn1.x500.X500Name;
070import org.bouncycastle.asn1.x500.X500NameBuilder;
071import org.bouncycastle.asn1.x500.style.BCStyle;
072import org.bouncycastle.cert.X509v3CertificateBuilder;
073import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
074import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
075import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
076import org.bouncycastle.crypto.params.HKDFParameters;
077import org.bouncycastle.operator.ContentSigner;
078import org.bouncycastle.operator.OperatorCreationException;
079import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
080
081/**
082 * The cipher utility provides functions to create a master key using PBKDF2,
083 * a derived key using SHA256, and to create a {@link Cipher} from a derived key.
084 * It furthermore offers a method to programmatically enable the unlimited strength
085 * crypto policies.
086 *
087 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
088 */
089public class CipherUtil {
090        private static final Logger logger = Logger.getLogger(CipherUtil.class.getSimpleName());
091
092        /**
093         * Chars from A-Z / a-z to be used in randomly generated passwords.
094         *
095         * <p><b>Note:</b> This string cannot contain numbers, to prevent breaking
096         * of the vector clock format.
097         */
098        private static final String ALPHABETIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
099
100        private static AtomicBoolean initialized = new AtomicBoolean(false);
101        private static AtomicBoolean unlimitedStrengthEnabled = new AtomicBoolean(false);
102        private static SecureRandom secureRandom = new SecureRandom();
103
104        static {
105                init();
106        }
107
108        /**
109         * Initializes the crypto provider ("Bouncy Castle") and tests whether the unlimited
110         * strength policy has been enabled. Unlimited crypto allows for stronger crypto algorithms
111         * such as AES-256 or Twofish-256.
112         *
113         * <p>The method is called in the <code>static</code> block of this class and hence initialized
114         * whenever then class is used.
115         *
116         * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography Extension (JCE) Unlimited Strength</a>
117         */
118        public static synchronized void init() {
119                if (!initialized.get()) {
120                        // Bouncy Castle
121                        if (Security.getProvider(CRYPTO_PROVIDER_ID) == null) {
122                                Security.addProvider(CRYPTO_PROVIDER);
123                        }
124
125                        // Unlimited strength
126                        try {
127                                unlimitedStrengthEnabled.set(Cipher.getMaxAllowedKeyLength("AES") > 128);
128                        }
129                        catch (NoSuchAlgorithmException e) {
130                                logger.log(Level.FINE, "No such transformation found", e);
131                                unlimitedStrengthEnabled.set(false);
132                        }
133
134                        initialized.set(true);
135                }
136        }
137
138        /**
139         * Attempts to programmatically enable the unlimited strength Java crypto extension
140         * using the reflection API.
141         *
142         * <p>This class tries to set the property <code>isRestricted</code> of the class
143         * <code>javax.crypto.JceSecurity</code> to <code>false</code> -- effectively disabling
144         * the artificial limitations (and the disallowed algorithms).
145         *
146         * <p><b>Note</b>: Be aware that enabling the unlimited strength extension needs to
147         * be acknowledged by the end-user to avoid legal issues!
148         *
149         * @throws CipherException If the unlimited strength policy cannot be enabled.
150         * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography Extension (JCE) Unlimited Strength</a>
151         */
152        public static void enableUnlimitedStrength() throws CipherException {
153                if (!unlimitedStrengthEnabled.get()) {
154                        logger.log(Level.FINE, "- Enabling unlimited strength/crypto ...");
155
156                        /*
157                         * We want to turn off the isRestricted field.  However it is a
158                         * private final field.  We therefore use reflection.
159                         */
160                        try {
161                                Field isRestrictedField = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
162                                isRestrictedField.setAccessible(true);
163                                Field modifiersField = Field.class.getDeclaredField("modifiers");
164                                modifiersField.setAccessible(true);
165                                modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
166                                isRestrictedField.set(null, false);
167                        }
168                        catch (Exception e) {
169                                throw new CipherException(e);
170                        }
171                }
172        }
173
174        /**
175         * Creates a random array of bytes using the default {@link SecureRandom} implementation
176         * of the currently active JVM. The returned array can be used as a basis for secret keys,
177         * IVs or salts.
178         *
179         * @param size Size of the returned array (in bytes)
180         * @return Returns a random byte array (using a secure pseudo random generator)
181         */
182        public static byte[] createRandomArray(int size) {
183                byte[] randomByteArray = new byte[size];
184                secureRandom.nextBytes(randomByteArray);
185
186                return randomByteArray;
187        }
188
189        /**
190         * Generates a random string the given length. Only uses characters
191         * A-Z/a-z (in order to always create valid serialized vector clock representations).
192         */
193        public static String createRandomAlphabeticString(int size) {
194                StringBuilder sb = new StringBuilder(size);
195
196                for (int i = 0; i < size; i++) {
197                        sb.append(ALPHABETIC_CHARS.charAt(secureRandom.nextInt(ALPHABETIC_CHARS.length())));
198                }
199
200                return sb.toString();
201        }
202
203        /**
204         * Creates a derived key from the given {@link SecretKey} an input salt and wraps the key in
205         * a {@link SecretKeySpec} using the given {@link CipherSpec}.
206         *
207         * <p>This method simply uses the {@link #createDerivedKey(byte[], byte[], String, int) createDerivedKey()}
208         * method using the encoded input key and the algorithm and key size given by the cipher spec.
209         *
210         * @param inputKey The source key to derive the new key from
211         * @param inputSalt Input salt used to generate the new key (a non-secret random value!)
212         * @param outputCipherSpec Defines the algorithm and key size of the new output key
213         * @return Returns a derived key (including the given input salt)
214         */
215        public static SaltedSecretKey createDerivedKey(SecretKey inputKey, byte[] inputSalt, CipherSpec outputCipherSpec)
216                        throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
217
218                return createDerivedKey(inputKey.getEncoded(), inputSalt, outputCipherSpec.getAlgorithm(), outputCipherSpec.getKeySize());
219        }
220
221        /**
222         * Creates a derived key from the given input key material (raw byte array) and an input salt
223         * and wraps the key in  a {@link SecretKeySpec} using the given output key algorithm and output
224         * key size.
225         *
226         * <p>The algorithm used to derive the new key from the input key material (IKM) is the
227         * <b>HMAC-based Extract-and-Expand Key Derivation Function (HKDF)</b> (see
228         * <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>)
229         *
230         * @param inputKeyMaterial The input key material as raw data bytes, e.g. determined from {@link SecretKey#getEncoded()}
231         * @param inputSalt Input salt used to generate the new key (a non-secret random value!)
232         * @param outputKeyAlgorithm Defines the algorithm of the new output key (for {@link SecretKeySpec#getAlgorithm()})
233         * @param outputKeySize Defines the key size of the new output key
234         * @return Returns a new pseudorandom key derived from the input key material using HKDF
235         * @see <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>
236         */
237        public static SaltedSecretKey createDerivedKey(byte[] inputKeyMaterial, byte[] inputSalt, String outputKeyAlgorithm, int outputKeySize)
238                        throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
239
240                HKDFBytesGenerator hkdf = new HKDFBytesGenerator(KEY_DERIVATION_DIGEST);
241                hkdf.init(new HKDFParameters(inputKeyMaterial, inputSalt, KEY_DERIVATION_INFO));
242
243                byte[] derivedKey = new byte[outputKeySize / 8];
244                hkdf.generateBytes(derivedKey, 0, derivedKey.length);
245
246                return toSaltedSecretKey(derivedKey, inputSalt, outputKeyAlgorithm);
247        }
248
249        public static SecretKey toSecretKey(byte[] secretKeyBytes, String algorithm) {
250                String plainAlgorithm = (algorithm.indexOf('/') != -1) ? algorithm.substring(0, algorithm.indexOf('/')) : algorithm;
251                SecretKey secretKey = new SecretKeySpec(secretKeyBytes, plainAlgorithm);
252
253                return secretKey;
254        }
255
256        public static SaltedSecretKey toSaltedSecretKey(byte[] secretKeyBytes, byte[] saltBytes, String algorithm) {
257                return new SaltedSecretKey(toSecretKey(secretKeyBytes, algorithm), saltBytes);
258        }
259
260        public static SaltedSecretKey createMasterKey(String password) throws CipherException {
261                byte[] salt = createRandomArray(MASTER_KEY_SALT_SIZE / 8);
262                return createMasterKey(password, salt);
263        }
264
265        public static SaltedSecretKey createMasterKey(String password, byte[] salt) throws CipherException {
266                try {
267                        logger.log(Level.FINE, "- Creating secret key using {0} with {1} rounds, key size {2} bit ...", new Object[] {
268                                        MASTER_KEY_DERIVATION_FUNCTION,
269                                        MASTER_KEY_DERIVATION_ROUNDS, MASTER_KEY_SIZE });
270
271                        SecretKeyFactory factory = SecretKeyFactory.getInstance(MASTER_KEY_DERIVATION_FUNCTION);
272                        KeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, MASTER_KEY_DERIVATION_ROUNDS, MASTER_KEY_SIZE);
273                        SecretKey masterKey = factory.generateSecret(pbeKeySpec);
274
275                        return new SaltedSecretKey(masterKey, salt);
276                }
277                catch (Exception e) {
278                        throw new CipherException(e);
279                }
280        }
281
282        public static boolean isEncrypted(File file) throws IOException {
283                byte[] actualMagic = new byte[MultiCipherOutputStream.STREAM_MAGIC.length];
284
285                RandomAccessFile rFile = new RandomAccessFile(file, "r");
286                rFile.read(actualMagic);
287                rFile.close();
288
289                return Arrays.equals(actualMagic, MultiCipherOutputStream.STREAM_MAGIC);
290        }
291
292        public static void encrypt(InputStream plaintextInputStream, OutputStream ciphertextOutputStream, List<CipherSpec> cipherSpecs,
293                        SaltedSecretKey masterKey) throws CipherException {
294
295                try {
296                        CipherSession cipherSession = new CipherSession(masterKey);
297                        OutputStream multiCipherOutputStream = new MultiCipherOutputStream(ciphertextOutputStream, cipherSpecs, cipherSession);
298
299                        int read = -1;
300                        byte[] buffer = new byte[4096];
301
302                        while (-1 != (read = plaintextInputStream.read(buffer))) {
303                                multiCipherOutputStream.write(buffer, 0, read);
304                        }
305
306                        plaintextInputStream.close();
307                        multiCipherOutputStream.close();
308                }
309                catch (IOException e) {
310                        throw new CipherException(e);
311                }
312        }
313
314        public static byte[] encrypt(InputStream plaintextInputStream, List<CipherSpec> cipherSuites, SaltedSecretKey masterKey) throws CipherException {
315                ByteArrayOutputStream ciphertextOutputStream = new ByteArrayOutputStream();
316                encrypt(plaintextInputStream, ciphertextOutputStream, cipherSuites, masterKey);
317
318                return ciphertextOutputStream.toByteArray();
319        }
320
321        public static byte[] decrypt(InputStream fromInputStream, SaltedSecretKey masterKey) throws CipherException {
322                try {
323                        CipherSession cipherSession = new CipherSession(masterKey);
324                        MultiCipherInputStream multiCipherInputStream = new MultiCipherInputStream(fromInputStream, cipherSession);
325                        ByteArrayOutputStream plaintextOutputStream = new ByteArrayOutputStream();
326
327                        int read = -1;
328                        byte[] buffer = new byte[4096];
329
330                        while (-1 != (read = multiCipherInputStream.read(buffer))) {
331                                plaintextOutputStream.write(buffer, 0, read);
332                        }
333
334                        multiCipherInputStream.close();
335                        plaintextOutputStream.close();
336
337                        return plaintextOutputStream.toByteArray();
338                }
339                catch (IOException e) {
340                        throw new CipherException(e);
341                }
342        }
343
344        /**
345         * Generates a 2048-bit RSA key pair.
346         */
347        public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException, CipherException, NoSuchProviderException {
348                KeyPairGenerator keyGen = KeyPairGenerator.getInstance(CipherParams.CERTIFICATE_KEYPAIR_ALGORITHM, CipherParams.CRYPTO_PROVIDER_ID);
349                keyGen.initialize(CipherParams.CERTIFICATE_KEYPAIR_SIZE, secureRandom);
350
351                return keyGen.generateKeyPair();
352        }
353
354        /**
355         * Generates a self-signed certificate, given a public/private key pair.
356         *
357         * @see <a href="https://code.google.com/p/gitblit/source/browse/src/com/gitblit/MakeCertificate.java?r=88598bb2f779b73479512d818c675dea8fa72138">Original source of this method</a>
358         */
359        public static X509Certificate generateSelfSignedCertificate(String commonName, KeyPair keyPair) throws OperatorCreationException,
360        CertificateException,
361                        InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
362
363                // Certificate CN, O and OU
364                X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
365
366                builder.addRDN(BCStyle.CN, commonName);
367                builder.addRDN(BCStyle.O, CipherParams.CERTIFICATE_ORGANIZATION);
368                builder.addRDN(BCStyle.OU, CipherParams.CERTIFICATE_ORGUNIT);
369
370                // Dates and serial
371                Date notBefore = new Date(System.currentTimeMillis() - 1 * 24 * 60 * 60 * 1000L);
372                Date notAfter = new Date(System.currentTimeMillis() + 5 * 365 * 24 * 60 * 60 * 1000L);
373                BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
374
375                // Issuer and subject (identical, because self-signed)
376                X500Name issuer = builder.build();
377                X500Name subject = issuer;
378
379                X509v3CertificateBuilder certificateGenerator =
380                                new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, keyPair.getPublic());
381
382                ContentSigner signatureGenerator = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
383                .setProvider(CipherParams.CRYPTO_PROVIDER)
384                .build(keyPair.getPrivate());
385
386                X509Certificate certificate = new JcaX509CertificateConverter()
387                .setProvider(CipherParams.CRYPTO_PROVIDER)
388                .getCertificate(certificateGenerator.build(signatureGenerator));
389
390                certificate.checkValidity(new Date());
391                certificate.verify(certificate.getPublicKey());
392
393                return certificate;
394        }
395
396        /**
397         * Creates an SSL context, given a key store and a trust store.
398         */
399        public static SSLContext createSSLContext(KeyStore keyStore, KeyStore trustStore) throws Exception {
400                try {
401                        // Server key and certificate
402                        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
403                        keyManagerFactory.init(keyStore, new char[0]);
404
405                        KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
406
407                        // Trusted certificates
408                        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
409                        trustManagerFactory.init(trustStore);
410
411                        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
412
413                        // Create SSL context
414                        SSLContext sslContext = SSLContext.getInstance("TLS");
415                        sslContext.init(keyManagers, trustManagers, null);
416
417                        return sslContext;
418                }
419                catch (Exception e) {
420                        throw new Exception("Unable to initialize SSL context", e);
421                }
422        }
423}