001package com.gigya.android.sdk.encryption;
002
003import android.content.Context;
004import android.os.Build;
005import android.security.KeyPairGeneratorSpec;
006import android.security.keystore.KeyGenParameterSpec;
007import android.security.keystore.KeyProperties;
008import android.support.annotation.RequiresApi;
009
010import com.gigya.android.sdk.persistence.IPersistenceService;
011import com.gigya.android.sdk.utils.CipherUtils;
012
013import java.math.BigInteger;
014import java.security.Key;
015import java.security.KeyPairGenerator;
016import java.security.KeyStore;
017import java.security.PrivateKey;
018import java.security.PublicKey;
019import java.util.Calendar;
020import java.util.TimeZone;
021
022import javax.crypto.Cipher;
023import javax.crypto.KeyGenerator;
024import javax.crypto.SecretKey;
025import javax.crypto.spec.SecretKeySpec;
026import javax.security.auth.x500.X500Principal;
027
028@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
029public class SessionKey implements ISecureKey {
030
031    private String TYPE = "AndroidKeyStore";
032
033    private final Context _context;
034    private final IPersistenceService _psService;
035
036    final static String SECRET_PREFERENCE_KEY = "GS_PREFA";
037
038    public SessionKey(Context context, IPersistenceService psService) {
039        _context = context;
040        _psService = psService;
041    }
042
043    @Override
044    public String getAlias() {
045        return "GS_ALIAS";
046    }
047
048    @Override
049    public String getTransformation() {
050        return "RSA/ECB/PKCS1Padding"; // Not using constants because they are only available from >=23.
051    }
052
053    @Override
054    public Cipher getEncryptionCipher(Key key) throws EncryptionException {
055        try {
056            final Cipher cipher = Cipher.getInstance(getTransformation());
057            cipher.init(Cipher.ENCRYPT_MODE, key);
058            return cipher;
059        } catch (Exception ex) {
060            ex.printStackTrace();
061            throw new EncryptionException("getDecryptionCipher: exception" + ex.getMessage(), ex.getCause());
062        }
063    }
064
065    @Override
066    public Cipher getDecryptionCipher(Key key) throws EncryptionException {
067        try {
068            final Cipher cipher = Cipher.getInstance(getTransformation());
069            cipher.init(Cipher.DECRYPT_MODE, key);
070            return cipher;
071        } catch (Exception ex) {
072            ex.printStackTrace();
073            throw new EncryptionException("getDecryptionCipher: exception" + ex.getMessage(), ex.getCause());
074        }
075    }
076
077
078    @Override
079    public SecretKey getKey() throws EncryptionException {
080        try {
081            KeyStore keyStore = KeyStore.getInstance(TYPE);
082            keyStore.load(null);
083            if (!keyStore.containsAlias(getAlias())) { // Keystore not available.
084                // Generate certificate for this new alias.
085                generateCertificateForAlias();
086                final PublicKey publicKey = keyStore.getCertificate(getAlias()).getPublicKey();
087                // Generate AES secret key
088                final KeyGenerator generator = KeyGenerator.getInstance("AES");
089                generator.init(128);
090                final SecretKey secretKey = generator.generateKey();
091                // Encrypt secret key and save it.
092                final Cipher cipher = getEncryptionCipher(publicKey);
093                byte[] encryptedAES = cipher.doFinal(secretKey.getEncoded());
094                final String newEncryptedSecret = CipherUtils.bytesToString(encryptedAES);
095                _psService.add(SECRET_PREFERENCE_KEY, newEncryptedSecret);
096                return secretKey;
097            } else if (!keyStore.entryInstanceOf(getAlias(), KeyStore.PrivateKeyEntry.class)) {
098                // Determines if the keystore for the specified entry is an instance or subclass of the alias specified.
099                // If not delete the entry and create a new one.
100                keyStore.deleteEntry(getAlias());
101                return getKey(); // Recursive.
102            } else {
103                // Keystore instance exist. Get private key from KeyStore instance and decrypt & generate the secret key.
104
105                final String aesKey = _psService.getString(SECRET_PREFERENCE_KEY, null);
106                if (aesKey != null && keyStore.containsAlias(getAlias()) && keyStore.entryInstanceOf(getAlias(), KeyStore.PrivateKeyEntry.class)) {
107                    // In (v3) secret key is ciphered and saved in the shared preferences.
108                    final PrivateKey privateKey = (PrivateKey) keyStore.getKey(getAlias(), null);
109                    final Cipher cipher = getDecryptionCipher(privateKey);
110                    byte[] decrypted = cipher.doFinal(CipherUtils.stringToBytes(aesKey));
111                    final String ALGORITHM_KEY = "AES";
112                    return new SecretKeySpec(decrypted, 0, decrypted.length, ALGORITHM_KEY);
113                }
114                return null;
115            }
116        } catch (Exception ex) {
117            ex.printStackTrace();
118            throw new EncryptionException("GetKey: exception" + ex.getMessage(), ex.getCause());
119        }
120    }
121
122    //region CERTIFICATE
123
124    private void generateCertificateForAlias() throws Exception {
125        final String ALGORITHM_KEYSTORE = "RSA";
126        final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM_KEYSTORE, TYPE);
127        final TimeZone timeZone = TimeZone.getTimeZone("UTC");
128        final Calendar start = Calendar.getInstance(timeZone);
129        final Calendar end = Calendar.getInstance(timeZone);
130        end.add(Calendar.YEAR, 25);
131        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
132            final KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
133                    getAlias(),
134                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
135                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
136                    .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
137                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
138                    .build();
139            keyGen.initialize(spec);
140            keyGen.generateKeyPair();
141        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
142            final KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(_context)
143                    .setAlias(getAlias())
144                    .setKeySize(2048)
145                    .setEndDate(end.getTime())
146                    .setStartDate(start.getTime())
147                    .setSerialNumber(BigInteger.ONE)
148                    .setSubject(new X500Principal("CN = Secured Preference Store, O = Devliving Online"))
149                    .build();
150            keyGen.initialize(spec);
151            keyGen.generateKeyPair();
152        } else {
153            final KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(_context)
154                    .setAlias(getAlias())
155                    .setEndDate(end.getTime())
156                    .setStartDate(start.getTime())
157                    .setSerialNumber(BigInteger.ONE)
158                    .setSubject(new X500Principal("CN = Secured Preference Store, O = Devliving Online"))
159                    .build();
160            keyGen.initialize(spec);
161            keyGen.generateKeyPair();
162        }
163    }
164
165    //endregion
166
167}