From 9d26f12e86d0349af33cea3b2de14bb491b16186 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 19 Dec 2019 10:05:10 -0800 Subject: [PATCH] Add an example of Base64 encoding that failed with java.util.Base64 Revert usage to Apache Commons Codec (dependency by OpenSaml) --- .../service/servlet/filter/Saml2Utils.java | 9 ++- .../servlet/filter/Saml2UtilsTests.java | 70 +++++++++++++++++++ .../saml2-response-sso-circle.encoded | 1 + 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java create mode 100644 saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java index 3404883e82..994db67849 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java @@ -16,6 +16,7 @@ package org.springframework.security.saml2.provider.service.servlet.filter; +import org.apache.commons.codec.binary.Base64; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.util.StringUtils; @@ -24,7 +25,6 @@ import org.springframework.web.util.UriComponentsBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.zip.Deflater; @@ -44,15 +44,14 @@ import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl; final class Saml2Utils { private static final char PATH_DELIMITER = '/'; - private static Base64.Encoder ENCODER = Base64.getEncoder(); - private static Base64.Decoder DECODER = Base64.getDecoder(); + private static org.apache.commons.codec.binary.Base64 BASE64 = new Base64(0, new byte[]{'\n'}); static String encode(byte[] b) { - return ENCODER.encodeToString(b); + return BASE64.encodeAsString(b); } static byte[] decode(String s) { - return DECODER.decode(s); + return BASE64.decode(s); } static byte[] deflate(String s) { diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java new file mode 100644 index 0000000000..972937715c --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.servlet.filter; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StreamUtils; +import org.springframework.web.util.UriUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +public class Saml2UtilsTests { + + private static Base64 UNCHUNKED_ENCODER = new Base64(0, new byte[]{'\n'}); + private static final Base64 CHUNKED_ENCODER = new Base64(76, new byte[] { '\n' }); + + @Test + public void decodeWhenUsingApacheCommonsBase64ThenXmlIsValid() throws Exception { + String responseUrlDecoded = getSsoCircleEncodedXml(); + String xml = new String(UNCHUNKED_ENCODER.decode(responseUrlDecoded.getBytes(UTF_8)), UTF_8); + validateSsoCircleXml(xml); + } + + @Test + public void decodeWhenUsingApacheCommonsBase64ChunkedThenXmlIsValid() throws Exception { + String responseUrlDecoded = getSsoCircleEncodedXml(); + String xml = new String(CHUNKED_ENCODER.decode(responseUrlDecoded.getBytes(UTF_8)), UTF_8); + validateSsoCircleXml(xml); + } + + @Test + public void decodeWhenUsingSamlUtilsBase64ThenXmlIsValid() throws Exception { + String responseUrlDecoded = getSsoCircleEncodedXml(); + String xml = new String(Saml2Utils.decode(responseUrlDecoded), UTF_8); + validateSsoCircleXml(xml); + } + + private void validateSsoCircleXml(String xml) { + assertThat(xml) + .contains("InResponseTo=\"ARQ9a73ead-7dcf-45a8-89eb-26f3c9900c36\"") + .contains(" ID=\"s246d157446618e90e43fb79bdd4d9e9e19cf2c7c4\"") + .contains("https://idp.ssocircle.com"); + } + + private String getSsoCircleEncodedXml() throws IOException { + ClassPathResource resource = new ClassPathResource("saml2-response-sso-circle.encoded"); + String response = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); + return UriUtils.decode(response, UTF_8); + } + +} diff --git a/saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded b/saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded new file mode 100644 index 0000000000..42e5fba09c --- /dev/null +++ b/saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6%0D%0AcHJvdG9jb2wiIElEPSJzMjQ2ZDE1NzQ0NjYxOGU5MGU0M2ZiNzliZGQ0ZDllOWUxOWNmMmM3YzQi%0D%0AIEluUmVzcG9uc2VUbz0iQVJROWE3M2VhZC03ZGNmLTQ1YTgtODllYi0yNmYzYzk5MDBjMzYiIFZl%0D%0AcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE5LTEyLTE5VDE3OjMxOjI0WiIgRGVzdGluYXRp%0D%0Ab249Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9sb2dpbi9zYW1sMi9zc28vc3NvY2lyY2xlIj48c2Ft%0D%0AbDpJc3N1ZXIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlv%0D%0AbiI%2BaHR0cHM6Ly9pZHAuc3NvY2lyY2xlLmNvbTwvc2FtbDpJc3N1ZXI%2BPHNhbWxwOlN0YXR1cyB4%0D%0AbWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj4KPHNhbWxw%0D%0AOlN0YXR1c0NvZGUgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90%0D%0Ab2NvbCIgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyI%2B%0D%0ACjwvc2FtbHA6U3RhdHVzQ29kZT4KPC9zYW1scDpTdGF0dXM%2BPHNhbWw6QXNzZXJ0aW9uIHhtbG5z%0D%0AOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJzMjg4MjBl%0D%0AZWQyY2QyNWQ0Y2I3ZTAwOTg1OTM4OGMzMWU1ZjFlZWZhOTciIElzc3VlSW5zdGFudD0iMjAxOS0x%0D%0AMi0xOVQxNzozMToyNFoiIFZlcnNpb249IjIuMCI%2BCjxzYW1sOklzc3Vlcj5odHRwczovL2lkcC5z%0D%0Ac29jaXJjbGUuY29tPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8v%0D%0Ad3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8%2BCjxkczpDYW5vbmlj%0D%0AYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwt%0D%0AZXhjLWMxNG4jIi8%2BCjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3Lncz%0D%0ALm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4KPGRzOlJlZmVyZW5jZSBVUkk9IiNzMjg4%0D%0AMjBlZWQyY2QyNWQ0Y2I3ZTAwOTg1OTM4OGMzMWU1ZjFlZWZhOTciPgo8ZHM6VHJhbnNmb3Jtcz4K%0D%0APGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNp%0D%0AZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8%2BCjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8v%0D%0Ad3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KPC9kczpUcmFuc2Zvcm1zPgo8ZHM6%0D%0ARGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2ln%0D%0AI3NoYTEiLz4KPGRzOkRpZ2VzdFZhbHVlPnc2SkVMMGRNNGVuT0EwWmQ5Q0tpbXNwTHpEUT08L2Rz%0D%0AOkRpZ2VzdFZhbHVlPgo8L2RzOlJlZmVyZW5jZT4KPC9kczpTaWduZWRJbmZvPgo8ZHM6U2lnbmF0%0D%0AdXJlVmFsdWU%2BCkg1TGh5VUZITnhzUUdaaWZjbmpkVUV1cnVCSTJ5UkpoYXJJQ1hoUkU5c1dBYkg3%0D%0ASWxGUWx1S2NmV3hGbjNpNEdIdzZRZDg3cDVkL3EKUE5ZTmptTVpUemU2bENIS2dmenZmRExObjNU%0D%0AM0ZhWXFtYkxvTkJUU1YxMTk1Wmgxa1FhdFIwY1NHZXF3NHJtbGR0enZvRzZrNk92YwovRUk5U05i%0D%0ARFJJYURBOW1lbXdEOG0wczVoSUxMb09uRGNQb2lxTHNKR3laYmR2YVZ3RVlnYS9XMitmSmNpTWNB%0D%0ARDViMGdpOWpab05oCko2dnhIcVpwaEg1dm9MZTNFdGVPQ083TTFhMTVmNk9jWU9MeXFYbFFUdytu%0D%0AeElsYnBEeG03ZTQ4VVljL1htYmRwMUsvNlMwOFdmZWkKNFZLL3ZSZjVaUnZ4ckZ4V2ZrRlRGYlN2%0D%0AVkVlOFpURWM3NlEzWkE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz4KPGRzOlg1%0D%0AMDlEYXRhPgo8ZHM6WDUwOUNlcnRpZmljYXRlPgpNSUlFWXpDQ0FrdWdBd0lCQWdJRElBWm1NQTBH%0D%0AQ1NxR1NJYjNEUUVCQ3dVQU1DNHhDekFKQmdOVkJBWVRBa1JGTVJJd0VBWURWUVFLCkRBbFRVMDlE%0D%0AYVhKamJHVXhDekFKQmdOVkJBTU1Ba05CTUI0WERURTJNRGd3TXpFMU1ETXlNMW9YRFRJMk1ETXdO%0D%0AREUxTURNeU0xb3cKUFRFTE1Ba0dBMVVFQmhNQ1JFVXhFakFRQmdOVkJBb1RDVk5UVDBOcGNtTnNa%0D%0AVEVhTUJnR0ExVUVBeE1SYVdSd0xuTnpiMk5wY21OcwpaUzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNE%0D%0AUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNBd1dKeU9ZaFltV1pGMlRKdm0xVnlaY2NzCjNaSjBU%0D%0Ac05jb2F6cjJwVFdjWThXVFJiSVY5ZDA2ellqbmd2V2lieWl5bGV3R1hjWU9OQjEwNlpOVWROZ3Jt%0D%0ARmQ1MTk0V3N5eDZiUHYKbmpaRUVSbnk5TE9mdXdRYXFEWWVLaEk2Yyt2ZVhBcG5PZnNZMjZ1OUxx%0D%0AYjlzZ2E5Sm5Da1VHUmFvVnJBVk0zeWZnaHYvQ2cvUUVnKwpJNlNWRVM3NXRLZGNMRFR0L0Z3bUFZ%0D%0AREVCVjhsNTJiY01ETkYrSld0QXVldEk5L2RXQ0JlOVZUQ2FzQXIyRnh3MVpZVEFpcUdJOXNXCjRr%0D%0AV1MyQXBlZGJxc2dIM3FxTWxQQTd0ZzlpS3k4WXcvZGVFbjBxUUl4OEdsVm5RRnBEZ3pHOWsrandC%0D%0Ab2ViQVlmR3ZNY08vQkRYRDIKcGJXVE4rRHZiVVJsQWdNQkFBR2plekI1TUFrR0ExVWRFd1FDTUFB%0D%0Ad0xBWUpZSVpJQVliNFFnRU5CQjhXSFU5d1pXNVRVMHdnUjJWdQpaWEpoZEdWa0lFTmxjblJwWm1s%0D%0AallYUmxNQjBHQTFVZERnUVdCQlFoQW1DZXdFN2FvbkF2eUpmakltQ1JaRHRjY1RBZkJnTlZIU01F%0D%0ACkdEQVdnQlRBMW5FQSswemE2cHBMSXRrT1g1eUVwOGNRYVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9D%0D%0AQWdFQUFoQzUvV3NGOXp0SkhnbysKeDlLVjlicVZTME1tc2dwRzI2eU9BcUZZd09TUG1VdVltSm1I%0D%0AZ21LR2pLcmoxZmRDSU50emNCSEZGQkMxbWFHSjMzbE1rMmJNMlRIeAoyMi9POTNmNFJGbkZhYjd0%0D%0AMjNqUkZjRjBhbVFVT3NEdmx0Zkp3N1hDYWw4SmRnUFVnNlROQzRGeTlYWXYwT0FIYzNvRHAzdmwx%0D%0AWWo4Ci8xcUJnNlJjMzlrZWhtRDV2OFNLWW1wRTd5Rkt4REYxb2w5REtERy9MdkNsU3ZudVZQMGI0%0D%0AQldkQkFBOWFKU0Z0ZE5HZ0V2cEVVcUcKa0oxb3NMVnFDTXZTWXNVdEhtYXBhWDNoaU05UmJYMzhq%0D%0Ac1Nnc2w0NFJhcjVJb2M3S1hPT1pGR2ZFS3l5VXF1Y1lwaldDT1hKRUxBVgpBenA3WFR2QTJxNTV1%0D%0AMzFoTzB3OFl4NHVFUUtsbXhEdVpteHBNejRFV0FSeWpIU0F1REtFVzFSSnZVcjYrNXVBOXFlT0t4%0D%0ATGlLTjFqCm82ZVdBY2w2V3I5TXJlWFI5a0ZwUzZrSGxsZmRWU3JKRVM0U1QwdWgxSnA0RVlnbWl5%0D%0ATW1GQ2JVcEtYaWZwc05XQ0xEZW5FM2hsbEYKMCtxM3dJZHUrNFA4MlJJTTcxbjdxVmduRG5LMjl3%0D%0AbkxoSERhdDlya0M2MkNJYm9ucGtWWW1uUmVYMGp6ZSs3dHdSYW5KT01DSitsRgpnMTZCRHZCY0c4%0D%0AdTBuL3dJRGtISGl0Qkk3YlUxazZjNkR5ZExRKzY5aDhTQ282c085WXVEKy8zeEFHS2FkNEltWjZ2%0D%0AVHdsQjR6RENwCnU2WWdRV29jV1JYRStWa09iK1JCZnZQNzU1UFVhTGZMNjNBRlZscE9uRXBJaW81%0D%0AKytVak5KUnVQdUFBPQo8L2RzOlg1MDlDZXJ0aWZpY2F0ZT4KPC9kczpYNTA5RGF0YT4KPC9kczpL%0D%0AZXlJbmZvPgo8L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0Pgo8c2FtbDpOYW1lSUQgRm9ybWF0%0D%0APSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDp1bnNwZWNpZmllZCIg%0D%0ATmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9pZHAuc3NvY2lyY2xlLmNvbSI%2BZmhhbmlrcGl2b3RhbDwv%0D%0Ac2FtbDpOYW1lSUQ%2BPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpu%0D%0AYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPgo8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0%0D%0AYSBJblJlc3BvbnNlVG89IkFSUTlhNzNlYWQtN2RjZi00NWE4LTg5ZWItMjZmM2M5OTAwYzM2IiBO%0D%0Ab3RPbk9yQWZ0ZXI9IjIwMTktMTItMTlUMTc6NDE6MjRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2Nh%0D%0AbGhvc3Q6ODA4MC9sb2dpbi9zYW1sMi9zc28vc3NvY2lyY2xlIi8%2BPC9zYW1sOlN1YmplY3RDb25m%0D%0AaXJtYXRpb24%2BCjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE5%0D%0ALTEyLTE5VDE3OjIxOjI0WiIgTm90T25PckFmdGVyPSIyMDE5LTEyLTE5VDE3OjQxOjI0WiI%2BCjxz%0D%0AYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24%2BCjxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9sb2NhbGhvc3Q6%0D%0AODA4MC9zYW1sMi9zZXJ2aWNlLXByb3ZpZGVyLW1ldGFkYXRhL3Nzb2NpcmNsZTwvc2FtbDpBdWRp%0D%0AZW5jZT4KPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24%2BCjwvc2FtbDpDb25kaXRpb25zPgo8c2Ft%0D%0AbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTktMTItMTlUMTY6Mzg6NTFaIiBTZXNz%0D%0AaW9uSW5kZXg9InMyN2NkMjI4ZTIzMjNlZDM5MzM3YjM1NDcyODk5MmQ5ODRiMDNiYzEwMSI%2BPHNh%0D%0AbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1l%0D%0Aczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1s%0D%0AOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ%2BPC9zYW1sOkF1dGhuU3Rh%0D%0AdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iRW1h%0D%0AaWxBZGRyZXNzIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5v%0D%0AcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxT%0D%0AY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZoYW5pa0BwaXZvdGFsLmlvPC9z%0D%0AYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2BPHNhbWw6QXR0cmlidXRlIE5hbWU9%0D%0AIlVzZXJJRCI%2BPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3Jn%0D%0ALzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2No%0D%0AZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5maGFuaWtwaXZvdGFsPC9zYW1sOkF0%0D%0AdHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2BPHNhbWw6QXR0cmlidXRlIE5hbWU9IkZpcnN0%0D%0ATmFtZSI%2BPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIw%0D%0AMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1h%0D%0ALWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5GaWxpcDwvc2FtbDpBdHRyaWJ1dGVWYWx1%0D%0AZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJMYXN0TmFtZSI%2BPHNhbWw6%0D%0AQXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1h%0D%0AIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4%0D%0Ac2k6dHlwZT0ieHM6c3RyaW5nIj5IYW5pazwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0%0D%0AcmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ%2BPC9zYW1sOkFzc2VydGlvbj48L3NhbWxw%0D%0AOlJlc3BvbnNlPg%3D%3D