Sign ffdec.jar and ffdec_lib.jar

This commit is contained in:
Jindra Petřík
2026-02-10 06:53:00 +01:00
parent d4463bf511
commit e7df2a581c
9 changed files with 421 additions and 4 deletions

View File

@@ -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

3
.gitignore vendored
View File

@@ -117,4 +117,5 @@ exported1.all.bin
wix/bin/
wix/obj/
*.msi
*.wixpdb
*.wixpdb
/altsigner/target/

View File

@@ -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

70
altsigner/pom.xml Normal file
View File

@@ -0,0 +1,70 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jpexs</groupId>
<artifactId>kms-jarsigner</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<bc.version>1.83</bc.version>
<google.kms.version>2.41.0</google.kms.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>META-INF/SIG-*</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.jpexs.kmsjarsigner.SignJar</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Cloud KMS -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-kms</artifactId>
<version>2.86.0</version>
</dependency>
<!-- Bouncy Castle for CMS/PKCS#7 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.83</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.83</version>
</dependency>
</dependencies>
</project>

View File

@@ -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
}

View File

@@ -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());
}
}

View File

@@ -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; }
}

View File

@@ -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 <in.jar> <out.jar> <cert-chain.pem> <kmsKeyVersion> <tsaUrl>");
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<java.security.cert.Certificate> ordered = new ArrayList<>(certs);
// JarSigner expects a CertPath; order typically leaf->intermediate... works fine.
return cf.generateCertPath(ordered);
}
}

106
cert/cert-chain.pem Normal file
View File

@@ -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-----