Skip to content

Commit c6d8c6c

Browse files
committed
Break up authscheme into multiple standalone classes, add tests.
1 parent 7dc8845 commit c6d8c6c

File tree

10 files changed

+347
-184
lines changed

10 files changed

+347
-184
lines changed

services/signin/src/main/java/software/amazon/awssdk/services/signin/auth/LoginCredentialsProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
3535
import software.amazon.awssdk.services.signin.SigninClient;
3636
import software.amazon.awssdk.services.signin.internal.AccessTokenManager;
37-
import software.amazon.awssdk.services.signin.internal.DpopAuthScheme;
37+
import software.amazon.awssdk.services.signin.internal.DpopAuthPlugin;
3838
import software.amazon.awssdk.services.signin.internal.LoginAccessToken;
3939
import software.amazon.awssdk.services.signin.internal.LoginCacheDirectorySystemSetting;
4040
import software.amazon.awssdk.services.signin.internal.OnDiskTokenManager;
@@ -161,7 +161,7 @@ private RefreshResult<AwsCredentials> refreshFromSigninService(LoginAccessToken
161161
log.debug(() -> "Credentials are near expiration/expired, refreshing from Signin service.");
162162

163163
try {
164-
SdkPlugin dpopAuthPlugin = DpopAuthScheme.DpopAuthPlugin.create(tokenFromDisc.getDpopKey());
164+
SdkPlugin dpopAuthPlugin = DpopAuthPlugin.create(tokenFromDisc.getDpopKey());
165165
CreateOAuth2TokenRequest refreshRequest =
166166
CreateOAuth2TokenRequest
167167
.builder()
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.signin.internal;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.concurrent.CompletableFuture;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
import software.amazon.awssdk.core.SdkPlugin;
23+
import software.amazon.awssdk.core.SdkServiceClientConfiguration;
24+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
25+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
26+
import software.amazon.awssdk.identity.spi.IdentityProvider;
27+
import software.amazon.awssdk.identity.spi.ResolveIdentityRequest;
28+
import software.amazon.awssdk.services.signin.SigninServiceClientConfiguration;
29+
import software.amazon.awssdk.services.signin.auth.scheme.SigninAuthSchemeParams;
30+
import software.amazon.awssdk.services.signin.auth.scheme.SigninAuthSchemeProvider;
31+
import software.amazon.awssdk.utils.Validate;
32+
33+
/**
34+
* An SDK plugin that will use DPoP auth for requests by adding the {@link DpopAuthScheme} and overriding the
35+
* {@link AuthSchemeProvider} with a custom provider that will always return Dpop.
36+
* The auth scheme uses the {@link DpopSigner} to add the required DPoP header to the request.
37+
*/
38+
@SdkInternalApi
39+
public class DpopAuthPlugin implements SdkPlugin {
40+
private final String dpopKeyPem;
41+
42+
private DpopAuthPlugin(String dpopKeyPem) {
43+
this.dpopKeyPem = Validate.paramNotNull(dpopKeyPem, "dpopKeyPem");
44+
}
45+
46+
/**
47+
* Create an instance of the DpopAuthPlugin using the dpopKey from the {@link LoginAccessToken}
48+
* @param dpopKeyPem - PEM file contents containing the base64-encoding of the ECPrivateKey format defined by
49+
* RFC5915: Elliptic Curve Private Key Structure. It MUST include the public key coordinates.
50+
* @return dpopAuthPlugin
51+
*/
52+
public static DpopAuthPlugin create(String dpopKeyPem) {
53+
return new DpopAuthPlugin(dpopKeyPem);
54+
}
55+
56+
@Override
57+
public void configureClient(SdkServiceClientConfiguration.Builder config) {
58+
SigninServiceClientConfiguration.Builder scb =
59+
Validate.isInstanceOf(SigninServiceClientConfiguration.Builder.class, config,
60+
"DpopAuthPlugin must be applied to a SigninServiceClient");
61+
scb.authSchemeProvider(new DpopAuthSchemeProvider());
62+
// we must use a static DpopIdentity here rather than one that dynamically loads from the disk cache
63+
// the refresh request takes the clientId/refreshToken sourced from the access token on disk as input
64+
// so we must sign the request with the dpopKey loaded from the same load. IE: do not read the
65+
// access token file twice!
66+
scb.putAuthScheme(DpopAuthScheme.create(StaticDpopIdentityProvider.create(dpopKeyPem)));
67+
}
68+
69+
private static class DpopAuthSchemeProvider implements SigninAuthSchemeProvider {
70+
71+
@Override
72+
public List<AuthSchemeOption> resolveAuthScheme(SigninAuthSchemeParams authSchemeParams) {
73+
return Collections.singletonList(AuthSchemeOption.builder().schemeId(DpopAuthScheme.SCHEME_NAME).build());
74+
}
75+
}
76+
77+
/**
78+
* A identity provider that provides a static {@link DpopIdentity}
79+
*/
80+
private static class StaticDpopIdentityProvider implements IdentityProvider<DpopIdentity> {
81+
private final DpopIdentity identity;
82+
83+
private StaticDpopIdentityProvider(DpopIdentity identity) {
84+
this.identity = Validate.paramNotNull(identity, "identity");
85+
}
86+
87+
public static StaticDpopIdentityProvider create(String dpopKeyPem) {
88+
return new StaticDpopIdentityProvider(DpopIdentity.create(dpopKeyPem));
89+
}
90+
91+
@Override
92+
public Class<DpopIdentity> identityType() {
93+
return DpopIdentity.class;
94+
}
95+
96+
@Override
97+
public CompletableFuture<? extends DpopIdentity> resolveIdentity(ResolveIdentityRequest request) {
98+
return CompletableFuture.completedFuture(identity);
99+
}
100+
}
101+
}

services/signin/src/main/java/software/amazon/awssdk/services/signin/internal/DpopAuthScheme.java

Lines changed: 9 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,27 @@
1515

1616
package software.amazon.awssdk.services.signin.internal;
1717

18-
import java.security.interfaces.ECPrivateKey;
19-
import java.security.interfaces.ECPublicKey;
20-
import java.time.Instant;
21-
import java.util.Collections;
22-
import java.util.List;
23-
import java.util.UUID;
24-
import java.util.concurrent.CompletableFuture;
25-
import software.amazon.awssdk.core.SdkPlugin;
26-
import software.amazon.awssdk.core.SdkRequest;
27-
import software.amazon.awssdk.core.SdkServiceClientConfiguration;
28-
import software.amazon.awssdk.http.SdkHttpRequest;
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
2919
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
30-
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
31-
import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest;
32-
import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest;
33-
import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest;
3420
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
35-
import software.amazon.awssdk.http.auth.spi.signer.SignRequest;
36-
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
37-
import software.amazon.awssdk.identity.spi.Identity;
3821
import software.amazon.awssdk.identity.spi.IdentityProvider;
3922
import software.amazon.awssdk.identity.spi.IdentityProviders;
40-
import software.amazon.awssdk.identity.spi.ResolveIdentityRequest;
41-
import software.amazon.awssdk.services.signin.SigninServiceClientConfiguration;
42-
import software.amazon.awssdk.services.signin.auth.scheme.SigninAuthSchemeParams;
43-
import software.amazon.awssdk.services.signin.auth.scheme.SigninAuthSchemeProvider;
44-
import software.amazon.awssdk.utils.Pair;
4523
import software.amazon.awssdk.utils.Validate;
46-
import software.amazon.awssdk.utils.http.SdkHttpUtils;
4724

48-
public class DpopAuthScheme implements AuthScheme<DpopAuthScheme.DpopIdentity> {
25+
/**
26+
* An AuthScheme representing authentication withOAuth 2.0 Demonstrating Proof of Possession (DPoP) header.
27+
*/
28+
@SdkInternalApi
29+
public class DpopAuthScheme implements AuthScheme<DpopIdentity> {
4930
public static final String SCHEME_NAME = "DPOP";
5031

51-
private final DpopIdentityProvider identityProvider;
32+
private final IdentityProvider<DpopIdentity> identityProvider;
5233

53-
private DpopAuthScheme(DpopIdentityProvider identityProvider) {
34+
private DpopAuthScheme(IdentityProvider<DpopIdentity> identityProvider) {
5435
this.identityProvider = Validate.paramNotNull(identityProvider, "identityProvider");
5536
}
5637

57-
public static DpopAuthScheme create(DpopIdentityProvider identityProvider) {
38+
public static DpopAuthScheme create(IdentityProvider<DpopIdentity> identityProvider) {
5839
return new DpopAuthScheme(identityProvider);
5940
}
6041

@@ -74,129 +55,4 @@ public IdentityProvider<DpopIdentity> identityProvider(IdentityProviders provide
7455
public HttpSigner<DpopIdentity> signer() {
7556
return new DpopSigner();
7657
}
77-
78-
public static class DpopIdentity implements Identity {
79-
private final ECPublicKey publicKey;
80-
private final ECPrivateKey privateKey;
81-
82-
private DpopIdentity(ECPublicKey publicKey, ECPrivateKey privateKey) {
83-
this.publicKey = publicKey;
84-
this.privateKey = privateKey;
85-
}
86-
87-
public static DpopIdentity create(ECPublicKey publicKey, ECPrivateKey privateKey) {
88-
return new DpopIdentity(publicKey, privateKey);
89-
}
90-
91-
public static DpopIdentity create(String dpopKeyPem) {
92-
Pair<ECPrivateKey, ECPublicKey> keys = EcKeyLoader.loadSec1Pem(dpopKeyPem);
93-
return new DpopIdentity(keys.right(), keys.left());
94-
}
95-
96-
public ECPublicKey getPublicKey() {
97-
return publicKey;
98-
}
99-
100-
public ECPrivateKey getPrivateKey() {
101-
return privateKey;
102-
}
103-
}
104-
105-
private static class DpopSigner implements HttpSigner<DpopIdentity> {
106-
107-
@Override
108-
public SignedRequest sign(SignRequest<? extends DpopIdentity> request) {
109-
return SignedRequest.builder()
110-
.request(doSign(request))
111-
.payload(request.payload().orElse(null))
112-
.build();
113-
}
114-
115-
@Override
116-
public CompletableFuture<AsyncSignedRequest> signAsync(AsyncSignRequest<? extends DpopIdentity> request) {
117-
return CompletableFuture.completedFuture(
118-
AsyncSignedRequest.builder()
119-
.request(doSign(request))
120-
.payload(request.payload().orElse(null))
121-
.build());
122-
}
123-
124-
/**
125-
* Using {@link BaseSignRequest}, sign the request with a {@link BaseSignRequest} and re-build it.
126-
*/
127-
private SdkHttpRequest doSign(BaseSignRequest<?, ? extends DpopIdentity> request) {
128-
return request.request().toBuilder()
129-
.putHeader(
130-
"DPoP",
131-
buildDpopHeader(request))
132-
.build();
133-
}
134-
135-
private String buildDpopHeader(BaseSignRequest<?, ? extends DpopIdentity> request) {
136-
SdkHttpRequest httpRequest = request.request();
137-
String endpoint = extractRequestEndpoint(httpRequest);
138-
return DpopHeaderGenerator.generateDPoPProofHeader(
139-
request.identity(), endpoint, httpRequest.method().name(),
140-
Instant.now().getEpochSecond(), UUID.randomUUID().toString());
141-
}
142-
143-
private static String extractRequestEndpoint(SdkHttpRequest httpRequest) {
144-
// using SdkHttpRequest.getUri() results in creating a URI which is slow and we don't need the query components
145-
// construct only the endpoint that we require for DPoP.
146-
String portString =
147-
SdkHttpUtils.isUsingStandardPort(httpRequest.protocol(), httpRequest.port()) ? "" : ":" + httpRequest.port();
148-
return httpRequest.protocol() + "://" + httpRequest.host() + portString + httpRequest.encodedPath();
149-
}
150-
}
151-
152-
private static class DpopIdentityProvider implements IdentityProvider<DpopIdentity> {
153-
private final DpopIdentity identity;
154-
155-
private DpopIdentityProvider(DpopIdentity identity) {
156-
this.identity = Validate.paramNotNull(identity, "identity");
157-
}
158-
159-
public static DpopIdentityProvider create(String dpopKeyPem) {
160-
return new DpopIdentityProvider(DpopIdentity.create(dpopKeyPem));
161-
}
162-
163-
@Override
164-
public Class<DpopIdentity> identityType() {
165-
return DpopIdentity.class;
166-
}
167-
168-
@Override
169-
public CompletableFuture<? extends DpopIdentity> resolveIdentity(ResolveIdentityRequest request) {
170-
return CompletableFuture.completedFuture(identity);
171-
}
172-
}
173-
174-
public static class DpopAuthSchemeResolver implements SigninAuthSchemeProvider {
175-
176-
@Override
177-
public List<AuthSchemeOption> resolveAuthScheme(SigninAuthSchemeParams authSchemeParams) {
178-
return Collections.singletonList(AuthSchemeOption.builder().schemeId(SCHEME_NAME).build());
179-
}
180-
}
181-
182-
public static class DpopAuthPlugin implements SdkPlugin {
183-
private final String dpopKeyPem;
184-
185-
private DpopAuthPlugin(String dpopKeyPem) {
186-
this.dpopKeyPem = Validate.paramNotNull(dpopKeyPem, "dpopKeyPem");
187-
}
188-
189-
public static DpopAuthPlugin create(String dpopKeyPem) {
190-
return new DpopAuthPlugin(dpopKeyPem);
191-
}
192-
193-
@Override
194-
public void configureClient(SdkServiceClientConfiguration.Builder config) {
195-
SigninServiceClientConfiguration.Builder scb =
196-
Validate.isInstanceOf(SigninServiceClientConfiguration.Builder.class, config,
197-
"DpopAuthPlugin must be applied to a SigninServiceClient");
198-
scb.authSchemeProvider(new DpopAuthSchemeResolver());
199-
scb.putAuthScheme(DpopAuthScheme.create(DpopIdentityProvider.create(dpopKeyPem)));
200-
}
201-
}
20258
}

services/signin/src/main/java/software/amazon/awssdk/services/signin/internal/DpopHeaderGenerator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.util.Base64;
2828
import software.amazon.awssdk.annotations.SdkInternalApi;
2929
import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
30-
import software.amazon.awssdk.utils.Pair;
3130
import software.amazon.awssdk.utils.Validate;
3231

3332
/**
@@ -65,7 +64,7 @@ private DpopHeaderGenerator() {
6564
* @param uuid - Unique identifier for the DPoP proof JWT - should be a UUID4 string.
6665
* @return DPoP header value
6766
*/
68-
public static String generateDPoPProofHeader(DpopAuthScheme.DpopIdentity dpopIdentity, String endpoint, String httpMethod,
67+
public static String generateDPoPProofHeader(DpopIdentity dpopIdentity, String endpoint, String httpMethod,
6968
long epochSeconds, String uuid) {
7069
Validate.paramNotNull(dpopIdentity, "dpopIdentity");
7170
Validate.paramNotBlank(endpoint, "endpoint");
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.signin.internal;
17+
18+
import java.security.interfaces.ECPrivateKey;
19+
import java.security.interfaces.ECPublicKey;
20+
import software.amazon.awssdk.annotations.SdkInternalApi;
21+
import software.amazon.awssdk.identity.spi.Identity;
22+
import software.amazon.awssdk.utils.Pair;
23+
24+
/**
25+
* An identity representing the public and private keys required to sign a request using DPoP.
26+
*/
27+
@SdkInternalApi
28+
public class DpopIdentity implements Identity {
29+
private final ECPublicKey publicKey;
30+
private final ECPrivateKey privateKey;
31+
32+
private DpopIdentity(ECPublicKey publicKey, ECPrivateKey privateKey) {
33+
this.publicKey = publicKey;
34+
this.privateKey = privateKey;
35+
}
36+
37+
public static DpopIdentity create(ECPublicKey publicKey, ECPrivateKey privateKey) {
38+
return new DpopIdentity(publicKey, privateKey);
39+
}
40+
41+
public static DpopIdentity create(String dpopKeyPem) {
42+
Pair<ECPrivateKey, ECPublicKey> keys = EcKeyLoader.loadSec1Pem(dpopKeyPem);
43+
return new DpopIdentity(keys.right(), keys.left());
44+
}
45+
46+
public ECPublicKey getPublicKey() {
47+
return publicKey;
48+
}
49+
50+
public ECPrivateKey getPrivateKey() {
51+
return privateKey;
52+
}
53+
}

0 commit comments

Comments
 (0)