/* * Copyright 2013-2016 Amazon.com, * Inc. or its affiliates. All Rights Reserved. * * Licensed under the Amazon Software License (the "License"). * You may not use this file except in compliance with the * License. A copy of the License is located at * * http://aws.amazon.com/asl/ * * or in the "license" file accompanying this file. This file is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, express or implied. See the License * for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.mobileconnectors.cognitoidentityprovider.util; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.util.Log; import com.amazonaws.util.Base64; import com.amazonaws.util.StringUtils; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * A utility class for device operations. */ public final class CognitoDeviceHelper { final static private String TAG = "CognitoDeviceHelper"; final static private String COGNITO_DEVICE_CACHE = "CognitoIdentityProviderDeviceCache"; final static private String COGNITO_DEVICE_KEY = "DeviceKey"; final static private String COGNITO_DEVICE_GROUP_KEY = "DeviceGroupKey"; final static private String COGNITO_DEVICE_SECRET = "DeviceSecret"; final static public int DEFAULT_DEVICE_PAGINATION_LIMIT = 10; static deviceSRP srpCalculator = null; /** * Uses the Android class {@android.os.build} to return the model of the android device. * * @return Device model name, which is also the name of the device. */ public static String getDeviceName() { return Build.MODEL; } /** * Returns the cached key for this device. Device keys are stored in SharedPreferences and are * used to track devices. Returns null if no device key was cached. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the application. * @param context REQUIRED: Application context. * @return device key as String, null if the device-key is not available. */ public static String getDeviceKey(String username, String userPoolId, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); if (cipCachedDeviceDetails != null && cipCachedDeviceDetails.contains(COGNITO_DEVICE_KEY)) { return cipCachedDeviceDetails.getString(COGNITO_DEVICE_KEY, null); } } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } return null; } /** * Returns the cached device secret for this device. Device secret is generated when the device * is confirmed and is used for device identification. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the application. * @param context REQUIRED: Application context. * @return device secret as String, null if the device-key is not available. */ public static String getDeviceSecret(String username, String userPoolId, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); if (cipCachedDeviceDetails != null && cipCachedDeviceDetails.contains(COGNITO_DEVICE_SECRET)) { return cipCachedDeviceDetails.getString(COGNITO_DEVICE_SECRET, null); } } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } return null; } /** * Returns the cached device group key for this device. Device secret is generated when the device * is confirmed and is used for device identification. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the application. * @param context REQUIRED: Application context. * @return device group key as String, null if the device-key is not available. */ public static String getDeviceGroupKey(String username, String userPoolId, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); if (cipCachedDeviceDetails != null && cipCachedDeviceDetails.contains(COGNITO_DEVICE_GROUP_KEY)) { return cipCachedDeviceDetails.getString(COGNITO_DEVICE_GROUP_KEY, null); } } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } return null; } /** * This method caches the device key. Device key is assigned by the Amazon Cognito service and is * used as a device identifier. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the device. * @param deviceKey REQUIRED: Cognito assigned device key. * @param context REQUIRED: App context, needed to access device datastore. */ public static void cacheDeviceKey(String username, String userPoolId, String deviceKey, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); cipCachedDeviceDetails.edit().putString(COGNITO_DEVICE_KEY, deviceKey).apply(); } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } } /** * This method caches the device verifier. Device verifier is generated locally by the SDK and * it is used to authenticate the device through device SRP authentication. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the device. * @param deviceSecret REQUIRED: Cognito assigned device key. * @param context REQUIRED: App context, needed to access device datastore. */ public static void cacheDeviceVerifier(String username, String userPoolId, String deviceSecret, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); cipCachedDeviceDetails.edit().putString(COGNITO_DEVICE_SECRET, deviceSecret).apply(); } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } } /** * This method caches the device group key. Device verifier is generated locally by the SDK and * it is used to authenticate the device through device SRP authentication. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the device. * @param deviceGroupKey REQUIRED: Cognito assigned device group key. * @param context REQUIRED: App context, needed to access device datastore. */ public static void cacheDeviceGroupKey(String username, String userPoolId, String deviceGroupKey, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); cipCachedDeviceDetails.edit().putString(COGNITO_DEVICE_GROUP_KEY, deviceGroupKey).apply(); } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } } /** * Clears cached device details for this user. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the device. * @param context REQUIRED: App context, needed to access device datastore. */ public static void clearCachedDevice(String username, String userPoolId, Context context) { try { SharedPreferences cipCachedDeviceDetails = context.getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0); cipCachedDeviceDetails.edit().clear().apply(); } catch (Exception e) { Log.e(TAG, "Error accessing SharedPreferences" + e.getMessage()); } } /** * Generates SRP verification parameters for device verification. * * @param deviceKey REQUIRED: Username this device belongs to. * @param deviceGroup REQUIRED: This is the device group id returned by the service. * @return srp verification details for this device, as a {@link Map}. */ public static Map<String, String> generateVerificationParameters(String deviceKey, String deviceGroup) { Map<String, String> devVerfPars = new HashMap<String, String>(); String deviceSecret = generateRandomString(); srpCalculator = new deviceSRP(deviceGroup, deviceKey, deviceSecret); byte[] salt = srpCalculator.getSalt().toByteArray(); byte[] srpVerifier = srpCalculator.getVerifier().toByteArray(); devVerfPars.put("salt", new String(Base64.encode(salt))); devVerfPars.put("verifier", new String(Base64.encode(srpVerifier))); devVerfPars.put("secret", deviceSecret); return devVerfPars; } /** * Generates and returns the key to access device details from shared preferences. * * @param username REQUIRED: The current user. * @param userPoolId REQUIRED: Client ID of the device. * @return a string which is a key to access the device key from SharedPreferences. */ private static String getDeviceDetailsCacheForUser(String username, String userPoolId) { return COGNITO_DEVICE_CACHE + "." + userPoolId + "." + username; } /** * Static class for SRP related calculations for devices. */ public static class deviceSRP { private BigInteger salt; private BigInteger verifier; private static final String HASH_ALGORITHM = "SHA-256"; private static final ThreadLocal<MessageDigest> THREAD_MESSAGE_DIGEST = new ThreadLocal<MessageDigest>() { @Override protected MessageDigest initialValue() { try { return MessageDigest.getInstance(HASH_ALGORITHM); } catch (NoSuchAlgorithmException e) { throw new ExceptionInInitializerError(e); } } }; private static final String HEX_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; private static final BigInteger N = new BigInteger(HEX_N, 16); private static final BigInteger g = BigInteger.valueOf(2); private static final SecureRandom SECURE_RANDOM; static { try { SECURE_RANDOM = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { throw new ExceptionInInitializerError(e); } } private static final int SALT_LENGTH_BITS = 128; public BigInteger getSalt() { return salt; } public BigInteger getVerifier() { return verifier; } public deviceSRP(String deviceGroupKey, String deviceKey, String password) { byte[] deviceKeyHash = getUserIdHash(deviceGroupKey, deviceKey, password); salt = new BigInteger(SALT_LENGTH_BITS, SECURE_RANDOM); verifier = calcVerifier(salt, deviceKeyHash); } private static BigInteger calcVerifier(BigInteger salt, byte[] userIdHash) { begin(); update(salt); update(userIdHash); byte[] digest = end(); BigInteger x = new BigInteger(1, digest); return g.modPow(x, N); } private byte[] getUserIdHash(String poolName, String userName, String password) { begin(); update(poolName, userName, ":", password); return end(); } public static void begin() { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); md.reset(); } public static byte[] end() { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); return md.digest(); } public static void update(String... strings) { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); for (String s : strings) { if (s != null) { md.update(s.getBytes(StringUtils.UTF8)); } } } public static void update(String s) { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); if (s != null) { md.update(s.getBytes(StringUtils.UTF8)); } } public static void update(BigInteger... bigInts) { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); for (BigInteger n : bigInts) { if (n != null) { md.update(n.toByteArray()); } } } public static void update(BigInteger n) { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); if (n != null) { md.update(n.toByteArray()); } } public static void update(ByteBuffer b) { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); if (b != null) { md.update(b.array()); } } public static void update(byte[] b) { MessageDigest md = THREAD_MESSAGE_DIGEST.get(); if (b != null) { md.update(b); } } } /** * Returns a string with random characters. * * @return a string with random alpha-numeric characters.s */ public static String generateRandomString() { UUID uuid = UUID.randomUUID(); return String.valueOf(uuid); } }