diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 9c5bf52bd..2982ac594 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -419,7 +419,7 @@ jobs:
# path: resources/ffdec.exe
sign_and_msi:
- name: Generate MSI, Sign EXE+MSI
+ name: Code signing, MSI installer
runs-on: windows-latest
needs:
- compute-version
@@ -444,13 +444,30 @@ jobs:
with:
name: dist
path: dist/
+
+ - name: Download lib_dist artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: dist_lib
+ path: libsrc/ffdec_lib/dist/
- name: Download unsigned EXE artifact
uses: actions/download-artifact@v4
with:
name: unsigned_exe
path: dist/
-
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: adopt
+ architecture: x64
+ java-version: 23
+
+ - name: Build alt signer
+ working-directory: altsigner
+ run: mvn clean package
+
- id: auth
uses: google-github-actions/auth@v2
with:
@@ -501,6 +518,41 @@ jobs:
Start-Process msiexec.exe -Wait -ArgumentList "/i `"$($msi.FullName)`" /qn /norestart"
+ - name: Sign ffdec.jar
+ shell: pwsh
+ run: |
+ $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION"
+ java -cp altsigner\target\kms-jarsigner-1.0.jar com.jpexs.kmsjarsigner.SignJar dist/ffdec.jar dist/ffdec-signed.jar cert/cert-chain.pem $kc http://timestamp.sectigo.com
+ move dist/ffdec-signed.jar dist/ffdec.jar
+
+ - name: Verify ffdec.jar signature
+ shell: pwsh
+ run: jarsigner.exe -verify -strict dist/ffdec-signed.jar
+
+ - name: Upload signed JAR artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: signed_jar
+ path: dist/ffdec.jar
+
+ - name: Sign ffdec_lib.jar
+ shell: pwsh
+ run: |
+ $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION"
+ java -cp altsigner\target\kms-jarsigner-1.0.jar com.jpexs.kmsjarsigner.SignJar libsrc/ffdec_lib/dist/ffdec_lib.jar libsrc/ffdec_lib/dist/ffdec_lib-signed.jar cert/cert-chain.pem $kc http://timestamp.sectigo.com
+ move libsrc/ffdec_lib/dist/ffdec_lib-signed.jar libsrc/ffdec_lib/dist/ffdec_lib.jar
+
+ - name: Verify ffdec_lib.jar signature
+ shell: pwsh
+ run: jarsigner.exe -verify -strict libsrc/ffdec_lib/dist/ffdec_lib.jar
+
+ - name: Upload signed lib JAR artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: signed_lib_jar
+ path: libsrc/ffdec_lib/dist/ffdec_lib.jar
+
+
- name: Locate signtool
id: signtool
shell: pwsh
@@ -727,11 +779,26 @@ jobs:
name: dist
path: dist/
+ - name: Download signed jar artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: signed_jar
+ path: dist/
+
- name: Download lib dist artifact
uses: actions/download-artifact@v4
with:
name: lib_dist
path: libsrc/ffdec_lib/dist/
+
+ - name: Download signed lib jar artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: signed_lib_jar
+ path: libsrc/ffdec_lib/dist/
+
+ - name: Copy signed ffdec_lib.jar to lib main dir
+ run: cp libsrc/ffdec_lib/dist/ffdec_lib.jar lib/ffdec_lib.jar
- name: Download signed EXE artifact
uses: actions/download-artifact@v4
diff --git a/.gitignore b/.gitignore
index 78ee66db7..79aa4e583 100644
--- a/.gitignore
+++ b/.gitignore
@@ -117,4 +117,5 @@ exported1.all.bin
wix/bin/
wix/obj/
*.msi
-*.wixpdb
\ No newline at end of file
+*.wixpdb
+/altsigner/target/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24eb1eb58..d9914038e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ All notable changes to this project will be documented in this file.
- Slovak translation (AI used)
- APNG (animated PNG) export for frames, sprites and morphshapes
- Context menu association icon
-- Windows installer (MSI) and ffdec.exe are signed
+- Windows installer (MSI), ffdec.exe, ffdec.jar and ffdec_lib.jar are signed
- ffdec.exe contains version information (+ on SplashScreen)
### Fixed
diff --git a/altsigner/pom.xml b/altsigner/pom.xml
new file mode 100644
index 000000000..6d24cdd2c
--- /dev/null
+++ b/altsigner/pom.xml
@@ -0,0 +1,70 @@
+
+ 4.0.0
+
+ com.jpexs
+ kms-jarsigner
+ 1.0
+
+
+ 17
+ 1.83
+ 2.41.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.3
+
+
+ package
+ shade
+
+ false
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+ META-INF/SIG-*
+
+
+
+
+
+ com.jpexs.kmsjarsigner.SignJar
+
+
+
+
+
+
+
+
+
+
+
+
+ com.google.cloud
+ google-cloud-kms
+ 2.86.0
+
+
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ 1.83
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.83
+
+
+
\ No newline at end of file
diff --git a/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsPrivateKey.java b/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsPrivateKey.java
new file mode 100644
index 000000000..3fa7884af
--- /dev/null
+++ b/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsPrivateKey.java
@@ -0,0 +1,19 @@
+package com.jpexs.kmsjarsigner;
+
+import java.security.PrivateKey;
+
+public final class KmsPrivateKey implements PrivateKey {
+ private final String cryptoKeyVersion; // projects/.../cryptoKeyVersions/1
+
+ public KmsPrivateKey(String cryptoKeyVersion) {
+ this.cryptoKeyVersion = cryptoKeyVersion;
+ }
+
+ public String cryptoKeyVersion() {
+ return cryptoKeyVersion;
+ }
+
+ @Override public String getAlgorithm() { return "RSA"; }
+ @Override public String getFormat() { return null; } // not exportable
+ @Override public byte[] getEncoded() { return null; } // not exportable
+}
\ No newline at end of file
diff --git a/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsProvider.java b/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsProvider.java
new file mode 100644
index 000000000..39810b8d9
--- /dev/null
+++ b/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsProvider.java
@@ -0,0 +1,14 @@
+package com.jpexs.kmsjarsigner;
+
+import java.security.Provider;
+
+public final class KmsProvider extends Provider {
+ public static final String NAME = "KMS";
+
+ public KmsProvider() {
+ super(NAME, 1.0, "Google Cloud KMS Signature Provider");
+ // We implement exactly what you need:
+ // RSA 3072 + PKCS#1 v1.5 + SHA-256 = "SHA256withRSA"
+ put("Signature.SHA256withRSA", KmsSha256WithRsaSignatureSpi.class.getName());
+ }
+}
\ No newline at end of file
diff --git a/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsSha256WithRsaSignatureSpi.java b/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsSha256WithRsaSignatureSpi.java
new file mode 100644
index 000000000..16c8e4c71
--- /dev/null
+++ b/altsigner/src/main/java/com/jpexs/kmsjarsigner/KmsSha256WithRsaSignatureSpi.java
@@ -0,0 +1,80 @@
+package com.jpexs.kmsjarsigner;
+
+import com.google.cloud.kms.v1.AsymmetricSignRequest;
+import com.google.cloud.kms.v1.CryptoKeyVersionName;
+import com.google.cloud.kms.v1.Digest;
+import com.google.cloud.kms.v1.KeyManagementServiceClient;
+import com.google.protobuf.ByteString;
+
+import java.io.ByteArrayOutputStream;
+import java.security.*;
+import java.security.spec.AlgorithmParameterSpec;
+
+public final class KmsSha256WithRsaSignatureSpi extends SignatureSpi {
+ private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ private KmsPrivateKey key;
+
+ @Override
+ protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
+ throw new InvalidKeyException("Verify not implemented in this provider (JarSigner does not need it).");
+ }
+
+ @Override
+ protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
+ if (!(privateKey instanceof KmsPrivateKey)) {
+ throw new InvalidKeyException("Expected KmsPrivateKey, got: " + privateKey.getClass());
+ }
+ this.key = (KmsPrivateKey) privateKey;
+ buf.reset();
+ }
+
+ @Override protected void engineUpdate(byte b) { buf.write(b); }
+
+ @Override
+ protected void engineUpdate(byte[] b, int off, int len) {
+ buf.write(b, off, len);
+ }
+
+ @Override
+ protected byte[] engineSign() throws SignatureException {
+ if (key == null) throw new SignatureException("Not initialized for signing");
+
+ try {
+ byte[] data = buf.toByteArray();
+ byte[] digestBytes = MessageDigest.getInstance("SHA-256").digest(data);
+
+ CryptoKeyVersionName kv = CryptoKeyVersionName.parse(key.cryptoKeyVersion());
+ Digest digest = Digest.newBuilder().setSha256(ByteString.copyFrom(digestBytes)).build();
+
+ AsymmetricSignRequest req = AsymmetricSignRequest.newBuilder()
+ .setName(kv.toString())
+ .setDigest(digest)
+ .build();
+
+ try (KeyManagementServiceClient kms = KeyManagementServiceClient.create()) {
+ return kms.asymmetricSign(req).getSignature().toByteArray();
+ }
+ } catch (Exception e) {
+ throw new SignatureException("KMS AsymmetricSign failed: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
+ throw new SignatureException("Verify not implemented");
+ }
+
+ @Override
+ protected void engineSetParameter(String param, Object value) { /* ignored */ }
+
+ @Override
+ protected Object engineGetParameter(String param) { return null; }
+
+ @Override
+ protected void engineSetParameter(AlgorithmParameterSpec params) {
+ // For SHA256withRSA (PKCS#1 v1.5) no params expected
+ }
+
+ @Override
+ protected AlgorithmParameters engineGetParameters() { return null; }
+}
\ No newline at end of file
diff --git a/altsigner/src/main/java/com/jpexs/kmsjarsigner/SignJar.java b/altsigner/src/main/java/com/jpexs/kmsjarsigner/SignJar.java
new file mode 100644
index 000000000..2f53c665b
--- /dev/null
+++ b/altsigner/src/main/java/com/jpexs/kmsjarsigner/SignJar.java
@@ -0,0 +1,60 @@
+package com.jpexs.kmsjarsigner;
+
+import jdk.security.jarsigner.JarSigner;
+
+import java.io.*;
+import java.net.URI;
+import java.nio.file.*;
+import java.security.*;
+import java.security.cert.*;
+import java.util.*;
+import java.util.zip.ZipFile;
+
+public final class SignJar {
+
+ // Usage:
+ // java -jar target/kms-jar-signer-1.0.0.jar input.jar output.jar chain.pem KMS_KEY_VERSION TSA_URL
+ //
+ // chain.pem: PEM with leaf + intermediate(s). Root optional (usually omit).
+ public static void main(String[] args) throws Exception {
+ if (args.length < 5) {
+ System.err.println("Usage: SignJar ");
+ System.exit(2);
+ }
+ Path inJar = Path.of(args[0]);
+ Path outJar = Path.of(args[1]);
+ Path chainPath = Path.of(args[2]);
+ String kmsKeyVersion = args[3];
+ URI tsa = URI.create(args[4]);
+
+ Provider kmsProvider = new KmsProvider();
+ Security.addProvider(kmsProvider);
+
+ PrivateKey kmsKey = new KmsPrivateKey(kmsKeyVersion);
+ CertPath certPath = loadPemCertPath(chainPath);
+
+ JarSigner signer = new JarSigner.Builder(kmsKey, certPath)
+ .digestAlgorithm("SHA-256")
+ .signatureAlgorithm("SHA256withRSA", kmsProvider)
+ .tsa(tsa) // timestamp via RFC3161 TSA
+ .build();
+
+ try (ZipFile zip = new ZipFile(inJar.toFile());
+ OutputStream os = Files.newOutputStream(outJar, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ signer.sign(zip, os);
+ }
+
+ System.out.println("Signed: " + outJar);
+ }
+
+ private static CertPath loadPemCertPath(Path pemPath) throws IOException, CertificateException {
+ byte[] pem = Files.readAllBytes(pemPath);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Collection extends java.security.cert.Certificate> certs = cf.generateCertificates(new ByteArrayInputStream(pem));
+ if (certs.isEmpty()) throw new CertificateException("No certificates found in " + pemPath);
+
+ List ordered = new ArrayList<>(certs);
+ // JarSigner expects a CertPath; order typically leaf->intermediate... works fine.
+ return cf.generateCertPath(ordered);
+ }
+}
\ No newline at end of file
diff --git a/cert/cert-chain.pem b/cert/cert-chain.pem
new file mode 100644
index 000000000..0d2d53bba
--- /dev/null
+++ b/cert/cert-chain.pem
@@ -0,0 +1,106 @@
+-----BEGIN CERTIFICATE-----
+MIIFyDCCBDCgAwIBAgIQHPG2s4v6Su+MOOTrbq79yDANBgkqhkiG9w0BAQwFADBU
+MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQD
+EyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0EgUjM2MB4XDTI2MDIwOTAw
+MDAwMFoXDTI5MDIwMzIzNTk1OVowXzELMAkGA1UEBhMCQ1oxHDAaBgNVBAgME1N0
+xZllZG/EjWVza8O9IGtyYWoxGDAWBgNVBAoMD0ppbmRyaWNoIFBldHJpazEYMBYG
+A1UEAwwPSmluZHJpY2ggUGV0cmlrMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
+igKCAYEAi4YPqddfVVpqhRCnxrSgg4dIHTlefcwjIEZXUooNbEJIAHb28OBbRfrU
+lILsET2x2eOUMCqiM22NfMFzA5KiOMQzffD+pxJS0M/6tVXpoI+3q94sybIV1OS+
+QiRw/+FtPsLwAsrzykbCOYj2YpgqrUfRqMMPcBpPN4kUf92CGfQGq9IqbAKtQkW+
+a9PTBvSo0MOytx5Vp7T5oe/0am/rxr3nwJPHbsTq9Efw33x/g/dzQn7aR1vGEdrL
+zeG0gFFdPDYTWQiSwkiSaoH3exltc4zkc4ynvfsoFFiazKafUUYSvsx7x7GZUAO1
+i1MIiYkzK5DaYOqGIN/zFO9Wc38qi9qkH4vrdYmKHqZXxNSFN4x1LT0wDrbzFWef
+dayrQT5ReR9iOFpoTwSCpktZXcqndw9P0eyTtIhB8AC9/peXYJZrFOuf1o5fSYM0
+7WaMTaaeQjS8zIH0K7Jyp8N0jzlNITH1MfkWh2CXblf4jvGvtiab8TrLTCOJzT0O
+lnxCBeIDAgMBAAGjggGJMIIBhTAfBgNVHSMEGDAWgBQPKssghyi47G9IritUpimq
+F6TNDDAdBgNVHQ4EFgQUEr4x6TulT8qxylRFTFjogiP/fOcwDgYDVR0PAQH/BAQD
+AgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwSgYDVR0gBEMw
+QTA1BgwrBgEEAbIxAQIBAwIwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdv
+LmNvbS9DUFMwCAYGZ4EMAQQBMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwu
+c2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3JsMHkG
+CCsGAQUFBwEBBG0wazBEBggrBgEFBQcwAoY4aHR0cDovL2NydC5zZWN0aWdvLmNv
+bS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIzNi5jcnQwIwYIKwYBBQUHMAGG
+F2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqGSIb3DQEBDAUAA4IBgQBOkf7v
+1vCt7of0tqW0tTnUm9r26fD8xg5wasGAfQRZcgcbdxuCoEsXz1wFC/YHkU6JvVIy
+mRW2zlTbr5DI1MSMDh97WSpwDHeHVtMdZFZOzhzoYNT6wrMAvh1FY8gdZiA11dnY
+YDVQev21N0Ym277Y6lnqu6bxi6KsDfwMvSCFWzUJYTAn6YDIcrH+C0Zh7FNfn4nH
+qlrXjezXRM2VGf7cD5l3WvEtT1PKhFK9SFCbM9f6B2MkQVfF5d+kyg9EL7x3M+ku
+Nt/pPzcSIKwJe5J/rN0cBiT9TNi/KCo8igyiudeRIWbkU3RF8bNM5ociEGhpNvEi
+LZEU2KqpN3uSdN8KxKVVdbJmyYARnctNxOPq7S6WsTGOSu2QffSfB+wT8bkT6ovd
+azOa34IXcY6haH11yK0oTHUvIqjCnwDJ1100Rw2vWuMOMxYOi12H3nbQsD7IEipa
+bZsfWJyzui5xNDSrlrq7tXo3CgFBn3nRYusQ7B/sPFFx7zMsO+VEBOrSiQo=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGGjCCBAKgAwIBAgIQYh1tDFIBnjuQeRUgiSEcCjANBgkqhkiG9w0BAQwFADBW
+MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQD
+EyRTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwHhcNMjEwMzIy
+MDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMP
+U2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNp
+Z25pbmcgQ0EgUjM2MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAmyud
+U/o1P45gBkNqwM/1f/bIU1MYyM7TbH78WAeVF3llMwsRHgBGRmxDeEDIArCS2VCo
+Vk4Y/8j6stIkmYV5Gej4NgNjVQ4BYoDjGMwdjioXan1hlaGFt4Wk9vT0k2oWJMJj
+L9G//N523hAm4jF4UjrW2pvv9+hdPX8tbbAfI3v0VdJiJPFy/7XwiunD7mBxNtec
+M6ytIdUlh08T2z7mJEXZD9OWcJkZk5wDuf2q52PN43jc4T9OkoXZ0arWZVeffvMr
+/iiIROSCzKoDmWABDRzV/UiQ5vqsaeFaqQdzFf4ed8peNWh1OaZXnYvZQgWx/SXi
+JDRSAolRzZEZquE6cbcH747FHncs/Kzcn0Ccv2jrOW+LPmnOyB+tAfiWu01TPhCr
+9VrkxsHC5qFNxaThTG5j4/Kc+ODD2dX/fmBECELcvzUHf9shoFvrn35XGf2RPaNT
+O2uSZ6n9otv7jElspkfK9qEATHZcodp+R4q2OIypxR//YEb3fkDn3UayWW9bAgMB
+AAGjggFkMIIBYDAfBgNVHSMEGDAWgBQy65Ka/zWWSC8oQEJwIDaRXBeF5jAdBgNV
+HQ4EFgQUDyrLIIcouOxvSK4rVKYpqhekzQwwDgYDVR0PAQH/BAQDAgGGMBIGA1Ud
+EwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwGwYDVR0gBBQwEjAG
+BgRVHSAAMAgGBmeBDAEEATBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsLnNl
+Y3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RSNDYuY3JsMHsG
+CCsGAQUFBwEBBG8wbTBGBggrBgEFBQcwAoY6aHR0cDovL2NydC5zZWN0aWdvLmNv
+bS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdSb290UjQ2LnA3YzAjBggrBgEFBQcw
+AYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggIBAAb/
+guF3YzZue6EVIJsT/wT+mHVEYcNWlXHRkT+FoetAQLHI1uBy/YXKZDk8+Y1LoNqH
+rp22AKMGxQtgCivnDHFyAQ9GXTmlk7MjcgQbDCx6mn7yIawsppWkvfPkKaAQsiqa
+T9DnMWBHVNIabGqgQSGTrQWo43MOfsPynhbz2Hyxf5XWKZpRvr3dMapandPfYgoZ
+8iDL2OR3sYztgJrbG6VZ9DoTXFm1g0Rf97Aaen1l4c+w3DC+IkwFkvjFV3jS49ZS
+c4lShKK6BrPTJYs4NG1DGzmpToTnwoqZ8fAmi2XlZnuchC4NPSZaPATHvNIzt+z1
+PHo35D/f7j2pO1S8BCysQDHCbM5Mnomnq5aYcKCsdbh0czchOm8bkinLrYrKpii+
+Tk7pwL7TjRKLXkomm5D1Umds++pip8wH2cQpf93at3VDcOK4N7EwoIJB0kak6pSz
+Eu4I64U6gZs7tS/dGNSljf2OSSnRr7KWzq03zl8l75jy+hOds9TWSenLbjBQUGR9
+6cFr6lEUfAIEHVC1L68Y1GGxx4/eRI82ut83axHMViw1+sVpbPxg51Tbnio1lB93
+079WPFnYaOvfGAA0e0zcfF/M9gXr+korwQTh2Prqooq2bYNMvUoUKD85gnJ+t0sm
+rWrb8dee2CvYZXD5laGtaAxOfy/VKNmwuWuAh9kc
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGgTCCBGmgAwIBAgIQAnw5AQynWsM6te4NVA755TANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjEw
+MzIyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBWMQswCQYDVQQGEwJHQjEYMBYGA1UE
+ChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdvIFB1YmxpYyBDb2Rl
+IFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+shJHjUoq14pbe0Idj
+JImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCDJ9qaDStQ6Utbs7hk
+NqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7P2bSlDFp+m2zNKzB
+enjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extmeme/G3h+pDHazJyCh
+1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUzT2MuuC3hv2WnBGsY
+2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6qRT5uWl+PoVvLnTCG
+MOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mczmrYI4IAFSEDu9oJk
+Rqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEcQNYWFyn8XJwYK+pF
+9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2TOglmmVhcKaO5DKYw
+ODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/AZwQsRb8zG4Y3G9i
+/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QIDAQABo4IBFjCCARIw
+HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFDLrkpr/
+NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
+MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNVHR8E
+STBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNB
+Q2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwNQYIKwYBBQUHAQEEKTAnMCUGCCsG
+AQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUA
+A4ICAQBfHYHOUvth+43SceEtI4YqgnlKuPoaE+1ucOskvqnva/yLCD4BZT4dYLEt
+04YG3m+hef+ZXdEKeFscAZpe4EesqsPB+X7IfHhTjS2yO15VwFtGdB56WX7HgFyL
+MmaEgJ9NKjFVaOFZb4mIStcdamlS5iDbFXFUGGtIlIdtgy+nhdpPXR4TL+z16QY4
+PHD7+aZ5J6/z8uD9wpnzI1jF7eF+7I/ekvCCiLw5vFYVcqvlOViI9VZmnYtDg1HA
+dTCOqPbPhVqzS+KRftx8+VGmJCTpVTxOmkW7uXbdDDOSG572ZPDWUU4lcHcwnfaR
+1zKob5u4uvbgigqe+pp+bmiW628Wqx1775G9LqiW26foBCmeHLq7AYlrt33KAW0/
+oocWV8FF0/BSRY5kiq9IHh/CTt+tAjXjAwy0RLtsXyfvEjiKzaQW8W2QU1tlLJVX
+VmLmfNxGlJLG65RvdR9cpZE10B8KWleHm6KfNWfcYmdTFbg1TpV8Bh9FhJcXxOjb
+rZpQOTaab9gTxyqOzOeD3mqUmHjb++lg6k9gyp2qEOaqY+mfJ1/wc4intu3qCRFR
+iEQF5mjhrovhe0S2NYgwjDWjlctIO1wZ13Cwq5xjy0W7tiy3kHigxZBF0MuqHkrt
+E1k3jTbYZdt6mifshQ0uiP/7C1Up/gZMhGvcAfKxxdnE05oTJA==
+-----END CERTIFICATE-----
+