diff --git a/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java b/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java
index 8235b84..3ab084e 100644
--- a/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java
+++ b/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java
@@ -1,9 +1,11 @@
package com.jetbrains.help;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.IoUtil;
import cn.hutool.core.net.Ipv4Util;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.jetbrains.help.context.*;
import lombok.SneakyThrows;
@@ -17,6 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.net.InetAddress;
+import java.security.cert.Certificate;
import java.util.Collection;
import java.util.List;
@@ -37,7 +40,6 @@ public class JetbrainsHelpApplication {
PluginsContextHolder.init();
CertificateContextHolder.init();
AgentContextHolder.init();
-
InetAddress localHost = InetAddress.getLocalHost();
String address = CharSequenceUtil.format("http://{}:{}", localHost.getHostAddress(), SpringUtil.getProperty("server.port"));
String runSuccessWarn = "\n====================================================================================\n" +
diff --git a/src/main/java/com/jetbrains/help/context/AgentContextHolder.java b/src/main/java/com/jetbrains/help/context/AgentContextHolder.java
index 166f303..06ac891 100644
--- a/src/main/java/com/jetbrains/help/context/AgentContextHolder.java
+++ b/src/main/java/com/jetbrains/help/context/AgentContextHolder.java
@@ -35,7 +35,7 @@ public class AgentContextHolder {
public static void init() {
log.info("Agent context init loading...");
jaNetfilterZipFile = FileTools.getFileOrCreat(JA_NETFILTER_FILE_PATH + ".zip");
- if (!FileTools.fileExists(JA_NETFILTER_FILE_PATH)) {
+ if (FileTools.fileNotExists(JA_NETFILTER_FILE_PATH)) {
unzipJaNetfilter();
if (!powerConfHasInit()) {
log.info("Agent config init loading...");
@@ -75,14 +75,27 @@ public class AgentContextHolder {
@SneakyThrows
private static String generatePowerConfigRule() {
- X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.crtFile()));
- RSAPublicKey publicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile()));
- RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.rootKeyFile()));
+ StringBuilder result = new StringBuilder();
+ X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getCrtFile()));
+ X509Certificate licenseCrt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getLicenseCrtFile()));
+ RSAPublicKey publicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getPublicKeyFile()));
+ RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getRootKeyFile()));
+ RSAPublicKey rootLicensePublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getRootLicenseKeyFile()));
BigInteger x = new BigInteger(1, crt.getSignature());
- BigInteger y = BigInteger.valueOf(65537L);
+ BigInteger y = rootPublicKey.getPublicExponent();
BigInteger z = rootPublicKey.getModulus();
BigInteger r = x.modPow(publicKey.getPublicExponent(), publicKey.getModulus());
- return CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r);
+ result.append("; Activation Code").append("\n")
+ .append(CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r))
+ .append("\n");
+ x = new BigInteger(1, licenseCrt.getSignature());
+ z = rootLicensePublicKey.getModulus();
+ y = rootLicensePublicKey.getPublicExponent();
+ r = x.modPow(publicKey.getPublicExponent(), publicKey.getModulus());
+ result.append("; License Server").append("\n")
+ .append(CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r))
+ .append("\n");
+ return result.toString();
}
private static String generatePowerConfigStr(String ruleValue) {
diff --git a/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java b/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java
index 154c24c..1f77a52 100644
--- a/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java
+++ b/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java
@@ -3,10 +3,12 @@ package com.jetbrains.help.context;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.PemUtil;
import cn.hutool.crypto.SecureUtil;
import com.jetbrains.help.util.FileTools;
import lombok.AccessLevel;
+import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.x500.X500Name;
@@ -27,29 +29,47 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
+/**
+ * 源证书来源
+ */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CertificateContextHolder {
private static final String ROOT_KEY_FILE_NAME = "external/certificate/root.key";
+ private static final String ROOT_LICENSE_KEY_FILE_NAME = "external/certificate/license-root.key";
private static final String PRIVATE_KEY_FILE_NAME = "external/certificate/private.key";
private static final String PUBLIC_KEY_FILE_NAME = "external/certificate/public.key";
private static final String CET_FILE_NAME = "external/certificate/ca.crt";
+ private static final String LICENSE_CET_FILE_NAME = "external/certificate/license-ca.crt";
+ public static final String SERVER_UID = "cikaros.top";
+ @Getter
private static File rootKeyFile;
+ @Getter
+ private static File rootLicenseKeyFile;
+
+ @Getter
private static File privateKeyFile;
+ @Getter
private static File publicKeyFile;
+ @Getter
private static File crtFile;
+ @Getter
+ private static File licenseCrtFile;
+
public static void init() {
log.info("certificate context init loading...");
rootKeyFile = FileTools.getFileOrCreat(ROOT_KEY_FILE_NAME);
- if (!FileTools.fileExists(PRIVATE_KEY_FILE_NAME)
- || !FileTools.fileExists(PUBLIC_KEY_FILE_NAME)
- || !FileTools.fileExists(CET_FILE_NAME)) {
+ rootLicenseKeyFile = FileTools.getFileOrCreat(ROOT_LICENSE_KEY_FILE_NAME);
+ if (FileTools.fileNotExists(PRIVATE_KEY_FILE_NAME)
+ || FileTools.fileNotExists(PUBLIC_KEY_FILE_NAME)
+ || FileTools.fileNotExists(CET_FILE_NAME)
+ || FileTools.fileNotExists(LICENSE_CET_FILE_NAME)) {
log.info("certificate context generate loading...");
generateCertificate();
log.info("certificate context generate success!");
@@ -57,27 +77,11 @@ public class CertificateContextHolder {
privateKeyFile = FileTools.getFileOrCreat(PRIVATE_KEY_FILE_NAME);
publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME);
crtFile = FileTools.getFileOrCreat(CET_FILE_NAME);
+ licenseCrtFile = FileTools.getFileOrCreat(LICENSE_CET_FILE_NAME);
}
log.info("certificate context init success !");
}
-
- public static File rootKeyFile() {
- return CertificateContextHolder.rootKeyFile;
- }
-
- public static File privateKeyFile() {
- return CertificateContextHolder.privateKeyFile;
- }
-
- public static File publicKeyFile() {
- return CertificateContextHolder.publicKeyFile;
- }
-
- public static File crtFile() {
- return CertificateContextHolder.crtFile;
- }
-
public static void generateCertificate() {
KeyPair keyPair = SecureUtil.generateKeyPair("RSA", 4096);
PrivateKey privateKey = keyPair.getPrivate();
@@ -86,16 +90,8 @@ public class CertificateContextHolder {
PemUtil.writePemObject("PRIVATE KEY", privateKey.getEncoded(), FileUtil.getWriter(privateKeyFile, StandardCharsets.UTF_8, false));
publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME);
PemUtil.writePemObject("PUBLIC KEY", publicKey.getEncoded(), FileUtil.getWriter(publicKeyFile, StandardCharsets.UTF_8, false));
- JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
- new X500Name("CN=JetProfile CA"),
- BigInteger.valueOf(System.currentTimeMillis()),
- DateUtil.yesterday(),
- DateUtil.date().offset(DateField.YEAR, 100),
- new X500Name("CN=Jetbrains-Help"),
- SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
try {
- ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
- Certificate certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateBuilder.build(signer));
+ Certificate certificate = generateCertificate(keyPair,"JetProfile CA","Jetbrains-Help");
crtFile = FileTools.getFileOrCreat(CET_FILE_NAME);
PemUtil.writePemObject("CERTIFICATE", certificate.getEncoded(), FileUtil.getWriter(crtFile, StandardCharsets.UTF_8, false));
} catch (OperatorCreationException e) {
@@ -105,7 +101,33 @@ public class CertificateContextHolder {
} catch (CertificateException e) {
throw new IllegalArgumentException("The certificate read exception", e);
}
+ //TODO 创建License Servers CA
+ try {
+ Certificate certificate = generateCertificate(keyPair,"License Servers CA",String.format("%s.lsrv.jetbrains.com", SERVER_UID));
+ licenseCrtFile = FileTools.getFileOrCreat(LICENSE_CET_FILE_NAME);
+ PemUtil.writePemObject("CERTIFICATE", certificate.getEncoded(), FileUtil.getWriter(licenseCrtFile, StandardCharsets.UTF_8, false));
+ } catch (OperatorCreationException e) {
+ throw new IllegalArgumentException("Certificate operator creation exception", e);
+ } catch (CertificateEncodingException e) {
+ throw new IllegalArgumentException("The certificate encoding exception", e);
+ } catch (CertificateException e) {
+ throw new IllegalArgumentException("The certificate read exception", e);
+ }
}
+ private static Certificate generateCertificate(KeyPair keyPair, String issuer, String subject) throws OperatorCreationException, CertificateException {
+ PrivateKey privateKey = keyPair.getPrivate();
+ PublicKey publicKey = keyPair.getPublic();
+ JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
+ new X500Name(String.format("CN=%s", issuer)),
+ BigInteger.valueOf(System.currentTimeMillis()),
+ DateUtil.yesterday(),
+ DateUtil.date().offset(DateField.YEAR, 100),
+ new X500Name(String.format("CN=%s", subject)),
+ SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
+ return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateBuilder.build(signer));
+
+ }
}
diff --git a/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java b/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java
index c1c69e2..defae9e 100644
--- a/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java
+++ b/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java
@@ -1,20 +1,24 @@
package com.jetbrains.help.context;
import cn.hutool.core.codec.Base64;
-import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.XmlUtil;
import cn.hutool.crypto.PemUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SignUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.json.JSONUtil;
import lombok.AccessLevel;
+import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
@@ -22,7 +26,7 @@ import java.security.cert.CertificateEncodingException;
import java.util.List;
import java.util.Set;
-import static cn.hutool.crypto.asymmetric.SignAlgorithm.SHA1withRSA;
+import static cn.hutool.crypto.asymmetric.SignAlgorithm.*;
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -43,9 +47,9 @@ public class LicenseContextHolder {
.setProducts(products);
String licensePartJson = JSONUtil.toJsonStr(licensePart);
String licensePartBase64 = Base64.encode(licensePartJson);
- PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile()));
- PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile()));
- Certificate certificate = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.crtFile()));
+ PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.getPrivateKeyFile()));
+ PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getPublicKeyFile()));
+ Certificate certificate = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getCrtFile()));
Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded());
String signatureBase64 = Base64.encode(sign.sign(licensePartJson));
String certBase64;
@@ -74,4 +78,147 @@ public class LicenseContextHolder {
private String paidUpTo;
}
+ public static String obtainTicket(ObtainTicketRequest request) {
+ PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.getPrivateKeyFile()));
+ PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getPublicKeyFile()));
+ Certificate cert = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getCrtFile()));
+ Certificate licenseCert = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getLicenseCrtFile()));
+ ObtainTicketResponse response = obtainTicket(privateKey, publicKey, cert, licenseCert, request);
+ Document document = XmlUtil.beanToXml(response);
+ String result = XmlUtil.toStr(document, "UTF-8", true, true);
+ //!important 必须去除XML中所有的换行和空格,即紧凑模式
+ result = result.replaceAll("[\n ]", "");
+ Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded());
+ String sign64 = Base64.encode(sign.sign(result));
+ String licenseCertBase64;
+ try {
+ licenseCertBase64 = Base64.encode(licenseCert.getEncoded());
+ } catch (CertificateEncodingException e) {
+ throw new IllegalArgumentException("Certificate extraction failed", e);
+ }
+ //
+ String signRes = String.format("", sign64, licenseCertBase64);
+ return signRes + "\n" + result;
+ }
+
+ public static ObtainTicketResponse obtainTicket(PrivateKey privateKey, PublicKey publicKey, Certificate cert, Certificate licenseCert, ObtainTicketRequest request) {
+ String certBase64;
+ String licenseCertBase64;
+ try {
+ certBase64 = Base64.encode(cert.getEncoded());
+ licenseCertBase64 = Base64.encode(licenseCert.getEncoded());
+ } catch (CertificateEncodingException e) {
+ throw new IllegalArgumentException("Certificate extraction failed", e);
+ }
+
+ long time = System.currentTimeMillis();
+ long leaseTime = time + (1000L * 60 * 60 * 24 * 365);
+ //{失效时间戳}:{serverUid}
+ String serverLease = String.format("%d:%s", leaseTime, CertificateContextHolder.SERVER_UID);
+
+ Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded());
+ String signatureBase64 = Base64.encode(sign.sign(String.format("%d:%s", time, request.getMachineId())));
+ //{时间戳}:{客户端提交的machineId}:SHA1withRSA:{对({时间戳}:{客户端提交的machineId})的签名}:{license Server CA}
+ String confirmationStamp = String.format("%d:%s:SHA1withRSA:%s:%s",
+ time, request.getMachineId(), signatureBase64, licenseCertBase64);
+
+ sign = SignUtil.sign(SHA512withRSA, privateKey.getEncoded(), publicKey.getEncoded());
+ byte[] bytes = sign.sign(serverLease);
+ signatureBase64 = Base64.encode(bytes);
+ //SHA512withRSA-{对「serverLease」的值进行签名}-{JetProfile CA}
+ String leaseSignature = String.format("SHA512withRSA-%s-%s", signatureBase64, certBase64);
+// String leaseSignature = new BigInteger(1,bytes).toString();
+ return ObtainTicketResponse.builder()
+ .action(Action.NONE)
+ .responseCode(ResponseCode.OK)
+ .message(ResponseCode.OK.name())
+ .ticketId("20")
+ .ticketProperties(String.format("licensee=%s\tlicenseType=4\tmetadata=0120230914PSAX000005", request.getUserName()))
+ .validationPeriod(71829000L)
+ .validationDeadlinePeriod(-1)
+ .prolongationPeriod(63462000L)//有效期
+ .salt(request.getSalt())
+ .serverUid(CertificateContextHolder.SERVER_UID)
+ .serverLease(serverLease)
+ .confirmationStamp(confirmationStamp)
+ .leaseSignature(leaseSignature)
+ .build();
+ }
+
+ public enum Action {
+ NONE,
+ }
+
+ public enum ResponseCode {
+ OK
+ }
+
+ /**
+ * 获取认证票据
+ *
+ * @author Cikaros
+ * @date 2023/11/1
+ */
+ @Data
+ @Builder
+ public static class ObtainTicketResponse {
+ @Builder.Default
+ private Action action = Action.NONE;
+ private String confirmationStamp;
+ private String leaseSignature;
+ @Builder.Default
+ private String message = "";
+ @Builder.Default
+ private Long prolongationPeriod = 1800000L;
+ @Builder.Default
+ private ResponseCode responseCode = ResponseCode.OK;
+ private String salt;
+ private String serverLease;
+ private String serverUid;
+ @Builder.Default
+ private String ticketId = "1";
+ private String ticketProperties;
+ @Builder.Default
+ private Integer validationDeadlinePeriod = -1;
+ @Builder.Default
+ private Long validationPeriod = 1800000L;
+
+ }
+
+ /**
+ * @author Cikaros
+ * @date 2023/10/20
+ */
+ @Data
+ public static class ObtainTicketRequest {
+
+ private String ideProductCode;
+
+ private Boolean empty;
+
+ private String buildDate;
+
+ private String buildNumber;
+
+ private Integer clientVersion;
+
+ private String hostName;
+
+ private String machineId;
+
+ private String productCode;
+
+ private String productFamilyId;
+
+ private String salt;
+
+ private Boolean secure;
+
+ private String userName;
+
+ private String version;
+
+ private Long versionNumber;
+
+ }
}
diff --git a/src/main/java/com/jetbrains/help/controller/ObtainTicketApiController.java b/src/main/java/com/jetbrains/help/controller/ObtainTicketApiController.java
new file mode 100644
index 0000000..d553583
--- /dev/null
+++ b/src/main/java/com/jetbrains/help/controller/ObtainTicketApiController.java
@@ -0,0 +1,22 @@
+package com.jetbrains.help.controller;
+
+import com.jetbrains.help.context.LicenseContextHolder;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping("/rpc")
+public class ObtainTicketApiController {
+
+ @GetMapping(value = "/obtainTicket.action", produces = {"application/xml;charset=utf-8"})
+ @ResponseBody
+ public ResponseEntity obtainTicket(LicenseContextHolder.ObtainTicketRequest request) {
+ return ResponseEntity.ok()
+ .contentType(MediaType.APPLICATION_XML)
+ .body(LicenseContextHolder.obtainTicket(request));
+ }
+}
diff --git a/src/main/java/com/jetbrains/help/util/FileTools.java b/src/main/java/com/jetbrains/help/util/FileTools.java
index 5b78a16..8d7db4a 100644
--- a/src/main/java/com/jetbrains/help/util/FileTools.java
+++ b/src/main/java/com/jetbrains/help/util/FileTools.java
@@ -3,12 +3,10 @@ package com.jetbrains.help.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
-import java.io.IOException;
public interface FileTools {
@@ -19,6 +17,10 @@ public interface FileTools {
return getFile(path).exists();
}
+ static boolean fileNotExists(String path) {
+ return !fileExists(path);
+ }
+
static File getFile(String path) {
File homeDir = application.getDir();
File source = application.getSource();
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index d2b4e74..4db737b 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -6,6 +6,6 @@ spring:
server:
port: 10768
help:
- default-license-name: 光云
- default-assignee-name: 藏柏
- default-expiry-date: 2111-11-11
\ No newline at end of file
+ default-license-name: cikaros.top
+ default-assignee-name: Cikaros
+ default-expiry-date: 2099-12-31
\ No newline at end of file
diff --git a/src/main/resources/external/certificate/license-root.key b/src/main/resources/external/certificate/license-root.key
new file mode 100644
index 0000000..56ad02a
--- /dev/null
+++ b/src/main/resources/external/certificate/license-root.key
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFTDCCAzSgAwIBAgIJAMCrW9HV+hjZMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNV
+BAMMEkxpY2Vuc2UgU2VydmVycyBDQTAgFw0xNjEwMTIxNDMwNTRaGA8yMTE2MTIy
+NzE0MzA1NFowHTEbMBkGA1UEAwwSTGljZW5zZSBTZXJ2ZXJzIENBMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoT7LvHj3JKK2pgc5f02z+xEiJDcvlBi6
+fIwrg/504UaMx3xWXAE5CEPelFty+QPRJnTNnSxqKQQmg2s/5tMJpL9lzGwXaV7a
+rrcsEDbzV4el5mIXUnk77Bm/QVv48s63iQqUjVmvjQt9SWG2J7+h6X3ICRvF1sQB
+yeat/cO7tkpz1aXXbvbAws7/3dXLTgAZTAmBXWNEZHVUTcwSg2IziYxL8HRFOH0+
+GMBhHqa0ySmF1UTnTV4atIXrvjpABsoUvGxw+qOO2qnwe6ENEFWFz1a7pryVOHXg
+P+4JyPkI1hdAhAqT2kOKbTHvlXDMUaxAPlriOVw+vaIjIVlNHpBGhqTj1aqfJpLj
+qfDFcuqQSI4O1W5tVPRNFrjr74nDwLDZnOF+oSy4E1/WhL85FfP3IeQAIHdswNMJ
+y+RdkPZCfXzSUhBKRtiM+yjpIn5RBY+8z+9yeGocoxPf7l0or3YF4GUpud202zgy
+Y3sJqEsZksB750M0hx+vMMC9GD5nkzm9BykJS25hZOSsRNhX9InPWYYIi6mFm8QA
+2Dnv8wxAwt2tDNgqa0v/N8OxHglPcK/VO9kXrUBtwCIfZigO//N3hqzfRNbTv/ZO
+k9lArqGtcu1hSa78U4fuu7lIHi+u5rgXbB6HMVT3g5GQ1L9xxT1xad76k2EGEi3F
+9B+tSrvru70CAwEAAaOBjDCBiTAdBgNVHQ4EFgQUpsRiEz+uvh6TsQqurtwXMd4J
+8VEwTQYDVR0jBEYwRIAUpsRiEz+uvh6TsQqurtwXMd4J8VGhIaQfMB0xGzAZBgNV
+BAMMEkxpY2Vuc2UgU2VydmVycyBDQYIJAMCrW9HV+hjZMAwGA1UdEwQFMAMBAf8w
+CwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQCJ9+GQWvBS3zsgPB+1PCVc
+oG6FY87N6nb3ZgNTHrUMNYdo7FDeol2DSB4wh/6rsP9Z4FqVlpGkckB+QHCvqU+d
+rYPe6QWHIb1kE8ftTnwapj/ZaBtF80NWUfYBER/9c6To5moW63O7q6cmKgaGk6zv
+St2IhwNdTX0Q5cib9ytE4XROeVwPUn6RdU/+AVqSOspSMc1WQxkPVGRF7HPCoGhd
+vqebbYhpahiMWfClEuv1I37gJaRtsoNpx3f/jleoC/vDvXjAznfO497YTf/GgSM2
+LCnVtpPQQ2vQbOfTjaBYO2MpibQlYpbkbjkd5ZcO5U5PGrQpPFrWcylz7eUC3c05
+UVeygGIthsA/0hMCioYz4UjWTgi9NQLbhVkfmVQ5lCVxTotyBzoubh3FBz+wq2Qt
+iElsBrCMR7UwmIu79UYzmLGt3/gBdHxaImrT9SQ8uqzP5eit54LlGbvGekVdAL5l
+DFwPcSB1IKauXZvi1DwFGPeemcSAndy+Uoqw5XGRqE6jBxS7XVI7/4BSMDDRBz1u
+a+JMGZXS8yyYT+7HdsybfsZLvkVmc9zVSDI7/MjVPdk6h0sLn+vuPC1bIi5edoNy
+PdiG2uPH5eDO6INcisyPpLS4yFKliaO4Jjap7yzLU9pbItoWgCAYa2NpxuxHJ0tB
+7tlDFnvaRnQukqSG+VqNWg==
+-----END CERTIFICATE-----
\ No newline at end of file