xref: /aosp_15_r20/external/aws-crt-java/src/main/java/software/amazon/awssdk/crt/auth/signing/AwsSigningConfig.java (revision 3c7ae9de214676c52d19f01067dc1a404272dc11)
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