├── .gitignore
├── src
└── main
│ └── java
│ └── searchfast
│ └── cloud
│ └── auth
│ └── cognito
│ ├── AWSPasswordAuthContext.java
│ ├── AWSClientEvidenceContext.java
│ ├── AWSDeviceContext.java
│ ├── AWSPasswordScramblingRoutine.java
│ ├── AWSClientEvidenceRoutine.java
│ ├── AWSDeviceEvidenceContext.java
│ ├── AWSCryptoSettings.java
│ ├── AWSCryptoUtils.java
│ ├── AWSUserHashRoutine.java
│ └── AWSCognitoSession.java
├── settings.xml
├── pom.xml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /*.iml
3 | /target/
4 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSPasswordAuthContext.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import java.math.BigInteger;
4 |
5 | public class AWSPasswordAuthContext {
6 |
7 | public final BigInteger A;
8 | public final BigInteger B;
9 |
10 | public AWSPasswordAuthContext(BigInteger A, BigInteger B) {
11 | this.A = A;
12 | this.B = B;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSClientEvidenceContext.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import java.math.BigInteger;
4 |
5 | /**
6 | * Stores public keys A, B and a session key S
7 | */
8 | public class AWSClientEvidenceContext extends AWSPasswordAuthContext {
9 |
10 | public final BigInteger S;
11 |
12 | public AWSClientEvidenceContext(BigInteger A, BigInteger B, BigInteger S) {
13 | super(A, B);
14 | this.S = S;
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ossrh
5 |
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ossrh
15 | agalera
16 | {TyS7BWPiky8HvIY491VyYwzJFf2bEU3a77QOsXRRFMA=}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSDeviceContext.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | /**
4 | * Stores device's password, salt and secret
5 | */
6 | public class AWSDeviceContext {
7 |
8 | private final String passwordVerifier;
9 | private final String salt;
10 | private final String secret;
11 |
12 | public AWSDeviceContext(String passwordVerifier, String salt, String secret) {
13 | this.passwordVerifier = passwordVerifier;
14 | this.salt = salt;
15 | this.secret = secret;
16 | }
17 |
18 | public String getPasswordVerifier() {
19 | return passwordVerifier;
20 | }
21 |
22 | public String getSalt() {
23 | return salt;
24 | }
25 |
26 | public String getSecret() {
27 | return secret;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSPasswordScramblingRoutine.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import java.math.BigInteger;
4 | import java.security.MessageDigest;
5 |
6 | /**
7 | * Public keys hash routine
8 | */
9 | public class AWSPasswordScramblingRoutine {
10 |
11 | private final AWSCryptoSettings config;
12 |
13 | public AWSPasswordScramblingRoutine(AWSCryptoSettings config) {
14 | this.config = config;
15 | }
16 |
17 | /**
18 | * Computes random scrambling parameter
19 | * u = H(A, B)
20 | */
21 | public BigInteger computeHash(AWSPasswordAuthContext ctx) {
22 | MessageDigest messageDigest = config.getMessageDigestInstance();
23 | messageDigest.update(ctx.A.toByteArray());
24 | BigInteger u = new BigInteger(1, messageDigest.digest(ctx.B.toByteArray()));
25 | if (u.equals(BigInteger.ZERO)) {
26 | throw new IllegalStateException("Hash of A and B cannot be zero");
27 | }
28 |
29 | return u;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSClientEvidenceRoutine.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import javax.crypto.Mac;
4 | import javax.crypto.spec.SecretKeySpec;
5 | import java.math.BigInteger;
6 | import java.nio.charset.StandardCharsets;
7 | import java.security.InvalidKeyException;
8 |
9 | /**
10 | * Computes client evidence key as expected by Cognito Idenity Pool
11 | */
12 | public class AWSClientEvidenceRoutine {
13 |
14 | private static final int DERIVED_KEY_SIZE = 16;
15 | private static final byte[] DERIVED_KEY_INFO = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8);
16 |
17 | private final AWSCryptoSettings settings;
18 | private final AWSPasswordScramblingRoutine hashedKeysRoutine;
19 |
20 | public AWSClientEvidenceRoutine(AWSCryptoSettings cryptoSettings,
21 | AWSPasswordScramblingRoutine hashedKeysRoutine) {
22 | this.settings = cryptoSettings;
23 | this.hashedKeysRoutine = hashedKeysRoutine;
24 | }
25 |
26 | public byte[] computeClientEvidenceKey(AWSClientEvidenceContext ctx) {
27 | byte[] result = new byte[DERIVED_KEY_SIZE];
28 | try {
29 | Mac mac = settings.getMacInstance();
30 |
31 | BigInteger u = hashedKeysRoutine.computeHash(new AWSPasswordAuthContext(ctx.A, ctx.B));
32 | byte[] scramble = u.toByteArray();
33 |
34 | mac.init(new SecretKeySpec(scramble, mac.getAlgorithm()));
35 | byte[] raw = mac.doFinal(ctx.S.toByteArray());
36 |
37 | SecretKeySpec secretKey = new SecretKeySpec(raw, mac.getAlgorithm());
38 |
39 | Mac ex = settings.getMacInstance();
40 | ex.init(secretKey);
41 |
42 | byte[] t = new byte[0];
43 | int loc = 0;
44 |
45 | for(byte i = 1; loc < DERIVED_KEY_SIZE; ++i) {
46 | ex.update(t);
47 | ex.update(DERIVED_KEY_INFO);
48 | ex.update(i);
49 | t = ex.doFinal();
50 |
51 | for(int x = 0; x < t.length && loc < DERIVED_KEY_SIZE; ++loc) {
52 | result[loc] = t[x];
53 | ++x;
54 | }
55 | }
56 | } catch (InvalidKeyException e) {
57 | throw new IllegalArgumentException(e);
58 | }
59 |
60 | return result;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSDeviceEvidenceContext.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import java.math.BigInteger;
4 | import java.nio.charset.StandardCharsets;
5 | import java.security.MessageDigest;
6 | import java.util.Arrays;
7 | import java.util.Base64;
8 | import java.util.List;
9 |
10 | /**
11 | * Computes device evidence context based on device key and group key
12 | */
13 | public class AWSDeviceEvidenceContext {
14 |
15 | public static String SALT_PARAMETER = "salt";
16 | public static String VERIFIER_PARAMETER = "verifier";
17 | public static String SECRET_PARAMETER = "secret";
18 |
19 | private final AWSCryptoSettings cryptoSettings;
20 |
21 | public AWSDeviceEvidenceContext(AWSCryptoSettings cryptoSettings) {
22 | this.cryptoSettings = cryptoSettings;
23 | }
24 |
25 | public AWSDeviceContext getDeviceContext(String deviceKey, String deviceGroupKey) {
26 | String deviceSecret = cryptoSettings.generateRandomString();
27 | final byte[] deviceKeyHash = getUserIdHash(deviceGroupKey, deviceKey, deviceSecret);
28 | byte[] salt = cryptoSettings.randomBigInteger().toByteArray();
29 | byte[] srpVerifier = generateVerifier(salt, deviceKeyHash).toByteArray();
30 |
31 | AWSDeviceContext deviceContext = new AWSDeviceContext(new String(Base64.getEncoder().encode(srpVerifier)),
32 | new String(Base64.getEncoder().encode(salt)),
33 | deviceSecret);
34 | return deviceContext;
35 | }
36 |
37 | private byte[] getUserIdHash(String poolName, String userName, String password) {
38 | MessageDigest md = cryptoSettings.getMessageDigestInstance();
39 | md.reset();
40 |
41 | List hashedParts = Arrays.asList(new String[] { poolName, userName, ":", password});
42 | hashedParts.forEach(p -> {
43 | if (p != null) {
44 | md.update(p.getBytes(StandardCharsets.UTF_8));
45 | }
46 | });
47 |
48 | return md.digest();
49 | }
50 |
51 | private BigInteger generateVerifier(byte[] salt, byte[] userIdHash) {
52 | MessageDigest md = cryptoSettings.getMessageDigestInstance();
53 | md.reset();
54 |
55 | md.update(salt);
56 | md.update(userIdHash);
57 |
58 | final byte[] digest = md.digest();
59 | final BigInteger x = new BigInteger(1, digest);
60 | return AWSCryptoSettings.g.modPow(x, AWSCryptoSettings.N);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSCryptoSettings.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import javax.crypto.Mac;
4 | import java.math.BigInteger;
5 | import java.security.MessageDigest;
6 | import java.security.NoSuchAlgorithmException;
7 | import java.security.SecureRandom;
8 | import java.util.UUID;
9 |
10 | public class AWSCryptoSettings {
11 |
12 | private static final String HEX_N =
13 | "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
14 | + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
15 | + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
16 | + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
17 | + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
18 | + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
19 | + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
20 | + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
21 | + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
22 | + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
23 | + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
24 | + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
25 | + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
26 | + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
27 | + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
28 | + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
29 | public static final BigInteger N = new BigInteger(HEX_N, 16);
30 | public static final BigInteger g = BigInteger.valueOf(2);
31 |
32 | private static final String ALGORITHM = "SHA-256";
33 | private static final String MAC_ALGORITHM = "HmacSHA256";
34 | private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
35 | private static final int SALT_LENGTH_BITS = 128;
36 |
37 | /**
38 | * Returns new Message Digest based on the predefined
39 | * algorithm
40 | */
41 | public MessageDigest getMessageDigestInstance() {
42 |
43 | try {
44 | return MessageDigest.getInstance(ALGORITHM);
45 | } catch (NoSuchAlgorithmException e) {
46 | throw new IllegalStateException(e);
47 | }
48 | }
49 |
50 | /**
51 | * Creates new instance of MAC
52 | */
53 | public Mac getMacInstance() {
54 | try {
55 | return Mac.getInstance(MAC_ALGORITHM);
56 | } catch (NoSuchAlgorithmException e) {
57 | throw new IllegalStateException(e);
58 | }
59 | }
60 |
61 | public SecureRandom getSecureRandomInstance() {
62 | try {
63 | return SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
64 | } catch (NoSuchAlgorithmException e) {
65 | throw new IllegalStateException(e);
66 | }
67 | }
68 |
69 | public BigInteger randomBigInteger() {
70 | return new BigInteger(SALT_LENGTH_BITS, getSecureRandomInstance());
71 | }
72 |
73 | public String generateRandomString() {
74 | final UUID uuid = UUID.randomUUID();
75 | return String.valueOf(uuid);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSCryptoUtils.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import java.math.BigInteger;
4 | import java.security.MessageDigest;
5 | import java.security.SecureRandom;
6 |
7 | /**
8 | * SRP utility methods
9 | * - compute private key a
10 | * - compute public key A from a
11 | * - compute multiplier k
12 | * - compute session key S
13 | */
14 | public class AWSCryptoUtils {
15 |
16 | private final SecureRandom random = new SecureRandom();
17 | private final AWSCryptoSettings settings;
18 |
19 | public AWSCryptoUtils(AWSCryptoSettings settings) {
20 | this.settings = settings;
21 | }
22 |
23 | public BigInteger computeSessionKey(final BigInteger k,
24 | final BigInteger x,
25 | final BigInteger u,
26 | final BigInteger a,
27 | final BigInteger B) {
28 |
29 | final BigInteger exp = u.multiply(x).add(a);
30 | final BigInteger tmp = settings.g.modPow(x, settings.N).multiply(k);
31 | return B.subtract(tmp).modPow(exp, settings.N);
32 | }
33 |
34 | /**
35 | * Computes multiplier, k = H(N, g)
36 | */
37 | public BigInteger computeK() {
38 | MessageDigest messageDigest = settings.getMessageDigestInstance();
39 | messageDigest.update(settings.N.toByteArray());
40 | byte[] digest = messageDigest.digest(settings.g.toByteArray());
41 |
42 | return new BigInteger(1, digest);
43 | }
44 |
45 | /**
46 | *
47 | */
48 | public BigInteger computeEphemeralKey(BigInteger privateKey) {
49 | return settings.g.modPow(privateKey, settings.N);
50 | }
51 |
52 | /**
53 | * Creates random private key a
54 | */
55 | public BigInteger generatePrivateKey() {
56 |
57 | final int minBits = Math.min(256, settings.N.bitLength() / 2);
58 |
59 | BigInteger min = BigInteger.ONE.shiftLeft(minBits - 1);
60 | BigInteger max = settings.N.subtract(BigInteger.ONE);
61 |
62 | return createRandomBigIntegerInRange(min, max, random);
63 | }
64 |
65 | private static BigInteger createRandomBigIntegerInRange(final BigInteger min,
66 | final BigInteger max,
67 | final SecureRandom random) {
68 |
69 | final int cmp = min.compareTo(max);
70 |
71 | if (cmp >= 0) {
72 |
73 | if (cmp > 0)
74 | throw new IllegalArgumentException("'min' may not be greater than 'max'");
75 |
76 | return min;
77 | }
78 |
79 | if (min.bitLength() > max.bitLength() / 2)
80 | return createRandomBigIntegerInRange(BigInteger.ZERO, max.subtract(min), random).add(min);
81 |
82 | final int MAX_ITERATIONS = 1000;
83 |
84 | for (int i = 0; i < MAX_ITERATIONS; ++i) {
85 |
86 | BigInteger x = new BigInteger(max.bitLength(), random);
87 |
88 | if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0)
89 | return x;
90 | }
91 |
92 | return new BigInteger(max.subtract(min).bitLength() - 1, random).add(min);
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSUserHashRoutine.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import javax.crypto.Mac;
4 | import javax.crypto.spec.SecretKeySpec;
5 | import java.math.BigInteger;
6 | import java.nio.charset.StandardCharsets;
7 | import java.security.InvalidKeyException;
8 | import java.security.MessageDigest;
9 | import java.text.SimpleDateFormat;
10 | import java.util.Base64;
11 | import java.util.Date;
12 | import java.util.HashMap;
13 | import java.util.Locale;
14 | import java.util.Map;
15 | import java.util.SimpleTimeZone;
16 |
17 | /**
18 | * User hashing functions
19 | * - provides x hashing routine (x = H(salt | H(poolName | userId | ":" | password)))
20 | * - provides challenge digest routine
21 | */
22 | public class AWSUserHashRoutine {
23 |
24 | private final String username;
25 | private final String password;
26 | private final String poolName;
27 | private final AWSCryptoSettings config;
28 | private final SimpleDateFormat simpleDateFormat;
29 |
30 | public AWSUserHashRoutine(AWSCryptoSettings config,
31 | String username,
32 | String password,
33 | String userPoolId) {
34 | this.config = config;
35 | this.username = username;
36 | this.password = password;
37 | this.poolName = userPoolId.split("_", 2)[ 1 ];
38 |
39 | this.simpleDateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy", Locale.US);
40 | this.simpleDateFormat.setTimeZone(new SimpleTimeZone(SimpleTimeZone.UTC_TIME, "UTC"));
41 | }
42 |
43 | /**
44 | * Computes user hash routine based on provided salt valute
45 | */
46 | public BigInteger computeUserHash(byte[] salt) {
47 | MessageDigest messageDigest = config.getMessageDigestInstance();
48 | messageDigest.update(poolName.getBytes(StandardCharsets.UTF_8));
49 | messageDigest.update(username.getBytes(StandardCharsets.UTF_8));
50 | messageDigest.update(":".getBytes(StandardCharsets.UTF_8));
51 |
52 | byte [] userIdHash = messageDigest.digest(password.getBytes(StandardCharsets.UTF_8));
53 | messageDigest.reset();
54 | messageDigest.update(salt);
55 |
56 | return new BigInteger(1, messageDigest.digest(userIdHash));
57 | }
58 |
59 | /**
60 | * Computes hashed user challenge response that is going to be
61 | * encrypted by the provided key and passing back the secret block string
62 | */
63 | public Map computeChallengeResponse(byte[] key, String secretBlockString) {
64 | Mac mac = config.getMacInstance();
65 | SecretKeySpec keySpec = new SecretKeySpec(key, mac.getAlgorithm());
66 | try {
67 | mac.init(keySpec);
68 |
69 | mac.update(poolName.getBytes(StandardCharsets.UTF_8));
70 | mac.update(username.getBytes(StandardCharsets.UTF_8));
71 |
72 | byte[] secretBlock = Base64.getDecoder().decode(secretBlockString);
73 | mac.update(secretBlock);
74 |
75 | String dateString = simpleDateFormat.format(new Date());
76 | byte[] hmac = mac.doFinal(dateString.getBytes(StandardCharsets.UTF_8));
77 |
78 | Map responses = new HashMap<>();
79 | responses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlockString);
80 | responses.put("PASSWORD_CLAIM_SIGNATURE", new String(Base64.getEncoder().encode(hmac), StandardCharsets.UTF_8));
81 | responses.put("TIMESTAMP", dateString);
82 | responses.put("USERNAME", username);
83 |
84 | return responses;
85 | } catch (InvalidKeyException e) {
86 | throw new IllegalStateException(e);
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/searchfast/cloud/auth/cognito/AWSCognitoSession.java:
--------------------------------------------------------------------------------
1 | package searchfast.cloud.auth.cognito;
2 |
3 | import java.math.BigInteger;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | /**
8 | * This class provides necessary method to login
9 | * a Cognito user into the system in two steps by usging SRP protocol.
10 | *
11 | * 1. Step sends user's name, password, pool id and ephemeral key to the server
12 | * Properties in the same order that are being sent to the server are
13 | * - USERNAME
14 | * - PASSWORD
15 | * - SRP_A
16 | * - UserPoolId
17 | * 2. Step expects to compute response challenge based on parameters sent
18 | * by the server. Expected keys are
19 | * - SRP_B
20 | * - SALT
21 | * - SECRET_BLOCK
22 | *
23 | * The response from the second steps contains following keys
24 | * - PASSWORD_CLAIM_SECRET_BLOCK
25 | * - PASSWORD_CLAIM_SIGNATURE
26 | * - TIMESTAMP
27 | * - USERNAME
28 | */
29 | public class AWSCognitoSession {
30 |
31 | // Private key
32 | private BigInteger a;
33 | // Public key
34 | private BigInteger A;
35 | // Multiplier
36 | private BigInteger k;
37 |
38 | private String username;
39 |
40 | private final AWSUserHashRoutine userHashRoutine;
41 | private final AWSClientEvidenceRoutine clientEvidenceRoutine;
42 | private final AWSPasswordScramblingRoutine passwordScramblingRoutine;
43 | private final AWSDeviceEvidenceContext deviceEvidenceContext;
44 |
45 | private final AWSCryptoUtils cryptoUtils;
46 |
47 | public AWSCognitoSession(AWSCryptoSettings cryptoSettings,
48 | String username,
49 | String password,
50 | String poolId) {
51 | this.username = username;
52 |
53 | this.passwordScramblingRoutine = new AWSPasswordScramblingRoutine(cryptoSettings);
54 | this.userHashRoutine = new AWSUserHashRoutine(cryptoSettings, username, password, poolId);
55 | this.clientEvidenceRoutine = new AWSClientEvidenceRoutine(cryptoSettings, passwordScramblingRoutine);
56 | this.cryptoUtils = new AWSCryptoUtils(cryptoSettings);
57 | this.deviceEvidenceContext = new AWSDeviceEvidenceContext(cryptoSettings);
58 |
59 | // Generate client private key, ephemeral and multiplier
60 | a = cryptoUtils.generatePrivateKey();
61 | A = cryptoUtils.computeEphemeralKey(a);
62 | k = cryptoUtils.computeK();
63 | }
64 |
65 | public synchronized Map step1() {
66 | Map authParameters = new HashMap<>();
67 | authParameters.put("USERNAME", username);
68 | authParameters.put("SRP_A", A.toString(16));
69 |
70 | return authParameters;
71 | }
72 |
73 | public synchronized Map step2(Map params) {
74 | BigInteger B = new BigInteger(params.get("SRP_B"), 16);
75 | BigInteger salt = new BigInteger(params.get("SALT"), 16);
76 | String secretBlockString = params.get("SECRET_BLOCK");
77 |
78 | // Compute random scrambling parameter
79 | // u = H(A, B)
80 | BigInteger u = passwordScramblingRoutine.computeHash(new AWSPasswordAuthContext(A, B));
81 |
82 | // Compute session key
83 | // x = H(salt | H(poolName | userId | ":" | password))
84 | BigInteger x = userHashRoutine.computeUserHash(salt.toByteArray());
85 | BigInteger S = cryptoUtils.computeSessionKey(k, x, u, a, B);
86 |
87 | // Compute proof of session
88 | AWSClientEvidenceContext ctx = new AWSClientEvidenceContext(A, B, S);
89 | byte[] key = clientEvidenceRoutine.computeClientEvidenceKey(ctx);
90 |
91 | return userHashRoutine.computeChallengeResponse(key, secretBlockString);
92 | }
93 |
94 | public synchronized AWSDeviceContext getDeviceContext(String deviceKey, String deviceGroup) {
95 | return deviceEvidenceContext.getDeviceContext(deviceKey, deviceGroup);
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.github.kink80
8 | cognitoauth
9 | 1.0
10 | jar
11 |
12 | cognitoauth
13 | This library aims to fill the gap for pure Java based apps that want to interact with User Pools in AWS
14 | Cognito.
15 |
16 | https://github.com/kink80/cognitoauth
17 |
18 |
19 | 1.8
20 | 1.8
21 |
22 |
23 |
24 |
25 | Apache License, Version 2.0
26 | http://www.apache.org/licenses/LICENSE-2.0.txt
27 | repo
28 |
29 |
30 |
31 |
32 | https://github.com/kink80/cognitoauth
33 |
34 |
35 |
36 |
37 |
38 | org.apache.maven.plugins
39 | maven-source-plugin
40 | 2.2.1
41 |
42 |
43 | attach-sources
44 |
45 | jar-no-fork
46 |
47 |
48 |
49 |
50 |
51 | org.apache.maven.plugins
52 | maven-javadoc-plugin
53 | 2.9.1
54 |
55 |
56 | attach-javadocs
57 |
58 | jar
59 |
60 |
61 |
62 |
63 |
64 | org.apache.maven.plugins
65 | maven-compiler-plugin
66 |
67 | 1.8
68 | 1.8
69 |
70 |
71 |
72 | org.sonatype.plugins
73 | nexus-staging-maven-plugin
74 | 1.6.7
75 | true
76 |
77 | ossrh
78 | https://oss.sonatype.org/
79 | true
80 |
81 |
82 |
83 | org.apache.maven.plugins
84 | maven-gpg-plugin
85 | 1.5
86 |
87 |
88 | sign-artifacts
89 | verify
90 |
91 | sign
92 |
93 |
94 |
95 |
96 |
97 | org.sonatype.plugins
98 | nexus-staging-maven-plugin
99 | 1.6.7
100 | true
101 |
102 | ossrh
103 | https://oss.sonatype.org/
104 | true
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | ossrh
113 | https://oss.sonatype.org/content/repositories/snapshots
114 |
115 |
116 | ossrh
117 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
118 |
119 |
120 |
121 |
122 |
123 | agalera
124 | Adrian Galera
125 | agalera@sensefields.com
126 | sensefields
127 | http://www.sensefields.com/
128 |
129 | Developer
130 |
131 | Europe/Madrid
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AWS Cognito User pool authentication helper
2 |
3 | This library aims to fill the gap for pure Java based apps that want to interact with User Pools in AWS Cognito. The goal is to support user login into federated identity pool that
4 | would allow this user to be provided with AWS temporary credentials. These credentials can be used later on in order to consume other AWS resources.
5 |
6 |
7 | ### Prerequisities
8 |
9 | - A user pool exists in AWS Cognito service
10 | - The user pools has an client application registered with it
11 | - The client app does not need to have app secret associated with it
12 | - An identity pool exists in AWS Cognito Federated identities
13 | - Authentication provider within this identity pool points to
14 | the user pool id and app client id defined in the previous step
15 | - A user is registered into the user pool
16 | - IAM security role exists and users from the given pool are permitted to use AWS resources, the policy itself might look like this
17 | if you're accessing S3 resources
18 | ~~~~
19 | {
20 | "Version": "2012-10-17",
21 | "Statement": [
22 | {
23 | "Effect": "Allow",
24 | "Action": [
25 | "mobileanalytics:PutEvents",
26 | "cognito-sync:*",
27 | "cognito-identity:*"
28 | ],
29 | "Resource": [
30 | "*"
31 | ]
32 | },
33 | {
34 | "Action": [
35 | "s3:ListBucket"
36 | ],
37 | "Effect": "Allow",
38 | "Resource": [
39 | "arn:aws:s3:::your-bucket-name"
40 | ],
41 | "Condition": {
42 | "StringLike": {
43 | "s3:prefix": [
44 | "${cognito-identity.amazonaws.com:sub}/*"
45 | ]
46 | }
47 | }
48 | },
49 | {
50 | "Action": [
51 | "s3:GetObject",
52 | "s3:PutObject"
53 | ],
54 | "Effect": "Allow",
55 | "Resource": [
56 | "arn:aws:s3:::your-bucket-name/${cognito-identity.amazonaws.com:sub}/*"
57 | ]
58 | }
59 | ]
60 | }
61 | ~~~~
62 | - IAM trust relationship between the role and identity pool exists, here's a sample policy
63 | ~~~~
64 | {
65 | "Version": "2012-10-17",
66 | "Statement": [
67 | {
68 | "Effect": "Allow",
69 | "Principal": {
70 | "Federated": "cognito-identity.amazonaws.com"
71 | },
72 | "Action": "sts:AssumeRoleWithWebIdentity",
73 | "Condition": {
74 | "StringEquals": {
75 | "cognito-identity.amazonaws.com:aud": "identity pool id"
76 | },
77 | "ForAnyValue:StringLike": {
78 | "cognito-identity.amazonaws.com:amr": "authenticated"
79 | }
80 | }
81 | }
82 | ]
83 | }
84 | ~~~~
85 | ----
86 | ### Usage
87 |
88 | Note down following properties from your AWS account
89 |
90 | - user pool id
91 | - username
92 | - password
93 | - region
94 | - client application id
95 | - account id
96 | - identity pool id
97 | - endpoint of your idenity pool
98 |
99 | ----
100 |
101 | **Initialize cryptographic routines first and perform initial setup**
102 | ~~~~
103 | AWSCryptoSettings cryptoParams = new AWSCryptoSettings();
104 | AWSCognitoSession clientSession = new AWSCognitoSession(cryptoParams, "username", "password", "user pool id");
105 | ~~~~
106 |
107 | **Initiate authentication request**
108 | ~~~~
109 | WSCognitoIdentityProviderClient identityProviderClient = new AWSCognitoIdentityProviderClient(new AnonymousAWSCredentials());
110 | identityProviderClient.setRegion("region");
111 |
112 | InitiateAuthRequest authRequest = new InitiateAuthRequest()
113 | .withAuthFlow(AuthFlowType.USER_SRP_AUTH)
114 | .withClientId("client application id")
115 | .withAuthParameters(clientSession.step1());
116 | ~~~~
117 |
118 | **Respond to authentication challenge**
119 | ~~~~
120 | InitiateAuthResult authResult = identityProviderClient.initiateAuth(authRequest);
121 | Map params = authResult.getChallengeParameters();
122 | Map srpAuthResponses = clientSession.step2(params);
123 |
124 | RespondToAuthChallengeRequest respondToAuthChallengeRequest = new RespondToAuthChallengeRequest()
125 | .withChallengeName(authResult.getChallengeName())
126 | .withClientId("client application id")
127 | .withChallengeResponses(srpAuthResponses);
128 | RespondToAuthChallengeResult respondToAuthChallengeResult = identityProviderClient.respondToAuthChallenge(respondToAuthChallengeRequest);
129 | AuthenticationResultType authenticationResultType = respondToAuthChallengeResult.getAuthenticationResult();
130 | ~~~~
131 |
132 | **Your user pool can have devices enabled, in that case you need them using following routine**
133 | ~~~~
134 | if(authenticationResultType.getNewDeviceMetadata() != null) {
135 | NewDeviceMetadataType deviceMetadata = authenticationResultType.getNewDeviceMetadata();
136 |
137 | final AWSDeviceContext deviceContext = clientSession.getDeviceContext(deviceMetadata.getDeviceKey(),
138 | deviceMetadata.getDeviceGroupKey());
139 |
140 | final DeviceSecretVerifierConfigType deviceConfig = new DeviceSecretVerifierConfigType();
141 | deviceConfig.setPasswordVerifier(deviceContext.getPasswordVerifier());
142 | deviceConfig.setSalt(deviceContext.getSalt());
143 |
144 | ConfirmDeviceRequest confirmDeviceRequest = new ConfirmDeviceRequest();
145 | confirmDeviceRequest.setAccessToken(authenticationResultType.getAccessToken());
146 | confirmDeviceRequest.setDeviceKey(authenticationResultType.getNewDeviceMetadata().getDeviceKey());
147 | confirmDeviceRequest.setDeviceName("someDeviceName");
148 | confirmDeviceRequest.setDeviceSecretVerifierConfig(deviceConfig);
149 | ConfirmDeviceResult confirmDeviceResult = identityProviderClient.confirmDevice(confirmDeviceRequest);
150 | confirmDeviceResult.getUserConfirmationNecessary();
151 | }
152 | ~~~~
153 |
154 | You should be successfully authenticated at this stage, now let's obtain our AWS credentials through cognito.
155 |
156 | **Initialize Cognito identity client**
157 | ~~~~
158 | AmazonCognitoIdentityClient cognitoIdentityClient = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials());
159 | cognitoIdentityClient.setRegion("region");
160 | ~~~~
161 |
162 | **Obtain an ID from the Cogntito user pool**
163 | ~~~~
164 | Map loginsMap = new HashMap();
165 | // e.g. cognito-idp.eu-central-1.amazonaws.com/user_pool_id
166 | loginsMap.put("endpoint of your idenity pool/user pool id", authenticationResultType.getIdToken());
167 |
168 | GetIdRequest getIdRequest = new GetIdRequest().withAccountId("account id")
169 | .withIdentityPoolId("identity pool id)
170 | .withLogins(loginsMap);
171 |
172 | GetIdResult getIdResult = cognitoIdentityClient.getId(getIdRequest);
173 | ~~~~
174 |
175 | **And finally, get credentials**
176 | ~~~~
177 | GetCredentialsForIdentityRequest credentialsForIdentityRequest = new GetCredentialsForIdentityRequest()
178 | .withIdentityId(getIdResult.getIdentityId())
179 | .withLogins(loginsMap);
180 |
181 | GetCredentialsForIdentityResult credentialsForIdentityResult = cognitoIdentityClient.getCredentialsForIdentity(credentialsForIdentityRequest);
182 | credentialsForIdentityResult.getCredentials();
183 |
184 | AWSSessionCredentials sessionCredentials = new BasicSessionCredentials(
185 | credentialsForIdentityResult.getCredentials().getAccessKeyId(),
186 | credentialsForIdentityResult.getCredentials().getSecretKey(),
187 | credentialsForIdentityResult.getCredentials().getSessionToken());
188 | ~~~~
189 |
190 | ### Maven
191 |
192 | This project is available in Maven:
193 |
194 | groupId: com.github.kink80
195 |
196 | artifactId: cognitoauth
197 |
198 | version: 1.+
199 |
--------------------------------------------------------------------------------