1 /** 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * SPDX-License-Identifier: Apache-2.0. 4 */ 5 package software.amazon.awssdk.crt.auth.signing; 6 7 import java.util.function.Predicate; 8 import java.util.HashMap; 9 import java.util.Map; 10 11 import software.amazon.awssdk.crt.auth.credentials.Credentials; 12 import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider; 13 import software.amazon.awssdk.crt.CrtResource; 14 15 /** 16 * A class representing configuration related to signing something "signable" (an http request, a body chunk, a 17 * stream event) via an AWS signing process. 18 */ 19 public class AwsSigningConfig extends CrtResource { 20 21 /** 22 * What version of the AWS signing process should we use. 23 */ 24 public enum AwsSigningAlgorithm { 25 26 /** Standard AWS Sigv4 signing, based on AWS credentials and symmetric secrets */ 27 SIGV4(0), 28 29 /** AWS Sigv4a signing, based on ECDSA signatures */ 30 SIGV4_ASYMMETRIC(1), 31 32 /** AWS Sigv4 S3 Express signing */ 33 SIGV4_S3EXPRESS(2); 34 35 /** 36 * Constructs a Java enum value from the associated native enum value 37 * @param nativeValue native enum value 38 */ AwsSigningAlgorithm(int nativeValue)39 AwsSigningAlgorithm(int nativeValue) { 40 this.nativeValue = nativeValue; 41 } 42 43 /** 44 * Trivial Java Enum value to native enum value conversion function 45 * @return integer associated with this enum value 46 */ getNativeValue()47 public int getNativeValue() { return nativeValue; } 48 49 /** 50 * Creates a Java enum value from a native enum value as an integer 51 * @param value native enum value 52 * @return the corresponding Java enum value 53 */ getEnumValueFromInteger(int value)54 public static AwsSigningAlgorithm getEnumValueFromInteger(int value) { 55 AwsSigningAlgorithm enumValue = enumMapping.get(value); 56 if (enumValue != null) { 57 return enumValue; 58 } 59 60 throw new RuntimeException("Illegal signing algorithm value in signing configuration"); 61 } 62 buildEnumMapping()63 private static Map<Integer, AwsSigningAlgorithm> buildEnumMapping() { 64 Map<Integer, AwsSigningAlgorithm> enumMapping = new HashMap<Integer, AwsSigningAlgorithm>(); 65 enumMapping.put(SIGV4.getNativeValue(), SIGV4); 66 enumMapping.put(SIGV4_ASYMMETRIC.getNativeValue(), SIGV4_ASYMMETRIC); 67 enumMapping.put(SIGV4_S3EXPRESS.getNativeValue(), SIGV4_S3EXPRESS); 68 69 return enumMapping; 70 } 71 72 private int nativeValue; 73 74 private static Map<Integer, AwsSigningAlgorithm> enumMapping = buildEnumMapping(); 75 } 76 77 /** 78 * What sort of signature should be computed from the signable? 79 */ 80 public enum AwsSignatureType { 81 82 /** 83 * A signature for a full http request should be computed, with header updates applied to the signing result. 84 */ 85 HTTP_REQUEST_VIA_HEADERS(0), 86 87 /** 88 * A signature for a full http request should be computed, with query param updates applied to the signing result. 89 */ 90 HTTP_REQUEST_VIA_QUERY_PARAMS(1), 91 92 /** 93 * Compute a signature for a payload chunk. 94 */ 95 HTTP_REQUEST_CHUNK(2), 96 97 /** 98 * Compute a signature for an event stream event. 99 * 100 * This option is not yet supported. 101 */ 102 HTTP_REQUEST_EVENT(3), 103 104 /** 105 * Compute a signature for a payloads trailing headers. 106 */ 107 HTTP_REQUEST_TRAILING_HEADERS(6); 108 109 /** 110 * Constructs a Java enum value from a native enum value as an integer 111 * @param nativeValue native enum value 112 */ AwsSignatureType(int nativeValue)113 AwsSignatureType(int nativeValue) { 114 this.nativeValue = nativeValue; 115 } 116 117 /** 118 * Gets the native enum value as an integer that is associated with this Java enum value 119 * @return this value's associated native enum value 120 */ getNativeValue()121 public int getNativeValue() { return nativeValue; } 122 123 /** 124 * Creates a Java enum value from a native enum value as an integer 125 * @param value native enum value 126 * @return the corresponding Java enum value 127 */ getEnumValueFromInteger(int value)128 public static AwsSignatureType getEnumValueFromInteger(int value) { 129 AwsSignatureType enumValue = enumMapping.get(value); 130 if (enumValue != null) { 131 return enumValue; 132 } 133 134 throw new RuntimeException("Illegal signature type value in signing configuration"); 135 } 136 buildEnumMapping()137 private static Map<Integer, AwsSignatureType> buildEnumMapping() { 138 Map<Integer, AwsSignatureType> enumMapping = new HashMap<Integer, AwsSignatureType>(); 139 enumMapping.put(HTTP_REQUEST_VIA_HEADERS.getNativeValue(), HTTP_REQUEST_VIA_HEADERS); 140 enumMapping.put(HTTP_REQUEST_VIA_QUERY_PARAMS.getNativeValue(), HTTP_REQUEST_VIA_QUERY_PARAMS); 141 enumMapping.put(HTTP_REQUEST_CHUNK.getNativeValue(), HTTP_REQUEST_CHUNK); 142 enumMapping.put(HTTP_REQUEST_EVENT.getNativeValue(), HTTP_REQUEST_EVENT); 143 enumMapping.put(HTTP_REQUEST_TRAILING_HEADERS.getNativeValue(), HTTP_REQUEST_TRAILING_HEADERS); 144 145 return enumMapping; 146 } 147 148 private int nativeValue; 149 150 private static Map<Integer, AwsSignatureType> enumMapping = buildEnumMapping(); 151 } 152 153 /** 154 * A set of string constants for various canonical request payload values. If signed body header type is not NONE 155 * then the value will also be reflected in X-Amz-Content-Sha256 156 */ 157 public class AwsSignedBodyValue { 158 public static final String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; 159 public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; 160 public static final String STREAMING_UNSIGNED_PAYLOAD_TRAILER = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"; 161 public static final String STREAMING_AWS4_HMAC_SHA256_PAYLOAD = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"; 162 public static final String STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"; 163 public static final String STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER"; 164 public static final String STREAMING_AWS4_HMAC_SHA256_EVENTS = "STREAMING-AWS4-HMAC-SHA256-EVENTS"; 165 } 166 167 /** 168 * Controls if signing adds a header containing the canonical request's body value 169 */ 170 public enum AwsSignedBodyHeaderType { 171 /** Do not add any signing information about the body to the signed request */ 172 NONE(0), 173 174 /** Add the 'X-Amz-Content-Sha256' header to the signed request */ 175 X_AMZ_CONTENT_SHA256(1); 176 177 /** 178 * Constructs a Java enum value from a native enum value as an integer 179 * @param nativeValue native enum value 180 */ AwsSignedBodyHeaderType(int nativeValue)181 AwsSignedBodyHeaderType(int nativeValue) { 182 this.nativeValue = nativeValue; 183 } 184 185 /** 186 * Gets the native enum value as an integer that is associated with this Java enum value 187 * @return this value's associated native enum value 188 */ getNativeValue()189 public int getNativeValue() { return nativeValue; } 190 191 /** 192 * Creates a Java enum value from a native enum value as an integer 193 * @param value native enum value 194 * @return the corresponding Java enum value 195 */ getEnumValueFromInteger(int value)196 public static AwsSignedBodyHeaderType getEnumValueFromInteger(int value) { 197 AwsSignedBodyHeaderType enumValue = enumMapping.get(value); 198 if (enumValue != null) { 199 return enumValue; 200 } 201 202 throw new RuntimeException("Illegal signed body header value in signing configuration"); 203 } 204 buildEnumMapping()205 private static Map<Integer, AwsSignedBodyHeaderType> buildEnumMapping() { 206 Map<Integer, AwsSignedBodyHeaderType> enumMapping = new HashMap<Integer, AwsSignedBodyHeaderType>(); 207 enumMapping.put(NONE.getNativeValue(), NONE); 208 enumMapping.put(X_AMZ_CONTENT_SHA256.getNativeValue(), X_AMZ_CONTENT_SHA256); 209 210 return enumMapping; 211 } 212 213 private int nativeValue; 214 215 private static Map<Integer, AwsSignedBodyHeaderType> enumMapping = buildEnumMapping(); 216 } 217 218 private int algorithm = AwsSigningAlgorithm.SIGV4.getNativeValue(); 219 private int signatureType = AwsSignatureType.HTTP_REQUEST_VIA_HEADERS.getNativeValue(); 220 private String region; 221 private String service; 222 private long time = System.currentTimeMillis(); 223 private CredentialsProvider credentialsProvider; 224 private Credentials credentials; 225 private Predicate<String> shouldSignHeader; 226 private boolean useDoubleUriEncode = true; 227 private boolean shouldNormalizeUriPath = true; 228 private boolean omitSessionToken = false; 229 private String signedBodyValue = null; 230 private int signedBodyHeader = AwsSignedBodyHeaderType.NONE.getNativeValue(); 231 private long expirationInSeconds = 0; 232 233 /** 234 * Default constructor 235 */ AwsSigningConfig()236 public AwsSigningConfig() {} 237 238 /** 239 * Creates a new signing configuration from this one. 240 * @return a clone of this signing configuration 241 */ clone()242 public AwsSigningConfig clone() { 243 try (AwsSigningConfig clone = new AwsSigningConfig()) { 244 245 clone.setAlgorithm(getAlgorithm()); 246 clone.setSignatureType(getSignatureType()); 247 clone.setRegion(getRegion()); 248 clone.setService(getService()); 249 clone.setTime(getTime()); 250 clone.setCredentialsProvider(getCredentialsProvider()); 251 clone.setCredentials(getCredentials()); 252 clone.setShouldSignHeader(getShouldSignHeader()); 253 clone.setUseDoubleUriEncode(getUseDoubleUriEncode()); 254 clone.setShouldNormalizeUriPath(getShouldNormalizeUriPath()); 255 clone.setOmitSessionToken(getOmitSessionToken()); 256 clone.setSignedBodyValue(getSignedBodyValue()); 257 clone.setSignedBodyHeader(getSignedBodyHeader()); 258 clone.setExpirationInSeconds(getExpirationInSeconds()); 259 260 // success, bump up the ref count so we can escape the try-with-resources block 261 clone.addRef(); 262 return clone; 263 } 264 } 265 266 /** 267 * Required override method that must begin the release process of the acquired native handle 268 */ 269 @Override releaseNativeHandle()270 protected void releaseNativeHandle() {} 271 272 /** 273 * Override that determines whether a resource releases its dependencies at the same time the native handle is released or if it waits. 274 * Resources with asynchronous shutdown processes should override this with false, and establish a callback from native code that 275 * invokes releaseReferences() when the asynchronous shutdown process has completed. See HttpClientConnectionManager for an example. 276 */ 277 @Override canReleaseReferencesImmediately()278 protected boolean canReleaseReferencesImmediately() { return true; } 279 280 /** 281 * Sets what version of the AWS signing process should be used 282 * @param algorithm desired version of the AWS signing process 283 */ setAlgorithm(AwsSigningAlgorithm algorithm)284 public void setAlgorithm(AwsSigningAlgorithm algorithm) { this.algorithm = algorithm.getNativeValue(); } 285 286 /** 287 * Gets what version of the AWS signing procecss will be used 288 * @return what version of the AWS signing procecss will be used 289 */ getAlgorithm()290 public AwsSigningAlgorithm getAlgorithm() { 291 return AwsSigningAlgorithm.getEnumValueFromInteger(algorithm); 292 } 293 294 /** 295 * Sets what sort of signature should be computed 296 * @param signatureType what kind of signature to compute 297 */ setSignatureType(AwsSignatureType signatureType)298 public void setSignatureType(AwsSignatureType signatureType) { this.signatureType = signatureType.getNativeValue(); } 299 300 /** 301 * Gets what kind of signature will be computed 302 * @return what kind of signature will be computed 303 */ getSignatureType()304 public AwsSignatureType getSignatureType() { 305 return AwsSignatureType.getEnumValueFromInteger(signatureType); 306 } 307 308 /** 309 * Sets what to use for region when signing. Depending on the algorithm, this may not be an actual region name 310 * and so no validation is done on this parameter. In sigv4a, this value is used for the "region-set" concept. 311 * @param region region value to use when signing 312 */ setRegion(String region)313 public void setRegion(String region) { this.region = region; } 314 315 /** 316 * Gets what will be used for the region or region-set concept during signing. 317 * @return what will be used for the region or region-set concept during signing 318 */ getRegion()319 public String getRegion() { return region; } 320 321 /** 322 * Sets what service signing name to use. 323 * @param service signing name of the service that this signing calculation should use 324 */ setService(String service)325 public void setService(String service) { this.service = service; } 326 327 /** 328 * Gets what service signing name will be used 329 * @return what service signing name will be used 330 */ getService()331 public String getService() { return service; } 332 333 /** 334 * Sets the point in time that signing should be relative to. Not Instant for Android API level support reasons. 335 * Additionally, for http requests, X-Amz-Date will be added to the request using this time point. 336 * @param time point in time, as milliseconds since epoch, that signing should be relative to 337 */ setTime(long time)338 public void setTime(long time) { this.time = time; } 339 340 /** 341 * Gets the point in time (in milliseconds since epoch) that signing will be done relative to 342 * @return the point in time (in milliseconds since epoch) that signing will be done relative to 343 */ getTime()344 public long getTime() { return this.time; } 345 346 /** 347 * Sets the provider to use to source credentials from before signing. 348 * @param credentialsProvider provider to retrieve credentials from prior to signing 349 */ setCredentialsProvider(CredentialsProvider credentialsProvider)350 public void setCredentialsProvider(CredentialsProvider credentialsProvider) { 351 swapReferenceTo(this.credentialsProvider, credentialsProvider); 352 this.credentialsProvider = credentialsProvider; 353 } 354 355 /** 356 * Gets the provider to source credentials from before signing 357 * @return the provider to source credentials from before signing 358 */ getCredentialsProvider()359 public CredentialsProvider getCredentialsProvider() { return credentialsProvider; } 360 361 /** 362 * Sets the credentials to use for signing. Overrides the provider setting if non-null. 363 * @param credentials credentials to use for signing 364 */ setCredentials(Credentials credentials)365 public void setCredentials(Credentials credentials) { this.credentials = credentials; } 366 367 /** 368 * Gets the credentials to use for signing. 369 * @return credentials to use for signing 370 */ getCredentials()371 public Credentials getCredentials() { return credentials; } 372 373 /** 374 * Sets a header-name signing predicate filter. Headers that do not pass the filter will not be signed. 375 * @param shouldSignHeader header-name signing predicate filter 376 */ setShouldSignHeader(Predicate<String> shouldSignHeader)377 public void setShouldSignHeader(Predicate<String> shouldSignHeader) { this.shouldSignHeader = shouldSignHeader; } 378 379 /** 380 * Gets the header-name signing predicate filter to use 381 * @return the header-name signing predicate filter to use 382 */ getShouldSignHeader()383 public Predicate<String> getShouldSignHeader() { return shouldSignHeader; } 384 385 /** 386 * Sets whether or not signing should uri encode urls as part of canonical request construction. 387 * We assume the uri will be encoded once in preparation for transmission. Certain services 388 * do not decode before checking signature, requiring us to actually double-encode the uri in the canonical 389 * request in order to pass a signature check. 390 * @param useDoubleUriEncode should signing uri encode urls in the canonical request 391 */ setUseDoubleUriEncode(boolean useDoubleUriEncode)392 public void setUseDoubleUriEncode(boolean useDoubleUriEncode) { this.useDoubleUriEncode = useDoubleUriEncode; } 393 394 /** 395 * Gets whether or not signing will uri encode urls during canonical request construction 396 * @return whether or not signing will uri encode urls during canonical request construction 397 */ getUseDoubleUriEncode()398 public boolean getUseDoubleUriEncode() { return useDoubleUriEncode; } 399 400 /** 401 * Sets whether or not the uri path should be normalized during canonical request construction 402 * @param shouldNormalizeUriPath whether or not the uri path should be normalized during canonical request construction 403 */ setShouldNormalizeUriPath(boolean shouldNormalizeUriPath)404 public void setShouldNormalizeUriPath(boolean shouldNormalizeUriPath) { this.shouldNormalizeUriPath = shouldNormalizeUriPath; } 405 406 /** 407 * Gets whether or not the uri path should be normalized during canonical request construction 408 * @return whether or not the uri path should be normalized during canonical request construction 409 */ getShouldNormalizeUriPath()410 public boolean getShouldNormalizeUriPath() { return shouldNormalizeUriPath; } 411 412 /** 413 * Sets whether or not X-Amz-Session-Token should be added to the canonical request when signing with session 414 * credentials. 415 * 416 * "X-Amz-Security-Token" is added during signing, as a header or 417 * query param, when credentials have a session token. 418 * If false (the default), this parameter is included in the canonical request. 419 * If true, this parameter is still added, but omitted from the canonical request. 420 * 421 * @param omitSessionToken whether or not X-Amz-Session-Token should be added to the canonical request when signing with session 422 * credentials 423 */ setOmitSessionToken(boolean omitSessionToken)424 public void setOmitSessionToken(boolean omitSessionToken) { this.omitSessionToken = omitSessionToken; } 425 426 /** 427 * Gets whether or not X-Amz-Session-Token should be added to the canonical request when signing with session 428 * credentials. 429 * @return whether or not X-Amz-Session-Token should be added to the canonical request when signing with session 430 * credentials 431 */ getOmitSessionToken()432 public boolean getOmitSessionToken() { return omitSessionToken; } 433 434 /** 435 * Sets the payload hash override value to use in canonical request construction. If the signed body header type is 436 * not set to null, then the designated header will also take on this value. If this value is NULL, then the signer 437 * will compute the SHA256 of the body stream and use that instead. 438 * @param signedBodyValue payload hash override value to use in canonical request construction 439 */ setSignedBodyValue(String signedBodyValue)440 public void setSignedBodyValue(String signedBodyValue) { 441 if (signedBodyValue != null && signedBodyValue.isEmpty()) { 442 throw new IllegalArgumentException("Signed Body Value must be null or non-empty string."); 443 } 444 this.signedBodyValue = signedBodyValue; 445 } 446 447 /** 448 * Gets the payload hash override to use in canonical request construction. 449 * @return the payload hash override to use in canonical request construction 450 */ getSignedBodyValue()451 public String getSignedBodyValue() { return signedBodyValue; } 452 453 /** 454 * Sets what signed body header should hold the payload hash (or override value). 455 * @param signedBodyHeader what signed body header should hold the payload hash (or override value) 456 */ setSignedBodyHeader(AwsSignedBodyHeaderType signedBodyHeader)457 public void setSignedBodyHeader(AwsSignedBodyHeaderType signedBodyHeader) { this.signedBodyHeader = signedBodyHeader.getNativeValue(); } 458 459 /** 460 * Gets what signed body header should hold the payload hash (or override value). 461 * @return what signed body header should hold the payload hash (or override value) 462 */ getSignedBodyHeader()463 public AwsSignedBodyHeaderType getSignedBodyHeader() { return AwsSignedBodyHeaderType.getEnumValueFromInteger(signedBodyHeader); } 464 465 /** 466 * Sets the expiration time in seconds when using query param signing (pre-signed url). The appropriate query param 467 * will be added to the URL when building the canonical and signed requests. 468 * @param expirationInSeconds time in seconds that a pre-signed url will be valid for 469 */ setExpirationInSeconds(long expirationInSeconds)470 public void setExpirationInSeconds(long expirationInSeconds) { this.expirationInSeconds = expirationInSeconds; } 471 472 /** 473 * Gets the expiration time in seconds to use when signing to make a pre-signed url. 474 * @return the expiration time in seconds for a pre-signed url 475 */ getExpirationInSeconds()476 public long getExpirationInSeconds() { return expirationInSeconds; } 477 478 /** 479 * Helper to get the default signing Config for S3. 480 * @param region The region to sign with 481 * @param credentialsProvider The provider while signing request. 482 * @return the default signing config for S3 483 */ getDefaultS3SigningConfig(String region, CredentialsProvider credentialsProvider)484 static public AwsSigningConfig getDefaultS3SigningConfig(String region, CredentialsProvider credentialsProvider){ 485 AwsSigningConfig defaultConfig = new AwsSigningConfig(); 486 defaultConfig.setAlgorithm(AwsSigningAlgorithm.SIGV4); 487 defaultConfig.setService("s3"); 488 defaultConfig.setSignedBodyHeader(AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA256); 489 defaultConfig.setSignedBodyValue("UNSIGNED-PAYLOAD"); 490 defaultConfig.setRegion(region); 491 defaultConfig.setCredentialsProvider(credentialsProvider); 492 defaultConfig.setShouldNormalizeUriPath(false); 493 defaultConfig.setUseDoubleUriEncode(false); 494 return defaultConfig; 495 } 496 } 497