in src/main/java/com/awslabs/aws/iot/websockets/BasicMqttOverWebsocketsProvider.java [125:265]
public String getMqttOverWebsocketsUriString(Optional<MqttOverWebsocketsUriConfig> optionalMqttOverWebsocketsUriConfig) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
Optional<String> optionalScopeDownJson = Optional.empty();
if (optionalMqttOverWebsocketsUriConfig.isPresent()) {
// We have a websockets config, have they've specified both the policy JSON and the policy object?
MqttOverWebsocketsUriConfig mqttOverWebsocketsUriConfig = optionalMqttOverWebsocketsUriConfig.get();
if (mqttOverWebsocketsUriConfig.optionalScopeDownPolicy().isPresent() &&
mqttOverWebsocketsUriConfig.optionalScopeDownPolicyJson().isPresent()) {
throw new RuntimeException("Scope down policy object and scope down policy JSON can not be used simultaneously. Use either a scope down policy object or scope down policy JSON but not both.");
}
if (mqttOverWebsocketsUriConfig.optionalScopeDownPolicy().isPresent()) {
optionalScopeDownJson = mqttOverWebsocketsUriConfig.optionalScopeDownPolicy().map(ScopeDownPolicy::toString);
} else if (mqttOverWebsocketsUriConfig.optionalScopeDownPolicyJson().isPresent()) {
optionalScopeDownJson = mqttOverWebsocketsUriConfig.optionalScopeDownPolicyJson();
}
}
long time = System.currentTimeMillis();
DateTime dateTime = new DateTime(time);
String dateStamp = getDateStamp(dateTime);
String amzdate = getAmzDate(dateTime);
String service = "iotdata";
Optional<Region> optionalRegion = optionalMqttOverWebsocketsUriConfig.flatMap(MqttOverWebsocketsUriConfig::optionalRegion);
Optional<AwsCredentialsProviderChain> optionalAwsCredentialsProviderChain = optionalMqttOverWebsocketsUriConfig.flatMap(MqttOverWebsocketsUriConfig::optionalAwsCredentialsProviderChain);
Region region = optionalRegion.orElseGet(this::getDefaultRegionString);
String regionString = region.toString();
String clientEndpoint = optionalMqttOverWebsocketsUriConfig
.flatMap(MqttOverWebsocketsUriConfig::optionalEndpointAddress)
.flatMap(EndpointAddress::getEndpointAddress)
.orElseGet(() -> getEndpointAddressForRegion(optionalRegion));
AwsCredentials awsCredentials;
String awsAccessKeyId;
String awsSecretAccessKey;
Optional<String> optionalSessionToken = Optional.empty();
Optional<String> optionalRoleToAssume = optionalMqttOverWebsocketsUriConfig
.flatMap(MqttOverWebsocketsUriConfig::optionalRoleToAssume)
.flatMap(RoleToAssume::getRoleToAssume);
StsClient stsClient = getStsClient.apply(optionalAwsCredentialsProviderChain, optionalRegion);
if (!optionalRoleToAssume.isPresent()) {
if (optionalScopeDownJson.isPresent()) {
// There is a scope down policy, get a federation token with it
// Trim the UUID down to a size that STS will accept
String name = UUID.randomUUID().toString().substring(0, 31);
GetFederationTokenRequest getFederationTokenRequest = GetFederationTokenRequest.builder()
.name(name)
.policy(optionalScopeDownJson.get())
.build();
GetFederationTokenResponse getFederationTokenResponse = stsClient.getFederationToken(getFederationTokenRequest);
Credentials credentials = getFederationTokenResponse.credentials();
awsAccessKeyId = credentials.accessKeyId();
awsSecretAccessKey = credentials.secretAccessKey();
optionalSessionToken = Optional.of(credentials.sessionToken());
} else {
// No scope down policy, just use the user's existing permissions
awsCredentials = credentialsProvider.resolveCredentials();
awsAccessKeyId = awsCredentials.accessKeyId();
awsSecretAccessKey = awsCredentials.secretAccessKey();
if (awsCredentials instanceof AwsSessionCredentials) {
optionalSessionToken = Optional.of(((AwsSessionCredentials) awsCredentials).sessionToken());
}
}
} else {
// Assume a new role
String roleArn = optionalRoleToAssume.get();
if (!roleArn.startsWith(ARN_AWS_IAM)) {
// The role coming from the environment will be the full ARN, if this is just the role name add the proper prefix
String accountId = getAccountId.apply(stsClient);
roleArn = ARN_AWS_IAM + accountId + ":role/" + roleArn;
}
log.debug("Attempting to assume role: " + roleArn);
AssumeRoleRequest.Builder assumeRoleRequestBuilder = AssumeRoleRequest.builder()
.roleArn(roleArn)
.roleSessionName("aws-iot-core-websockets-java");
// Add the scope down policy if there is one
optionalScopeDownJson.ifPresent(assumeRoleRequestBuilder::policy);
AssumeRoleRequest assumeRoleRequest = assumeRoleRequestBuilder.build();
AssumeRoleResponse assumeRoleResult = stsClient.assumeRole(assumeRoleRequest);
Credentials credentials = assumeRoleResult.credentials();
awsSecretAccessKey = credentials.secretAccessKey();
awsAccessKeyId = credentials.accessKeyId();
optionalSessionToken = Optional.of(credentials.sessionToken());
}
String algorithm = "AWS4-HMAC-SHA256";
String method = "GET";
String canonicalUri = "/mqtt";
String credentialScope = dateStamp + "/" + regionString + "/" + service + "/" + "aws4_request";
Tuple2<String, String> xAmzAlgorithm = Tuple.of("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
Tuple2<String, String> xAmzCredential = Tuple.of("X-Amz-Credential", URLEncoder.encode(awsAccessKeyId + "/" + credentialScope, StandardCharsets.UTF_8));
Tuple2<String, String> xAmzDate = Tuple.of("X-Amz-Date", amzdate);
Tuple2<String, String> xAmzSignedHeaders = Tuple.of(X_AMZ_SIGNED_HEADERS, "host");
String canonicalQueryString = String.join("&",
xAmzAlgorithm.apply(this::tupleToParameter),
xAmzCredential.apply(this::tupleToParameter),
xAmzDate.apply(this::tupleToParameter),
xAmzSignedHeaders.apply(this::tupleToParameter));
String canonicalHeaders = "host:" + clientEndpoint + ":443\n";
String payloadHash = sha256("");
String canonicalRequest = method + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\nhost\n" + payloadHash;
String stringToSign = algorithm + "\n" + amzdate + "\n" + credentialScope + "\n" + sha256(canonicalRequest);
byte[] signingKey = getSignatureKey(awsSecretAccessKey, dateStamp, regionString, service);
String signature = sign(signingKey, stringToSign);
Tuple2<String, String> xAmzSignature = Tuple.of("X-Amz-Signature", signature);
canonicalQueryString = String.join("&", canonicalQueryString,
xAmzSignature.apply(this::tupleToParameter));
if (optionalSessionToken.isPresent()) {
Tuple2<String, String> xAmzSecurityToken = Tuple.of("X-Amz-Security-Token", URLEncoder.encode(optionalSessionToken.get(), StandardCharsets.UTF_8));
canonicalQueryString = String.join("&",
canonicalQueryString,
xAmzSecurityToken.apply(this::tupleToParameter));
}
return "wss://" + clientEndpoint + canonicalUri + "?" + canonicalQueryString;
}