com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoDeviceHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoDeviceHelper.java

Source

/*
 *  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 com.amazonaws.util.Base64;
import com.amazonaws.util.StringUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

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 {
    private static final Log LOGGER = LogFactory.getLog(CognitoDeviceHelper.class);
    private static final String COGNITO_DEVICE_CACHE = "CognitoIdentityProviderDeviceCache";
    private static final String COGNITO_DEVICE_KEY = "DeviceKey";
    private static final String COGNITO_DEVICE_GROUP_KEY = "DeviceGroupKey";
    private static final String COGNITO_DEVICE_SECRET = "DeviceSecret";
    /**
     * Default pagination limit.
     */
    public static final int DEFAULT_DEVICE_PAGINATION_LIMIT = 10;

    static deviceSRP srpCalculator = null;

    /**
     * Uses the Android class {@link 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 {
            final SharedPreferences cipCachedDeviceDetails = context
                    .getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0);
            if (cipCachedDeviceDetails != null && cipCachedDeviceDetails.contains(COGNITO_DEVICE_KEY)) {
                return cipCachedDeviceDetails.getString(COGNITO_DEVICE_KEY, null);
            }
        } catch (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
        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 {
            final SharedPreferences cipCachedDeviceDetails = context
                    .getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0);
            if (cipCachedDeviceDetails != null && cipCachedDeviceDetails.contains(COGNITO_DEVICE_SECRET)) {
                return cipCachedDeviceDetails.getString(COGNITO_DEVICE_SECRET, null);
            }
        } catch (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
        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 {
            final 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 (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
        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 {
            final SharedPreferences cipCachedDeviceDetails = context
                    .getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0);
            cipCachedDeviceDetails.edit().putString(COGNITO_DEVICE_KEY, deviceKey).apply();
        } catch (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
    }

    /**
     * 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 {
            final SharedPreferences cipCachedDeviceDetails = context
                    .getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0);
            cipCachedDeviceDetails.edit().putString(COGNITO_DEVICE_SECRET, deviceSecret).apply();
        } catch (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
    }

    /**
     * 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 {
            final SharedPreferences cipCachedDeviceDetails = context
                    .getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0);
            cipCachedDeviceDetails.edit().putString(COGNITO_DEVICE_GROUP_KEY, deviceGroupKey).apply();
        } catch (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
    }

    /**
     * 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 {
            final SharedPreferences cipCachedDeviceDetails = context
                    .getSharedPreferences(getDeviceDetailsCacheForUser(username, userPoolId), 0);
            cipCachedDeviceDetails.edit().clear().apply();
        } catch (final Exception e) {
            LOGGER.error("Error accessing SharedPreferences", e);
        }
    }

    /**
     * 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) {
        final Map<String, String> devVerfPars = new HashMap<String, String>();
        final String deviceSecret = generateRandomString();
        srpCalculator = new deviceSRP(deviceGroup, deviceKey, deviceSecret);
        final byte[] salt = srpCalculator.getSalt().toByteArray();
        final 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.
     */
    @SuppressWarnings("checkstyle:typename")
    public static class deviceSRP {
        private final BigInteger salt;
        private final 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 (final 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 GG = BigInteger.valueOf(2);

        private static final SecureRandom SECURE_RANDOM;

        static {
            try {
                SECURE_RANDOM = SecureRandom.getInstance("SHA1PRNG");
            } catch (final NoSuchAlgorithmException e) {
                throw new ExceptionInInitializerError(e);
            }
        }

        private static final int SALT_LENGTH_BITS = 128;

        /**
         * @return Salt used for SRP.
         */
        public BigInteger getSalt() {
            return salt;
        }

        /**
         * Returns the generated verifier.
         * @return verifier.
         */
        public BigInteger getVerifier() {
            return verifier;
        }

        /**
         * Helps to start the SRP validation of the device.
         * @param deviceGroupKey REQUIRED: Group assigned to the device.
         * @param deviceKey REQUIRED: Unique identifier assigned to the device. 
         * @param password REQUIRED: The device password.
         */
        public deviceSRP(String deviceGroupKey, String deviceKey, String password) {
            final byte[] deviceKeyHash = getUserIdHash(deviceGroupKey, deviceKey, password);

            salt = new BigInteger(SALT_LENGTH_BITS, SECURE_RANDOM);
            verifier = calcVerifier(salt, deviceKeyHash);
        }

        /**
         * Generates the SRP verifier.
         * @param salt REQUIRED: The random salt created by the service.
         * @param userIdHash REQIURED: Username hash.
         * @return verifier as a BigInteger.
         */
        private static BigInteger calcVerifier(BigInteger salt, byte[] userIdHash) {
            begin();
            update(salt);
            update(userIdHash);
            final byte[] digest = end();

            final BigInteger x = new BigInteger(1, digest);
            return GG.modPow(x, N);
        }

        /**
         * Computes the user hash.
         * @param poolName REQUIRED: The pool-id of the user.
         * @param userName REQUIRED: The internal username of the user.
         * @param password REQUIRED: The password intered by the user.
         * @return hash as a byte array.
         */
        private byte[] getUserIdHash(String poolName, String userName, String password) {
            begin();
            update(poolName, userName, ":", password);
            return end();
        }

        /**
         * Start byte digest for SRP.
         */
        public static void begin() {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            md.reset();
        }

        /**
         * Complete digest.
         * @return the digest as a byte array.
         */
        public static byte[] end() {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            return md.digest();
        }

        /**
         * Adds a series of strings to the digest.
         * @param strings REQUIRED: Strings to add.
         */
        public static void update(String... strings) {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            for (final String s : strings) {
                if (s != null) {
                    md.update(s.getBytes(StringUtils.UTF8));
                }
            }
        }

        /**
         * Adds a string to the digest.
         * @param s REQUIRED: String to add.
         */
        public static void update(String s) {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            if (s != null) {
                md.update(s.getBytes(StringUtils.UTF8));
            }
        }

        /**
         * Adds a series of BigIntegers to the digest.
         * @param bigInts REQUIRED: Numbers to add.
         */
        public static void update(BigInteger... bigInts) {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            for (final BigInteger n : bigInts) {
                if (n != null) {
                    md.update(n.toByteArray());
                }
            }
        }

        /**
         * Adds a BigInteger to the digest.
         * @param n REQUIRED: The number to add.
         */
        public static void update(BigInteger n) {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            if (n != null) {
                md.update(n.toByteArray());
            }
        }

        /**
         * Adds the contents of a byte-buffer to the digest.
         * @param b REQUIRED: bytes to add.
         */
        public static void update(ByteBuffer b) {
            final MessageDigest md = THREAD_MESSAGE_DIGEST.get();
            if (b != null) {
                md.update(b.array());
            }
        }

        /**
         * Adds a byte array to the digest.
         * @param b REQUIRED: bytes to add.
         */
        public static void update(byte[] b) {
            final 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() {
        final UUID uuid = UUID.randomUUID();
        return String.valueOf(uuid);
    }
}