2
2
// Licensed under the MIT License.
3
3
package com .azure .communication .callautomation .implementation ;
4
4
5
- import java .net .URL ;
6
- import java .nio .ByteBuffer ;
7
- import java .nio .charset .StandardCharsets ;
8
- import java .security .InvalidKeyException ;
9
- import java .security .MessageDigest ;
10
- import java .security .NoSuchAlgorithmException ;
11
- import java .time .OffsetDateTime ;
12
- import java .time .ZoneOffset ;
13
- import java .time .format .DateTimeFormatter ;
14
- import java .util .Arrays ;
15
- import java .util .Base64 ;
16
- import java .util .HashMap ;
17
- import java .util .Locale ;
18
- import java .util .Map ;
19
- import java .util .Objects ;
20
- import java .util .stream .Collectors ;
21
-
22
5
import com .azure .core .credential .AzureKeyCredential ;
6
+ import com .azure .core .http .HttpHeaderName ;
7
+ import com .azure .core .http .HttpHeaders ;
23
8
import com .azure .core .http .HttpPipelineCallContext ;
24
9
import com .azure .core .http .HttpPipelineNextPolicy ;
25
10
import com .azure .core .http .HttpResponse ;
26
11
import com .azure .core .http .policy .HttpPipelinePolicy ;
27
-
28
12
import com .azure .core .util .logging .ClientLogger ;
29
13
import reactor .core .Exceptions ;
30
14
import reactor .core .publisher .Flux ;
31
15
import reactor .core .publisher .Mono ;
32
16
33
17
import javax .crypto .Mac ;
34
18
import javax .crypto .spec .SecretKeySpec ;
19
+ import java .net .URL ;
20
+ import java .nio .ByteBuffer ;
21
+ import java .nio .charset .StandardCharsets ;
22
+ import java .security .InvalidKeyException ;
23
+ import java .security .MessageDigest ;
24
+ import java .security .NoSuchAlgorithmException ;
25
+ import java .time .OffsetDateTime ;
26
+ import java .time .ZoneOffset ;
27
+ import java .time .format .DateTimeFormatter ;
28
+ import java .util .Base64 ;
29
+ import java .util .Locale ;
30
+ import java .util .Objects ;
35
31
36
32
/**
37
33
* HttpPipelinePolicy to append CommunicationClient required headers
38
34
*/
39
35
public final class CustomHmacAuthenticationPolicy implements HttpPipelinePolicy {
40
- private static final String X_MS_DATE_HEADER = "x-ms-date" ;
41
- private static final String X_MS_STRING_TO_SIGN_HEADER = "x-ms-hmac-string-to-sign-base64" ;
42
- private static final String HOST_HEADER = "host" ;
43
- private static final String CONTENT_HASH_HEADER = "x-ms-content-sha256" ;
44
- // Order of the headers are important here for generating correct signature
45
- private static final String [] SIGNED_HEADERS = new String []{X_MS_DATE_HEADER , HOST_HEADER , CONTENT_HASH_HEADER };
46
-
47
- private static final String AUTHORIZATIONHEADERNAME = "Authorization" ;
48
- private static final String HMACSHA256FORMAT = "HMAC-SHA256 SignedHeaders=%s&Signature=%s" ;
36
+ private static final ClientLogger LOGGER = new ClientLogger (CustomHmacAuthenticationPolicy .class );
37
+ private static final HttpHeaderName X_FORWARDED_HOST = HttpHeaderName .fromString ("X-FORWARDED-HOST" );
38
+ private static final HttpHeaderName X_MS_DATE_HEADER = HttpHeaderName .fromString ("x-ms-date" );
39
+ private static final HttpHeaderName X_MS_STRING_TO_SIGN_HEADER
40
+ = HttpHeaderName .fromString ("x-ms-hmac-string-to-sign-base64" );
41
+ private static final HttpHeaderName CONTENT_HASH_HEADER = HttpHeaderName .fromString ("x-ms-content-sha256" );
49
42
50
43
// Previously DateTimeFormatter.RFC_1123_DATE_TIME was being used. There
51
44
// was an issue with the day of month part. RFC_1123_DATE_TIME does not
@@ -58,7 +51,6 @@ public final class CustomHmacAuthenticationPolicy implements HttpPipelinePolicy
58
51
59
52
private final AzureKeyCredential credential ;
60
53
private final String acsResource ;
61
- private final ClientLogger logger = new ClientLogger (CustomHmacAuthenticationPolicy .class );
62
54
63
55
/**
64
56
* Created with a non-null client credential
@@ -77,7 +69,7 @@ public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineN
77
69
? Flux .just (ByteBuffer .allocate (0 ))
78
70
: context .getHttpRequest ().getBody ();
79
71
80
- if ("http " .equals (context .getHttpRequest ().getUrl ().getProtocol ())) {
72
+ if (! "https " .equals (context .getHttpRequest ().getUrl ().getProtocol ())) {
81
73
return Mono .error (
82
74
new RuntimeException ("AzureKeyCredential requires a URL using the HTTPS protocol scheme" ));
83
75
}
@@ -88,13 +80,17 @@ public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineN
88
80
.map (alternativeUrl -> (URL ) alternativeUrl )
89
81
.orElse (context .getHttpRequest ().getUrl ());
90
82
91
- return appendAuthorizationHeaders (
92
- hostnameToSignWith ,
93
- context .getHttpRequest ().getHttpMethod ().toString (),
94
- contents )
95
- .flatMap (headers -> {
96
- headers .entrySet ().forEach (
97
- header -> context .getHttpRequest ().setHeader (header .getKey (), header .getValue ()));
83
+ return contents .collect (() -> {
84
+ try {
85
+ return MessageDigest .getInstance ("SHA-256" );
86
+ } catch (NoSuchAlgorithmException e ) {
87
+ throw LOGGER .logExceptionAsError (Exceptions .propagate (e ));
88
+ }
89
+ }, MessageDigest ::update )
90
+ .flatMap (messageDigest -> {
91
+ addAuthenticationHeaders (acsResource , hostnameToSignWith ,
92
+ context .getHttpRequest ().getHttpMethod ().toString (), messageDigest ,
93
+ context .getHttpRequest ().getHeaders ());
98
94
99
95
return next .process ();
100
96
});
@@ -103,38 +99,20 @@ public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineN
103
99
}
104
100
}
105
101
106
- private Mono <Map <String , String >> appendAuthorizationHeaders (URL url , String httpMethod , Flux <ByteBuffer > contents ) {
107
- return contents .collect (() -> {
108
- try {
109
- return MessageDigest .getInstance ("SHA-256" );
110
- } catch (NoSuchAlgorithmException e ) {
111
- throw logger .logExceptionAsError (Exceptions .propagate (e ));
112
- }
113
- }, MessageDigest ::update )
114
- .map (messageDigest -> addAuthenticationHeaders (url , httpMethod , messageDigest ));
115
- }
116
-
117
- private Map <String , String > addAuthenticationHeaders (final URL url ,
118
- final String httpMethod ,
119
- final MessageDigest messageDigest ) {
120
- final Map <String , String > headers = new HashMap <>();
121
-
102
+ private void addAuthenticationHeaders (String acsResource , URL url , String httpMethod ,
103
+ MessageDigest messageDigest , HttpHeaders headers ) {
122
104
final String contentHash = Base64 .getEncoder ().encodeToString (messageDigest .digest ());
123
- headers .put ("X-FORWARDED-HOST" , acsResource );
124
- headers .put (HOST_HEADER , acsResource );
125
- headers .put (CONTENT_HASH_HEADER , contentHash );
126
- String utcNow = OffsetDateTime .now (ZoneOffset .UTC )
127
- .format (HMAC_DATETIMEFORMATTER_PATTERN );
128
- headers .put (X_MS_DATE_HEADER , utcNow );
129
- addSignatureHeader (url , httpMethod , headers );
130
- return headers ;
105
+ headers .set (X_FORWARDED_HOST , acsResource );
106
+ headers .set (HttpHeaderName .HOST , acsResource );
107
+ headers .set (CONTENT_HASH_HEADER , contentHash );
108
+ String xMsDate = OffsetDateTime .now (ZoneOffset .UTC ).format (HMAC_DATETIMEFORMATTER_PATTERN );
109
+ headers .set (X_MS_DATE_HEADER , xMsDate );
110
+ addSignatureHeader (url , httpMethod , headers , xMsDate , acsResource , contentHash );
131
111
}
132
112
133
- private void addSignatureHeader (final URL url , final String httpMethod , final Map <String , String > httpHeaders ) {
134
- final String signedHeaderNames = String .join (";" , SIGNED_HEADERS );
135
- final String signedHeaderValues = Arrays .stream (SIGNED_HEADERS )
136
- .map (httpHeaders ::get )
137
- .collect (Collectors .joining (";" ));
113
+ private void addSignatureHeader (URL url , String httpMethod , HttpHeaders headers , String xMsDate , String host ,
114
+ String xMsContentSha256 ) {
115
+ String signedHeaderValues = xMsDate + ";" + host + ";" + xMsContentSha256 ;
138
116
139
117
String pathAndQuery = url .getPath ();
140
118
if (url .getQuery () != null ) {
@@ -152,13 +130,15 @@ private void addSignatureHeader(final URL url, final String httpMethod, final Ma
152
130
sha256HMAC = Mac .getInstance ("HmacSHA256" );
153
131
sha256HMAC .init (new SecretKeySpec (key , "HmacSHA256" ));
154
132
} catch (NoSuchAlgorithmException | InvalidKeyException e ) {
155
- throw logger .logExceptionAsError (new RuntimeException (e ));
133
+ throw LOGGER .logExceptionAsError (new RuntimeException (e ));
156
134
}
157
135
158
136
final String signature =
159
137
Base64 .getEncoder ().encodeToString (sha256HMAC .doFinal (stringToSign .getBytes (StandardCharsets .UTF_8 )));
160
- httpHeaders .put (AUTHORIZATIONHEADERNAME , String .format (HMACSHA256FORMAT , signedHeaderNames , signature ));
161
- httpHeaders .put (X_MS_STRING_TO_SIGN_HEADER , Base64 .getEncoder ().encodeToString (stringToSign .getBytes (StandardCharsets .UTF_8 )));
138
+ String authorization = "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=" + signature ;
139
+ headers .set (HttpHeaderName .AUTHORIZATION , authorization );
140
+ headers .set (X_MS_STRING_TO_SIGN_HEADER ,
141
+ Base64 .getEncoder ().encodeToString (stringToSign .getBytes (StandardCharsets .UTF_8 )));
162
142
}
163
143
164
144
}
0 commit comments