Skip to content

Commit b11137a

Browse files
authored
Support default RSA-OAEP decryption parameters
Assign default RSA-OAEP parameters according to RFC 4337 if they were not in the XML
1 parent 8075fa6 commit b11137a

File tree

4 files changed

+59
-10
lines changed

4 files changed

+59
-10
lines changed

build.savant

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jakartaXMLBindVersion = "4.0.0"
1919
slf4jVersion = "2.0.7"
2020
testngVersion = "7.8.0"
2121

22-
project(group: "io.fusionauth", name: "fusionauth-samlv2", version: "0.11.0", licenses: ["ApacheV2_0"]) {
22+
project(group: "io.fusionauth", name: "fusionauth-samlv2", version: "0.11.1", licenses: ["ApacheV2_0"]) {
2323
workflow {
2424
fetch {
2525
cache()

src/main/java/io/fusionauth/samlv2/service/DefaultSAMLv2Service.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,12 +1205,23 @@ private Key decryptKey(EncryptedKeyType encryptedKey, PrivateKey transportEncryp
12051205
mgf = MaskGenerationFunction.MGF1_SHA1;
12061206
}
12071207

1208-
// Check we have necessary parameters
1208+
// If the parameters could not be resolved:
1209+
// 1) Use defaults from RFC 3447 if no values were found in XML
1210+
// 2) Throw an exception if an XML value could not be resolved to an enum value
1211+
// https://www.rfc-editor.org/rfc/rfc3447#appendix-A.2.1
12091212
if (digest == null) {
1210-
throw new SAMLException("Unable to determine digest algorithm from URI [" + digestUri + "]");
1213+
if (digestUri == null) {
1214+
digest = DigestAlgorithm.SHA1;
1215+
} else {
1216+
throw new SAMLException("Unable to determine digest algorithm from URI [" + digestUri + "]");
1217+
}
12111218
}
12121219
if (mgf == null) {
1213-
throw new SAMLException("Unable to determine mask generation function from URI [" + mgfUri + "]");
1220+
if (mgfUri == null) {
1221+
mgf = MaskGenerationFunction.MGF1_SHA1;
1222+
} else {
1223+
throw new SAMLException("Unable to determine mask generation function from URI [" + mgfUri + "]");
1224+
}
12141225
}
12151226

12161227
// Create cryptographic parameters and initialize the cipher instance

src/test/java/io/fusionauth/samlv2/service/DefaultSAMLv2ServiceTest.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.security.cert.Certificate;
4343
import java.security.cert.CertificateFactory;
4444
import java.security.cert.X509Certificate;
45+
import java.security.spec.RSAPrivateKeySpec;
4546
import java.security.spec.RSAPublicKeySpec;
4647
import java.security.spec.X509EncodedKeySpec;
4748
import java.time.ZoneOffset;
@@ -111,6 +112,42 @@
111112
@SuppressWarnings({"unchecked"})
112113
@Test(groups = "unit")
113114
public class DefaultSAMLv2ServiceTest {
115+
@Test
116+
public void assertionDecryptionDefaults() throws Exception {
117+
// If RSA-OAEP Digest and Mask Generation Function are not specified by XML, decryption should fall back to the defaults
118+
119+
// Build a known key pair.
120+
KeyFactory factory = KeyFactory.getInstance("RSA");
121+
// The public key is not required for this test, but it may be useful to modify or expand this test in the future
122+
// PublicKey publicKey = factory.generatePublic(new RSAPublicKeySpec(new BigInteger("21734648244307152755738902242704624429675455693104061482953980655823499524284217582577935962219675181839097134429878676848067944269649003417313253763145613039845156858929146350893510281417425701635390227843218753386852942958087790126591910892081707753005524949329857277363222746280909051526362184081185954039703446436022345307092346517413518280909483768946131477611274390374625720745000173012484689181319542884541163003470909355448313533318136237678943263133529991715284549440616270148923866161198748312992261382455526114770464413102345807150728423473869759031086596301998397561122681012070445972165920288084712186321"), new BigInteger("65537")));
123+
PrivateKey privateKey = factory.generatePrivate(
124+
new RSAPrivateKeySpec(
125+
new BigInteger("21734648244307152755738902242704624429675455693104061482953980655823499524284217582577935962219675181839097134429878676848067944269649003417313253763145613039845156858929146350893510281417425701635390227843218753386852942958087790126591910892081707753005524949329857277363222746280909051526362184081185954039703446436022345307092346517413518280909483768946131477611274390374625720745000173012484689181319542884541163003470909355448313533318136237678943263133529991715284549440616270148923866161198748312992261382455526114770464413102345807150728423473869759031086596301998397561122681012070445972165920288084712186321"),
126+
new BigInteger("2627246950446332058699110175423135552922607992443510918533979809198372876869242896214083780043399404771798030104421912476774863886112568550161826087731198353627009668377202151330979270479101063648863411278727725778272563805391938498601722966981572071033305898173415465329072899217806147766785803779409419532480730005663347830294097525463269173509836986107754922630483079886942638729284878709865541937468056706189367357847138095922090696226255189351459450052835991176393120796259048519702721129794393046985282164398590601866202429118551867608688177161937729422431790107723304390299253182892873596285122556471119338537")
127+
)
128+
);
129+
130+
// Load an unsigned sample response encrypted using the associated public certificate from above
131+
byte[] ba = Files.readAllBytes(Paths.get("src/test/xml/encodedResponse-assertionDecryptionDefaults.txt"));
132+
String encodedXML = new String(ba, StandardCharsets.UTF_8);
133+
134+
// Parse the encrypted sample response
135+
DefaultSAMLv2Service service = new DefaultSAMLv2Service();
136+
AuthenticationResponse parsedResponse = service.parseResponse(
137+
encodedXML,
138+
false, null,
139+
true, privateKey
140+
);
141+
142+
// Load a known encoded sample response from file and parse it
143+
ba = Files.readAllBytes(Paths.get("src/test/xml/encodedResponse.txt"));
144+
String encodedResponse = new String(ba, StandardCharsets.UTF_8);
145+
AuthenticationResponse response = service.parseResponse(encodedResponse, false, null);
146+
147+
// Verify the parsed encrypted response matches the original pulled from file
148+
assertEquals(parsedResponse, response);
149+
}
150+
114151
@DataProvider(name = "assertionEncryption")
115152
public Object[][] assertionEncryption() {
116153
return new Object[][]{
@@ -547,8 +584,8 @@ public void parseLogout_Request_raw(Binding binding) throws Exception {
547584
String redirectSignature = Files.readString(Paths.get("src/test/xml/signature/logout-request.txt"));
548585
String x509encoded = "MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==";
549586
try (InputStream is = new ByteArrayInputStream(Base64.getMimeDecoder().decode(x509encoded))) {
550-
CertificateFactory factor = CertificateFactory.getInstance("X.509");
551-
certificate = (X509Certificate) factor.generateCertificate(is);
587+
CertificateFactory factory = CertificateFactory.getInstance("X.509");
588+
certificate = (X509Certificate) factory.generateCertificate(is);
552589
}
553590

554591
assertNotNull(certificate);
@@ -1025,8 +1062,8 @@ public void parse_LogoutRequest(Binding binding) throws Exception {
10251062
String redirectSignature = Files.readString(Paths.get("src/test/xml/signature/logout-request.txt"));
10261063
String x509encoded = "MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==";
10271064
try (InputStream is = new ByteArrayInputStream(Base64.getMimeDecoder().decode(x509encoded))) {
1028-
CertificateFactory factor = CertificateFactory.getInstance("X.509");
1029-
certificate = (X509Certificate) factor.generateCertificate(is);
1065+
CertificateFactory factory = CertificateFactory.getInstance("X.509");
1066+
certificate = (X509Certificate) factory.generateCertificate(is);
10301067
}
10311068

10321069
assertNotNull(certificate);
@@ -1069,8 +1106,8 @@ public void parse_LogoutResponse(Binding binding) throws Exception {
10691106
String redirectSignature = Files.readString(Paths.get("src/test/xml/signature/logout-response.txt"));
10701107
String x509encoded = "MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==";
10711108
try (InputStream is = new ByteArrayInputStream(Base64.getMimeDecoder().decode(x509encoded))) {
1072-
CertificateFactory factor = CertificateFactory.getInstance("X.509");
1073-
certificate = (X509Certificate) factor.generateCertificate(is);
1109+
CertificateFactory factory = CertificateFactory.getInstance("X.509");
1110+
certificate = (X509Certificate) factory.generateCertificate(is);
10741111
}
10751112

10761113
assertNotNull(certificate);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PG5zMzpSZXNwb25zZSB4bWxuczpuczM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOm5zMj0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIgeG1sbnM6bnM0PSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIERlc3RpbmF0aW9uPSJodHRwczovL2xvY2FsLmZ1c2lvbmF1dGguaW8vb2F1dGgyL2NhbGxiYWNrIiBJRD0iX2M4MTgzN2FjLTY0NjUtNDFjYy1hMTAyLTYyNTdkNzExMjQ3MyIgSW5SZXNwb25zZVRvPSJpZDMxZjgzYjM1LTU5NTQtNGI2Ny1hY2Y4LTc2M2MxNWZjM2FhYyIgSXNzdWVJbnN0YW50PSIyMDE5LTAyLTI4VDE4OjI5OjU1LjQ3M1oiIFZlcnNpb249IjIuMCI+PElzc3Vlcj5odHRwczovL3N0cy53aW5kb3dzLm5ldC9jMjE1MDExMS0zYzQ0LTQ1MDgtOWYwOC03OTBjYjQwMzJhMjMvPC9Jc3N1ZXI+PG5zMzpTdGF0dXM+PG5zMzpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L25zMzpTdGF0dXM+PEVuY3J5cHRlZEFzc2VydGlvbj48bnM0OkVuY3J5cHRlZERhdGEgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCI+PG5zNDpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwOS94bWxlbmMxMSNhZXMyNTYtZ2NtIi8+PG5zNDpDaXBoZXJEYXRhPjxuczQ6Q2lwaGVyVmFsdWU+VDFyeVNrOWVBWU1LYmVhbFpUbGR1UzhManFGTVhyT2gxMitLNUEwYy96bVhGeTlEMXpXMXFWb040ZTJ2YnVVaW04V0ppVU5DUW5zL3ZPaDFCMHZ0eTRjcHdqUXZGTnJQZkg1cEQ4UkpKTTRpUnIxQkFWZGszdmw3NEYybUx0WmMwQWh5WTk1NnB3akRJTkdLQ3J2dXhjYUsrQk4rY2creTFReDVRandadS9ERUk3VTFqMEw4dFNzRWUwTEFRWWdqOWhWU1VSNGNKRmVIQmV3K2lRNWJ4dFF6d2tHdWdxTUdDeTE3ejlMeCtmV1Z4Sm9jYnIrYVBLVDEybVZlM3VsMFBackZrUnc2MTBocjRuMEtDNUZuazBSODBJZzBJVm40YXJBMHQ4eWlzanAxY096UVhtd2tWMk5iOTdRMFZrbkcxVVBYNnk0d29QTXNHenZmUytvVklVL0NmblFiS21UVFlvdlgyTVVqSFdKbENXcy9sN00xWFRzQXZ0b3gvT3BrT3ZYME5yT3VYMUpqSXYrYmd2VWdjTG9BMUFpTkJTNlJLZThleEdmbzNGVjFYdFJZdUtRaXkyc090QmYrU0w2ZytJZjZGcHErcmNjUzgzY0RPcE9laGtqcHNPcVYzSnJlOVRldVJhbEZ2RTAvNmhZTXM2cWVacCsrZndIVUtyME5nSlJ2dGRpcjNSWUdGZTh2REk5dU02blZyRFhtcjhCdjYzYVZ1cGtQWXZLTUc3bldMSXlMcGF4aEVmb3dFakE3andsdjdGNktxUHpQV293SXFOVUc1aDVJbnhuT3BSNjVHUFAvZHR0bmIxOGxvbUdac1FMSkJGb3JsblFZWUkvMnYzNFZmaEI1c2tMZkYwVk9Ia1RwUDZvQ0JIc1NZWUVpL2pOUUgvMzFUUk44bC9yM2Q0MVV2ZEtwaW1TUXAwa1ArSlp3a3oyMXFiMXFXbytlcnZkK3hlOWxLRFFDV0FiWmJRdjB2OEJXL2lFTXJLTXRCSkx6alg1TExaSVVhT00wYnp5MW1VSlFhZXdGMWx1WSswWnJrUU9IOVQ4ZkdaWDZXMmFxN3dqQzdtdlNvMWdoM3pNVmZZNzBONENxdWRjenB1M2ZVMWNid2t2TjFSVDJsTmkxdUlhNHZiNThiZ2xPLzVzUGxaSkwzSURhYkMwcll5VXJxbUdOdWVWblhtNUljT2pQMjNENXlHK3psNGFlNW45aVlwczdCYzQ1M2h1MmZuQlpDcElxdi84dllodTFlS3d3RzlYZkFVWUFSSEU0NlVwZC9KRVBLTWJsWllpWW1rd3F6Mnh0dnZId2N5OFRJOUlTNDJuQXgrUzd6UkhhcWRoSFptRmZCVkJVRzJFMXdrL3lYZG9rUnBPNytCQ3pudDEzbHlRMy9aZFZWNHB2cDNIaStITXlIY2VoUnVVam85ZXBUYitlRzFZSjZpL3JWK0ZyN2wxK0Z5dFRERFB0dVV3SHZhSVVLdElmN2Q1cGU3T3RMYzBOeURSeHppNkJxeWVhZE04Wk5SMUM3ZHVjWWRkdS8ra2V5eHlMTGZZbk9oWXlaOUNmQ0svWUtSTUJOMTZaQzJQdVBNV05ka1hoVDVHSXFCcWN4VzBMVWYxR3pNUEtNcVRoUmJ0UnVjK2RrOUV5R2gzQXowTEgwSGl6cnpGbTBtQ3BNcmZad2Nsc3R1OWpSSFVwZ0VmY0wrWE5ZaXZsSFRrM1hBZk5hb2tUUUpXMjhMZ3RrSUMwemlVKzhyRG96bTA4Ky9iQk90NlU5cjFObk5kSXdrRWtGdm56OVhHb0p1SW51cGozWkdDcy95eXRDMVp1N0NiQXdGTG82Zjh4RkZQcTdJNHJsVFlpQ1dZelBQUTF0ay9pdmh4QnRndjRvNzJQc0c3eS9tSzFuc2kzbmk4TWRMNmJucXdPWlpYS3ZYTERIb3YyRUxrZGZhcitCSWdoVENLMnRkaSszVU9uS1lqK0FwaFdUNk5SVUFESjNQdENpTVQ5eVRvTjBNTFFsVk05WWwzbXJkZElYMEg0QytJL0lOVWVwblMwdFJhVHJWNHhiMlB1VE5OaXpvYkl6MW9SVzZ0RlBZLzFUdFVHRSt2ODV0cStWTXltM1p0RmNJa3RKV0pTMHdMQmp6T280TjVtYUJyWGRtVGM0UzNPM3AyM3BrQXZieWdyVkNBSGswa0NrWnlhWWRxRW82dEpXdGpYdEgyWkNTQkpWemN1d2JMTFl5elF0dTJNODc5L0xIcTNZR0dTV2UyTkNUSWtqZzRwVm9OaGd2Z3VmZFZoQ0RwUkEwRHNWRVJPc2cvNVdiN1RBeDRIRjR5MnFZa3Q5TnZIcm9zWkE0U2pUWHptWElMa0wrUmNQREpUOENTcXBDT3ZhbzZMU2w4Ym8vR3c5R3JlS1ZOaXAvb3l4STdPTnBpYmNCTGZzUDhRZDFoMURtSVVTWW1SZDBIeFBGbUxqQjZKTTExNE4zc2ZJYzNNeE1iUGJFcVMzTVI5Zk9Ea1hsL1Z6Z1hiVnRzWE9ERHhOQVdTaWJUL05VUkNKWVdVL29ycUY5dVpRSWR5NWU2WHVHRzFqckdjYmtGK2FyYmJWUFVENVNxalRvR2dKRE02QWdUMlJHNDhENjg3N2hhVTlORmF4Vnd1QytjVGt3dGVSNHFiMFhoL1VRb0Q5eVJqUHFUZ1NMYkdIc3VXajk1VEFoQjBLT1daYzNYLzhtTTVuVWpySXAxK3B0bnBxOW5Lcm1kWXNnUWFjWHNNalh0T3pubisrdm5PSkpteTJIMEE0WkFHN0t1dXhFN1hnTHE3cE4yaHFDait4a1U3VFZXaW5FbVVhQll4akUwNUM2Ym10eFU4cmFUUW05aFlhVHljY1lWeGhYNTRVb1Z5K1Rxa2ZJdmRKd1l2RmlCQ01WQ1MwN0pTdVF2SVZQNjQ1UjRSM2FBeEZVcFg3akFXYS9IK25wV2hVYkV1dmlEemlUNVlmR0orVEUyQlAyMUc4NUJZb00yUUxOZlFjandidTdlZDJzNjlLRWZialBOWVc1RW1VWC9rSDFNa29NRUJSK014ZXExWFJNWkhPVG4yTGVhK1Q2YWtSTWYyWC82OEczeVBSSktlbE5QZkVGbXgvUHFyVlpNeHh5SUU3aEsrNExYWmpKNkdVZGxCbFpWZ3RzZkl5YU5DU2lkL2N2T25JdktxY3JGU1hwNFhUcUZNbXpuRjRTQ0wzcDR4NCtzUFhWQ0lVRG1ITmJDS1dBcEw1eGJMSEJiY2k3R2lyaWpBeTNEcnViNzZhTnNuR3RRRmtYUTU4SmEzNHk1VTVWc05yV1RRU2pkcmVCMWdyZVN3NzVUaithT3hFOUpOaElrWm4yM1JMUHhEdklGRVpJZnhNcUsrOTdHQXg0N3NxVEVtalpuT2xVZXRsZE1XdFRoeUpuTDJJSFE5dkc1SXB5RE9Zd0R4SjNadVNhcHVMblBLcHlhSi81SDlOb05XYWk5Nkd1L1B2ZndNYyt3Tkt0b0ZhMFJScWVKTWcwMmdGOS84czFxQVpjQ2o4Z3NqUjJtVEo5N05IZ0VNYTFJbjIvMGNXV2E5SDdiVlBlQU1DeElZaHQzVy9LSVJqN3Y1SEY5eVcyc1I4ZW4rQ0xBR1U2ekQ2SGFFaHplY3pIa0hZRUFGZVlSV01kMDl6a1FDSWVHc2doYm1mVDViRE9kSmpZc2wrTEttZVRqOUlxTWRYQU1JOUZzbFowVEppUXdRNDFCVFVuMzRXeHArOHdrRktiaVdZMU5NNnJVUGhMeWsrajdlNmYzTndzdGNrQXJsbFBPVUxDRGVENGRIVEpkRWc1VlRVSGJma1gzRGZoOHdhRzJTNE5zQ0pOcStoRHMwbEpEUmJZYUxqTURneEF6UHlOL0pqTjVMdG9rSU0xd3dseXFtbm1UUUlINDQzUEI1ZFJPa2xJUTBISXRDKzBjajBBZVZOcGZXelZkSWYwNk5xaDRsdzJxS1JIb0hydEU1ZEhob3YzdC9BcGo2UHZsVHBaNloxTUpkaEZ1TjM1OTliTHcyanNxYUxGZGtaMXNmT2ZqL29nZGRBc053YnZ1V3NMWHFVVFRhMEc2RWFpN0QzR0ZuNTFVeUFUZlFZQlZDV0lFeWpjNXlKV0FWbWZQTllDVWlSUU02dCtMUTZGR0RQcDBJRVVDNDJzNlRaTmJPMDYreDNMRmxQMlVOcGdCYk9FdktaTVVnNGJVSTJiWE5ESVB5aGU4UnZ4TVNmTUE5VjB4TlAxd1Axb2hCWjZmT082Q0xobCtjb2NabHBvOW14KzEvelBnK1IvOGVyMXJTN2VWQWoxWnIzcHRMTGFqSG5zZTJ0Qld1ZmU4NFNsM3VDWUkrS0NsTjZlb09vTVovN0VYUTBpT2s1ZWNBby9XbjBkTFdoSnVzRUlrNUgvVkRDSnY3K2NGSnJVbmpyY1VSTzd0Q1ZvZTNmQ2VOay9PaHlXU2IzNkJkaVdVZm5LNFA5MnZHYTJIZFV2Sy9EMGJRV0RlYWdhTGdwRWVKSlpldUtMZmoyMGF0bDFCeGVRYllnOVhsazVoRlhqbndYY2xDL0JBeGtwSG1ubFhYMzQrYmJOUFI2ZWhqV1NoVm9HQnNlR3hFbEs3Q3dPZFRFZno1eHpXRzc2eUxYMFRHKys1OVZCNUl2aGlCZjUwTzhTR1NUTE54WTNpU1pjTExoNnVhVFlVK1Rnd291NEx5VGRReWpGTHVxZHhXWWxQSkxpaFFaTnNsVUZrZDhnbVpIQjFGdWE5dnVLTmQydGZiZHAvbTdIOGNvQlhPMFZXeUdKRnNwT1EzYjJlTlY0S3BmVC9WQm40QWFBeE9FelNmcytSWFBocU9xL2c0Mll3aEYzMWRic1dER2RxNmFxWW94ZVJvVEZXeXJPM3JIZ0hwYUszUHdMdVRqbndZaUtNZnpmODRpaHh2Rm5Sei9Tc0oxcWIyL25qdEJibDVDUmozb1dsL015R3M4SmFzSHRFcGErWFpkdG1KMUJXME9UV2FydFpXa0h4WEdBRjRlaVF5UEt5U3g5eWRNN1pIQ0E2cnRIOUF4M2d0UGFhMXRUOEdlOWdWUUVmQUxNS0d2dDgwT3RITElmalk0WGhGOE5BN1pZWXg1djlmNGhLUlZTSjN3NE01NjNVU0V6VTdXNjVhZi9EdDFyTzY3NVVBNlFtQXlCZGRXbkxKWm01byt4M1BXWWxOMnJYbnV4REJuaEFYVVRvemNENU0zajBGWWZPMFYxUGVzdHYxRitJc21JVEpqQ0tBTHNITlMyaGtpdjBYZzU3MU93eXV1RGFVMDl3TnZFT3pXeTl1T2Rac0plVjF0SHVOMmZtRUZ3WkZrSnVKMGhHZCt5aUNIemdYSkIrV29OKzVVcWRnRFllblJZMHhhc0hnMFczMklyUWN5aDJpTysrWGRteHJwYW01S3Q5R1FYU25Sb3BSU2tSZCtrWlNVL2ZhdGhMYVhpVlRWZlBiNGNydzZDU0ErMjdqYnkxY213L0E0UzM5RDJmYjhYM3hpcEFBTklMUkIvaXUwOFVhbm1KSEdzdTJNY3hZeHVsQjBNdXAvMnRLQkszTis4cUwydFMvN1ZCRk5hYmtpQ3BCbDlJL3F3am1henhSWkRxbWJWU0w4bHZYMUFLWXlqVnNNK0ZsNit5TVByMFRQNDcxb0xIem9kUFY1aG82UzlKZmsvK2kvYWFONnZCUUVVdy9FdTA3dlFGVzloRXpMS3VsSEdCQzVMeWlSU1hNbExHUi9Md3UycFpLWHVEb3RmMmJOS2tLd0cweW5JL3dLZE9oUlQwL3RZVDlKd3ZGRkVwV3pneU9sbTZmS3VteG1CQTBMdGViNjU0LytQcFBIVEdjZVFWVFdvZENxY1k0SWU1Tk1FcFNxWmp0Qkc3Uzh2WER3Z1VEakRWbkQ5QmVLeDZjV1lrM3BiS3lEajBXdHAvc0RQTGtmQnk3WDBPU3hjbVpXNHlXYlBLSVdoei90NEI3bUZXZ0srNDZmRVcrbDdYWkl5REl0dHl3ZXJVWk9FbVY0Ky8ybDQ5Y0VvZDE1ME1rN0JMeTdyeGVzeUhJR09MZ0wybGVLUGYrbC9ycW1VMlZRMXc4akNFRmpQM2VKZnpweWErdU1vOGMwdUpwMmdRME50YWVwY3JRQ2ZYN1ZQMHVRcVVoVzIvaXFSZDh4SFhKQmtUSWg1Q2F6LzNOZzltTGhHQlNMSUxwMktVZUlTN093eFBCZ3lBSWd4TDh0SXlhb0hMV29DY3pNcUVqQ0ErWC9UdXdxR3NPQ0ZuTzF2ejBuVVlJT0NPRE9NSVgyd09tU3NnTVQ4QWpGTlBwRG53ZkFKTUhMRTZhVzhxQnBtV0MzYmkySzdORDUrMlNpU0VOazRXbjM2Qm05bGM2enBVZ1FXdFpaU2YwOTIva1pyY1VJNVA4c05uaUlIdGdaMWU1UndjQVhLd0dFa3YrSWpUWWdsZDY0MGZ2RldRL1NITDZUUVZnTzJMa00vakdQUzd2a2VJd0lNTTRPT04xNUUwV0ZMcUpCZkc0ZzV6MWRNMTR2WnR4WlFicE1lQzVrVjFuZDNRTW5yVHpCT0srcXZYZEQvZ1J6QXpRVzJSeTBabjlHSWE2eUpOQ2d4TlVZTWYzN3MzOTgwenhFa05RVHZZaDhUbzQ0OWZuK0ZjeWxnOHE5WSs5M1Q1UXhEYUFRRzhpUzhaZzhBSkRUUmNNTVJGMitLZDBscDI1aEhCb1pwQmNVcDhKakNNMTJ5NkFvaHhzdE9RdjwvbnM0OkNpcGhlclZhbHVlPjwvbnM0OkNpcGhlckRhdGE+PC9uczQ6RW5jcnlwdGVkRGF0YT48bnM0OkVuY3J5cHRlZEtleT48bnM0OkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDA5L3htbGVuYzExI3JzYS1vYWVwIi8+PG5zNDpDaXBoZXJEYXRhPjxuczQ6Q2lwaGVyVmFsdWU+bXYwdEEvbWFXUkJpcDlIVHF5dUUvZ3d4eTBpNVlCeEp4OHpyQVY5dDU5aHJ6czFUMktYTmhHZXNnUU9UTmZ3VEY3R3NVdkhiTWZ2NlJCMmFpWVh5VFZ0TXlPL2NOdjBzYVcyVlQ2TDRJcmdDbGI4YXZmQmg0SDl3Q2Z6WXdXdnRseG1RanFMOURJWjJRemdmZWs2Sjl6end4aXZTMVBld2hNdC8yRTZtYlp4OStzN0R2Y3hyN0k2dWJBcVVoZlA4YUR4MENGak5yTXlmRzRPZ2xnbVJZSDdxL3ZGbFNDRnFHNUZlUExVUGh3QW9zNlVTNm1zTlpRT2hwaC9uV0tzcGxWVlhScGRyMjhUZjdlU0o0K2VKbzF2UFJmd0NUSWx3MDNYaHJ0K29UcWljcU9rOEJ0aFdmSThadE5vc2htUWRXWjZWenVUYXhZblN1THpQMk16QmtBPT08L25zNDpDaXBoZXJWYWx1ZT48L25zNDpDaXBoZXJEYXRhPjwvbnM0OkVuY3J5cHRlZEtleT48L0VuY3J5cHRlZEFzc2VydGlvbj48L25zMzpSZXNwb25zZT4=

0 commit comments

Comments
 (0)