From 2fdd623638eb81236bf6154a323fdbb7e55a2920 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Thu, 3 May 2018 11:19:57 -0700 Subject: [PATCH] Ensure message protection works for keys create\delete --- .../authentication/KeyVaultCredentials.java | 45 ++++++++++++++++--- .../messagesecurity/HttpMessageSecurity.java | 13 +++++- .../keyvault/test/EnhancedKeyVaultTest.java | 8 +++- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java b/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java index 52f8b1a677a7a..fc9d9f7bfb9ef 100644 --- a/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java +++ b/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java @@ -9,6 +9,8 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Arrays; +import java.util.List; import com.microsoft.rest.credentials.ServiceClientCredentials; import com.microsoft.azure.keyvault.messagesecurity.HttpMessageSecurity; @@ -31,6 +33,7 @@ public abstract class KeyVaultCredentials implements ServiceClientCredentials { private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; private static final String BEARER_TOKEP_REFIX = "Bearer "; + private List supportedMethods = Arrays.asList("sign", "verify", "encrypt", "decrypt", "wrapkey", "unwrapkey"); private final ChallengeCache cache = new ChallengeCache(); @@ -88,7 +91,9 @@ public Response intercept(Chain chain) throws IOException { * @return Pair of protected request and HttpMessageSecurity used for encryption. */ private Pair buildAuthenticatedRequest(Request originalRequest, Map challengeMap) throws IOException{ - AuthenticationResult authResult = getAuthenticationCredentials(challengeMap); + + Boolean supportsPop = supportsMessageProtection(originalRequest.url().toString(), challengeMap); + AuthenticationResult authResult = getAuthenticationCredentials(supportsPop, challengeMap); if (authResult == null) { return null; @@ -97,9 +102,9 @@ private Pair buildAuthenticatedRequest(Request ori HttpMessageSecurity httpMessageSecurity = new HttpMessageSecurity( authResult.getAuthToken(), - authResult.getPopKey(), - challengeMap.get("x-ms-message-encryption-key"), - challengeMap.get("x-ms-message-signing-key")); + supportsPop ? authResult.getPopKey() : "", + supportsPop ? challengeMap.get("x-ms-message-encryption-key") : "", + supportsPop ? challengeMap.get("x-ms-message-signing-key") : ""); Request request = httpMessageSecurity.protectRequest(originalRequest); return Pair.of(request, httpMessageSecurity); @@ -136,7 +141,7 @@ private Pair buildAuthenticatedRequest(Request ori * @return request with removed body. */ private Request buildEmptyRequest(Request request){ - RequestBody body = RequestBody.create(MediaType.parse("application/jose+json"), "{}"); + RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{}"); if (request.method().equalsIgnoreCase("get")){ return request; } @@ -145,15 +150,41 @@ private Request buildEmptyRequest(Request request){ } } + /** + * Checks if resource supports message protection. + * + * @param url + * resource url. + * @param challengeMap + * the challenge map. + * @return true if message protection is supported. + */ + private Boolean supportsMessageProtection(String url, Map challengeMap) { + + if (!"true".equals(challengeMap.get("supportspop"))){ + return false; + } + + // Message protection is enabled only for subset of keys operations. + if (!url.toLowerCase().contains("/keys/")){ + return false; + } + + String[] tokens = url.split("\\?")[0].split("/"); + return supportedMethods.contains(tokens[tokens.length - 1]); + } + /** * Extracts the authentication challenges from the challenge map and calls * the authentication callback to get the bearer token and return it. * + * @param supportsPop + * is resource supports pop authentication. * @param challengeMap * the challenge map. * @return AuthenticationResult with bearer token and PoP key. */ - private AuthenticationResult getAuthenticationCredentials(Map challengeMap) { + private AuthenticationResult getAuthenticationCredentials(Boolean supportsPop, Map challengeMap) { String authorization = challengeMap.get("authorization"); if (authorization == null) { @@ -162,7 +193,7 @@ private AuthenticationResult getAuthenticationCredentials(Map ch String resource = challengeMap.get("resource"); String scope = challengeMap.get("scope"); - String schema = "true".equals(challengeMap.get("supportspop")) ? "pop" : "bearer"; + String schema = supportsPop ? "pop" : "bearer"; return doAuthenticate(authorization, resource, scope, schema); } diff --git a/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java b/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java index 9508a342aa818..f60034691b0b7 100644 --- a/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java +++ b/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import java.util.UUID; import java.util.concurrent.ExecutionException; +import okhttp3.internal.http.HttpHeaders; import okio.Buffer; @@ -55,6 +56,7 @@ public class HttpMessageSecurity { * string with server signing key (public only) or null if not supported */ public HttpMessageSecurity(String _clientSecurityToken, String _clientSignatureKeyString, String _serverEncryptionKeyString, String _serverSignatureKeyString) throws IOException{ + this.clientSecurityToken = _clientSecurityToken; if (_clientSignatureKeyString != null && !_clientSignatureKeyString.equals("")){ @@ -115,6 +117,10 @@ public Request protectRequest(Request request) throws IOException { request.body().writeTo(buffer); String currentbody = buffer.readUtf8(); + if (currentbody == null || currentbody.length() == 0){ + return result; + } + JsonWebKey clientPublicEncryptionKey = MessageSecurityHelper.GetJwkWithPublicKeyOnly(clientEncryptionKey); String payload = currentbody.substring(0, currentbody.length() - 1) + ",\"rek\":{\"jwk\":" + clientPublicEncryptionKey.toString() + "}}"; @@ -165,8 +171,13 @@ public Request protectRequest(Request request) throws IOException { */ public Response unprotectResponse(Response response) throws IOException{ try{ - if (!supportsProtection()) + if (!supportsProtection() || !HttpHeaders.hasBody(response)){ return response; + } + + if (!response.header("content-type").toLowerCase().contains("application/jose+json")){ + return response; + } JWSObject jwsObject = JWSObject.deserialize(response.body().string()); JWSHeader jwsHeader = jwsObject.jwsHeader(); diff --git a/azure-keyvault/src/test/java/com/microsoft/azure/keyvault/test/EnhancedKeyVaultTest.java b/azure-keyvault/src/test/java/com/microsoft/azure/keyvault/test/EnhancedKeyVaultTest.java index 994bc5f21308b..772446fe6742c 100644 --- a/azure-keyvault/src/test/java/com/microsoft/azure/keyvault/test/EnhancedKeyVaultTest.java +++ b/azure-keyvault/src/test/java/com/microsoft/azure/keyvault/test/EnhancedKeyVaultTest.java @@ -100,7 +100,13 @@ private Response getResponse(){ String responseBodyString = new String(Base64.decodeBase64("eyJwcm90ZWN0ZWQiOiJleUpoYkdjaU9pSlNVekkxTmlJc0ltdHBaQ0k2SWpSaU9EWTBaR0ptTFRZMFpHUXRORFZqWVMwNFlqWTVMV05tWldNd05EVTJOR0kxTUNJc0ltRjBJam9pVkc5clpXNGlMQ0owY3lJNk1Dd2ljQ0k2Ym5Wc2JDd2lkSGx3SWpvaVVHOVFJbjAiLCJwYXlsb2FkIjoiZXlKd2NtOTBaV04wWldRaU9pSmxlVXBvWWtkamFVOXBTbE5WTUVWMFZEQkdSbFZEU1hOSmJYUndXa05KTmtscVdUQlBWRmt4V2tkT2FVeFVTbXhPYWxGMFRrZEZlVTVETVdsWlYwMTNURmRSTVZscVl6QlpNa2w0V21wa2FrNURTWE5KYlZaMVdYbEpOa2xyUlhoTmFtaEVVV3ROZEZOR1RYbE9WRmxwWmxFaUxDSmxibU55ZVhCMFpXUmZhMlY1SWpvaVFsVnNkVEJIWDJObldVSm9iVFYzUm5WaGEzZHViekEwUkZVd1kxTkRYemxVWjNwRGVsVlNRMWd5Y1VkdWRqRldaRTFRU0RVelpFcE1TbkZJT0ROc2IyWmplVTVSV0hGWWEwRlZkbVUxYVhVdFNGVlpXRmRwVmpOYVNsaGhWRVZRTUZKb2RHZHdaMkl3VUhGMFZtSlVibWRHU25VNVJ6Rm5VMWswTmxaTVdYaGtWek41ZGtsTFpFRmhRalV5Ums4M1MyOVlZVWw2T1ROblgxOHdjMnhuV1hSTlkxSjNkRm80VlZaTVpFVjNOemxXZUY5elMybGZNbVZyVm5VeFFYaE1iV0o2ZWxKeVNYQnplblJhUWpKWWNIWlRSak16VFRoVE1XSXRWVnBSU2s5QlVqWmpaQzE0UkVsRFYzVTRWbEJLZUVaalFWSlVaVlpHT1ZCaGNqbFBSMEpKV1ZOd1NXMXdTRlpYTUVWWE4yTkVSVVl6YWpCU2NFaEVRMko1YUZoeGFtVmpUMEZsVlV4MVRtaExOa0ZLYVhwTmF6Rldha3RFVldzM09HSlVhbkpSYkZOV1ZHWmxOamxTYVdGYVJsUXRabmhtYVhCQklpd2lhWFlpT2lKV1JWWlVWa1JGZVUxNlVsVlNWazVWVFZSSmVrNUJJaXdpWTJsd2FHVnlkR1Y0ZENJNkltVnVNVXQxZURORWMzaDBNek5pT1Vwa1ZIcExlbkpEZW1GNVVtVmxhRVo2T0Y5dlZtRkxYMVpQUlhWa2NXb3pkVGt3WWxWamFqVlllV2xyVVdGSFMyUXlZa0V3T0hsaWVuaHRUalZMVFVkbVRGSjBRbWMwVGpOMFNEQmhXRGxFWHpKSk4wdFZVRk5SY25GQ1dGTkpaSFZtV0RsaFdrUnZaRTl4ZW0xMWFWRmpkWFJUVWtWVFJtRkJUa2xTWHpCS2FsUkpORmhOVW1RMk5uQXdiVFJLVjA5TlluSmZkRlJ3VDNwVmJtbEhXRzloYUdZMU1sSk5kV3MxVlRCWFNrOU9aSGRxWlhwdll6bHdWM0JLTUdSTVZVVmlhVGhQVjJzMk9IVkdkemR3TFRWc1JIcFRSbGx4WVhOQ2FsaHFXSFJLV1c1alUzRnZUbTU0Y1VFMWVtTjBSSGxpUm5NNFMzSXRWSGRCYUU1S2JYQnJUVzVpYkZoTWVrczRVMGxZWkc1alZEbHJRWEJFWVZKWFQzWkNVa014ZVdaNlFUTmhhemd6VmpaSlFUZzRRMGt5WVRWcmMzSm9lakZIWTNKWllVVkRkWEY1ZFROMFNsZ3RMVVIyWlhSRWIwSlpkbDgzWlY5eWN6RnNWMVIzV0VOdk9FdG5MVVZzTTA1VFVFTnJjM2xFT1RWWFRYRkhSVWx6Y1V4aU1XVllWazEwWVd4aE5scGZYMHQyVTFSWVRVa3pZa0pVYkRoTFJITklNME5GTW5FNWNFdGxSbXBvTFROdVVuQnNibGcwYVdWcldsUjFRbWxrVDJaT1ozQktkRk13TTFSM05uVlJRbTB0WXpaWVRrUlNTR00xTUZjMFJ6WTFOemMxWDJJMmFFZGZjbmcxYkZSV2NYUTNaamRRTkZGeU1tRnFPSE5RTUdwaVJYWk1SRkJYWlhKYVdtNXpUak55T0VscmIwcFpPVTVLT0ZSelJuRXpSemMyVDE5U1gyVnJNRlZQY0Vob2VTMDFWR3B0UW1wTWFYSm1SMGRaZW5RNGJEWkRkVGd4YzBJdExVZG1SSEYwVkRNNVQxTTJaM2RvYWsxQlUxSjRORzQwYkRCVmRFbFNiMlpCWVdNd1IzWkNhVUZrV2xRMVIzcEVYekZZYUdSdGVGSnhNbmR1Vms0eE1XaDRSeTFTTUY5ZlFYQlNVVUY1WVdwaWNGQnFhMkYxWDE5ck5qVnFibTFWYWxGcGNXbHNjM1Z4UkZkR2JVWXlja1V0VlUwMFFXRkxSVlI2TUdWdFJtd3diakZ5WVRWRlNYVkpXRXczVFVjNVgybDZVMlZ2UlRaT1lWWnRkMnRHWnpKbFpqTnRNbk0xWTFKMlRGZ3hSeUlzSW5SaFp5STZJbUZIWTAwelVrWnJOR2xQVG5KZmVGUjBURkpQV21jaWZRIiwic2lnbmF0dXJlIjoiSXYzMzZ4Z1ZMVjNjOXpVb1RIcVRPd3Z6cHg2VTBySXpYaVRqS1Fxak8zSmZsbUJWczJ1OEw0STNDVFhSbGZuU0U5Y0plSXNuazR1Nmh1ZzhhakN0eHdVczBaUjVJREtPaDlfYjJoTG5vZEhQaE9Fb1dEMTBaWEJSZFlKb2dqVDd6cXhNSkd2NC1Mdmg5aGp5TE5BLUZ5TXd0dG9QVmQxdjRfOVlNQzNGczhUTHAyZTlrRHdreUxYUXpDYXN1ZkpRSGVybW82MDFJcXFfUVVZbTZnaWJCVXFMOWI4NEk3OHNOSVRLcWxkb0tGLXVKeHcxNzJRM2NMbEZycEJIWWhDalB2OFYtZnJHOGtMOVBhXzdaRjJBME4ycTNkR1ltMDAzWGo3VllmUDBkVU1rTFRnLUppakJNUzNnMDlqVTdLck5xRjlkUF9xejhjXzFjaG1JWFhxa3VRIn0=")); ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/jose+json"), responseBodyString); - return (new Response.Builder()).body(responseBody).request(getRequest()).protocol(okhttp3.Protocol.HTTP_2).code(200).build(); + return (new Response.Builder()) + .header("content-type", "application/jose+json") + .body(responseBody) + .request(getRequest()) + .protocol(okhttp3.Protocol.HTTP_2) + .code(200) + .build(); } private String getRequestBody(Request request) throws Exception{