Add support of encryption and HTTP Auth
This commit is contained in:
parent
57e8cd39df
commit
4392035512
@ -4,5 +4,15 @@ module.exports = class Component {
|
|||||||
this.syntax = syntaxTree;
|
this.syntax = syntaxTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run server
|
||||||
run () {}
|
run () {}
|
||||||
|
|
||||||
|
// Working with domains
|
||||||
|
domainAction (context, domain) {}
|
||||||
|
|
||||||
|
// Call this function where need works with domain (DO NOT REWRITE)
|
||||||
|
callDomain (domain, domainST) {
|
||||||
|
const component = new domainST.Type(this.adr, domainST);
|
||||||
|
return { argv: (...argv) => component.domainAction(this, domain, ...argv) };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,82 @@
|
|||||||
const Component = require("./_component");
|
const Component = require("./_component");
|
||||||
|
|
||||||
|
const https = require("https");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
const bodyHand = require('body');
|
const bodyHand = require('body');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
function logger (...p) {
|
function logger (...p) {
|
||||||
return;
|
//return;
|
||||||
console.log("[DEBUG.main]:", ...p);
|
console.log("[DEBUG.main]:", ...p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function auth (self, req, res, next) {
|
||||||
|
if (self.syntax.auth) {
|
||||||
|
const ACCESS = "Basic " + Buffer.from(`${self.syntax.auth.login}:${self.syntax.auth.password}`).toString('base64');
|
||||||
|
if (req.headers['authorization'] === ACCESS) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (self.syntax.auth.mode === "strict" && req.headers['authorization'] !== undefined) {
|
||||||
|
return res.status(403).send("<h1>Permission denied</h1><hr/><p>Permission denied.</p>");
|
||||||
|
}
|
||||||
|
res.set("WWW-Authenticate", "Basic");
|
||||||
|
return res.status(401).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchProxy (req, res, reverseTo) {
|
||||||
|
const params = { method: req.method.toUpperCase(), headers: Object.assign({}, req.headers), redirect: "follow", follow: 20, compress: false };
|
||||||
|
delete params.headers.host;
|
||||||
|
delete params.headers.referer;
|
||||||
|
if (!["GET", "HEAD"].includes(params.method))
|
||||||
|
params.body = req.body;
|
||||||
|
|
||||||
|
const reqAddress = url.resolve(reverseTo, path.join("/", req.originalUrl));
|
||||||
|
fetch(reqAddress, params)
|
||||||
|
.then(async result => {
|
||||||
|
const content = await result.arrayBuffer();
|
||||||
|
//logger("result:", result);
|
||||||
|
//logger("result.arrayBuffer():", content);
|
||||||
|
//logger("result.Buffer:", Buffer.from(content).toString());
|
||||||
|
|
||||||
|
const headers = Object.fromEntries(result.headers);
|
||||||
|
|
||||||
|
//logger("headers Headers", result.headers);
|
||||||
|
//logger("headers Headers.raw()", result.headers.raw());
|
||||||
|
logger("headers object", headers);
|
||||||
|
|
||||||
|
Object.entries(headers).forEach(([header, value]) => {
|
||||||
|
res.set(header, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalContent = Buffer.from(content);
|
||||||
|
res.status(result.status).send(finalContent);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).send("<head><title>Error</title></head><body><h1>Error:</h1><hr/><p>Error catched. Check logs</p></body>");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = class HTTPReverse extends Component {
|
module.exports = class HTTPReverse extends Component {
|
||||||
constructor (adr, syntaxTree) {
|
constructor (adr, syntaxTree) {
|
||||||
super(adr, syntaxTree);
|
super(adr, syntaxTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
domainAction (context, domain, req, res) {
|
||||||
|
logger("DOMAIN ACT:", this.syntax);
|
||||||
|
if (!(context instanceof HTTPReverse))
|
||||||
|
throw new SyntaxError("You can't call revHTTP domain from unsupported type. Supported types: revHTTP, simpleHTTP, redirectHTTP");
|
||||||
|
fetchProxy(req, res, this.syntax.target);
|
||||||
|
}
|
||||||
|
|
||||||
run () {
|
run () {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
|
|
||||||
@ -38,39 +98,32 @@ module.exports = class HTTPReverse extends Component {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.app.use((req, res, next) => auth(this, req, res, next));
|
||||||
|
|
||||||
this.app.use("*", async (req, res) => {
|
this.app.use("*", async (req, res) => {
|
||||||
logger("REQ HEADERS:>", req.headers);
|
logger("REQ HEADERS:>", req.headers);
|
||||||
logger("originalUrl:>", req.originalUrl);
|
logger("originalUrl:>", req.originalUrl);
|
||||||
const currentDomain = req.hostname;
|
const currentDomain = req.hostname;
|
||||||
const params = { method: req.method.toUpperCase(), headers: Object.assign({}, req.headers), redirect: "follow", follow: 20 };
|
|
||||||
delete params.headers.host;
|
|
||||||
delete params.headers.referer;
|
|
||||||
if (!["GET", "HEAD"].includes(params.method))
|
|
||||||
params.body = req.body;
|
|
||||||
logger("hostname:", currentDomain);
|
logger("hostname:", currentDomain);
|
||||||
const reverseToLoc = !this.syntax.domains[currentDomain] ? reverseTo : this.syntax.domains[currentDomain].target;
|
const isNotDomained = !this.syntax.domains[currentDomain];
|
||||||
const reqAddress = url.resolve(reverseToLoc, path.join("/", req.originalUrl));
|
if (!isNotDomained) {
|
||||||
fetch(reqAddress, params)
|
return this
|
||||||
.then(async result => {
|
.callDomain(currentDomain, this.syntax.domains[currentDomain])
|
||||||
const content = await result.text();
|
.argv(req, res);
|
||||||
logger("result:", result);
|
}
|
||||||
logger("result.text():", content);
|
return fetchProxy(req, res, reverseTo);
|
||||||
Object.entries(result?.headers ? result.headers : new Object()).forEach(([header, value]) => {
|
|
||||||
res.set(header, value);
|
|
||||||
});
|
|
||||||
res.status(result.status).send(content);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
res.status(500).send("<head><title>Error</title></head><body><h1>Error:</h1><hr/><p>Error catched. Check logs</p></body>");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.listen(this.adr.port, address, (err) => {
|
const server = !this.syntax.encryption ? this.app : https.createServer({
|
||||||
|
cert : fs.readFileSync(this.syntax.encryption.public, 'utf-8'),
|
||||||
|
key : fs.readFileSync(this.syntax.encryption.private, 'utf-8')
|
||||||
|
}, this.app);
|
||||||
|
|
||||||
|
server.listen(this.adr.port, address, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
console.log(`Koboldcpp reverse proxy server listened at ${
|
console.log(`HTTP reverse proxy server listened at ${
|
||||||
address
|
address
|
||||||
}:${this.adr.port}`);
|
}:${this.adr.port}`);
|
||||||
})
|
})
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"revHTTP": require("./http-reverse"),
|
"revHTTP": require("./http-reverse"),
|
||||||
|
"simpleHTTP": require("./http-reverse"),
|
||||||
|
"redirectHTTP": require("./http-reverse"),
|
||||||
};
|
};
|
||||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -12,7 +12,8 @@
|
|||||||
"body": "^5.1.0",
|
"body": "^5.1.0",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"node-fetch": "^2.6.7"
|
"node-fetch": "^2.6.7",
|
||||||
|
"sse": "^0.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.9"
|
"nodemon": "^3.1.9"
|
||||||
@ -816,6 +817,14 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/options": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@ -1083,6 +1092,17 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sse": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/sse/-/sse-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-cviG7JH31TUhZeaEVhac3zTzA+2FwA7qvHziAHpb7mC7RNVJ/RbHN+6LIGsS2ugP4o2H15DWmrSMK+91CboIcg==",
|
||||||
|
"dependencies": {
|
||||||
|
"options": "0.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
@ -23,7 +23,8 @@
|
|||||||
"body": "^5.1.0",
|
"body": "^5.1.0",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"node-fetch": "^2.6.7"
|
"node-fetch": "^2.6.7",
|
||||||
|
"sse": "^0.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.9"
|
"nodemon": "^3.1.9"
|
||||||
|
@ -6,14 +6,14 @@ const components = require("./components");
|
|||||||
// const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi;
|
// const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi;
|
||||||
const DOMAINER_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi;
|
const DOMAINER_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi;
|
||||||
const DOMAINER_VALUE_FIND_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{/gmi;
|
const DOMAINER_VALUE_FIND_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{/gmi;
|
||||||
const ADDRESS_WITH_PORT_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}/gmi
|
const ADDRESS_WITH_PORT_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}/mi;
|
||||||
const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{([\s\n\r0-9a-z;:\/\."',]|domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}){0,}\}/gmi;
|
const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{([\s\n\r0-9a-z;:\/\."',]|domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}){0,}\}/gmi;
|
||||||
const COMMENT_REGEX = /#.{0,}(\n|\r|$)/gmi;
|
const COMMENT_REGEX = /#.{0,}(\n|\r|$)/gmi;
|
||||||
const CONTENT_INTO_CURLY_BRACES = /\{[\s\n\r0-9a-z;:\/\."',]{0,}\}{0,}\}/gmi;
|
const CONTENT_INTO_CURLY_BRACES = /\{[\s\n\r0-9a-z;:\/\."',]{0,}\}{0,}\}/gmi;
|
||||||
|
|
||||||
// Instruction regexps
|
// Instruction regexps
|
||||||
function readInstructions (instructions, fullCode, forDomains=false) {
|
function readInstructions (instructions, fullCode, forDomains=false) {
|
||||||
const syntaxTree = { firewall: false };
|
const syntaxTree = { firewall: false, encryption: {} };
|
||||||
|
|
||||||
for (let instruction of instructions) {
|
for (let instruction of instructions) {
|
||||||
if (!instruction) {
|
if (!instruction) {
|
||||||
@ -39,6 +39,22 @@ function readInstructions (instructions, fullCode, forDomains=false) {
|
|||||||
syntaxTree.target = instructionParts[1];
|
syntaxTree.target = instructionParts[1];
|
||||||
break;
|
break;
|
||||||
case "cert":
|
case "cert":
|
||||||
|
if (instructionParts.length < 3)
|
||||||
|
throw new SyntaxError("Invalid syntax of operator `cert`: cert <type:(public|private)> <path_to_cert>");
|
||||||
|
if (!["public", "private"].includes(instructionParts[1]))
|
||||||
|
throw new SyntaxError("Cert type can be only private or public, not " + instructionParts[1]);
|
||||||
|
syntaxTree.encryption[instructionParts[1]] = instructionParts.slice(2,).join(" ");
|
||||||
|
break;
|
||||||
|
case "auth":
|
||||||
|
if (instructionParts.length < 4 || instructionParts.length < 2)
|
||||||
|
throw new SyntaxError("Invalid syntax of operator `auth`: auth <mode:(loopback|strict)> <login> <password> OR auth null");
|
||||||
|
if (!["loopback", "strict", "null"].includes(instructionParts[1]))
|
||||||
|
throw new SyntaxError("Invalid mode: " + instructionParts[1] + ", valid modes: loopback, strict, null");
|
||||||
|
syntaxTree.auth = instructionParts[1] !== "null" ? {
|
||||||
|
mode: instructionParts[1],
|
||||||
|
login: instructionParts[2],
|
||||||
|
password: instructionParts.slice(3,).join(" ")
|
||||||
|
} : null;
|
||||||
break;
|
break;
|
||||||
case "firewall":
|
case "firewall":
|
||||||
break;
|
break;
|
||||||
@ -51,6 +67,11 @@ function readInstructions (instructions, fullCode, forDomains=false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(syntaxTree.encryption).length === 0)
|
||||||
|
syntaxTree.encryption = null;
|
||||||
|
else if (Object.keys(syntaxTree.encryption).length === 1)
|
||||||
|
throw new SyntaxError("No private or public key specified");
|
||||||
|
|
||||||
if (!forDomains) {
|
if (!forDomains) {
|
||||||
syntaxTree.domains = new Object();
|
syntaxTree.domains = new Object();
|
||||||
const allDomains = Object.fromEntries((fullCode.match(DOMAINER_REGEX) ?? [])
|
const allDomains = Object.fromEntries((fullCode.match(DOMAINER_REGEX) ?? [])
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
127.0.0.1:9889 {
|
127.0.0.1:9889 {
|
||||||
type revHTTP;
|
type revHTTP;
|
||||||
|
|
||||||
target https://example.org; # RRR
|
target https://example.org; # RRR
|
||||||
domain localhost {
|
domain localhost {
|
||||||
firewall whitelist GLOBAL;
|
firewall whitelist GLOBAL;
|
||||||
@ -12,6 +11,7 @@
|
|||||||
127.0.0.1:7889 {
|
127.0.0.1:7889 {
|
||||||
type revHTTP;
|
type revHTTP;
|
||||||
target https://google.com;
|
target https://google.com;
|
||||||
cert public /path/to/cert.pub;
|
auth loopback admin qwerty;
|
||||||
cert private /path/to/cert.priv;
|
cert public ./tests/pub.crt;
|
||||||
|
cert private ./tests/priv.key;
|
||||||
}
|
}
|
||||||
|
27
tests/priv.key
Normal file
27
tests/priv.key
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA2tGTaIEgV/Ar5hF5gL60pUJPYQuQ/8qCrKIB65i2KCTd/EIF
|
||||||
|
TmlcXVF+drSKyfBaPxee8FuuxR9gcw4mzPqYwRrqC1OPfANYEs+VUzBYRdI6uBgU
|
||||||
|
FWNVvWsDqpmHcGWgiZ+g/GRrfGBwN3xqpk+jPkTuXunBztZcvSyGCJnbpgKTbBrd
|
||||||
|
niRT6KX3KyZmwxSqvOQA7+ebUOUSrFY+t1Bf+BXD/QrECJ6RWbddrbFGSPE/QDei
|
||||||
|
8AlOWLVY8SHMtcC/0T4NSonR0yVVJ4NxkUO4P+kvdrqaKkY8UYXV4LLWt80J1Pky
|
||||||
|
YqhGq4g9nNnggpuDt4Xgp4uMI2hsb8H7Hn3JhwIDAQABAoIBAFEsnfqOrGjy99YJ
|
||||||
|
dYGFPY5w4vGlUM0TWxafowa7SBfsVU1XUJCeFV2o9bkjkLkJKPFu2c9gs5z83tLP
|
||||||
|
1SUFcdVxHpHFFd1zgCC6UTLjJFwCeTYB+LFWgGAcaufGvK+9g6iKRzZaKmWhgP+U
|
||||||
|
cBvyPyTCfYtIPFV2wECNW8ulCA61BgC8ecggCLkE3BBCVpz9pTcIKbNH/698EIIJ
|
||||||
|
EnS6VB2FBu0j1UGQ9cYOa5i1u+AW7GTfJxmO6zcdh5aNsWZQkshDmpyCbL3xHELK
|
||||||
|
okHvgbcoJDvNjW25UXcW78UJ+jHJoZJ3x61xVKFMaJ67ZlGNljlIhumhik2QPbvG
|
||||||
|
KLK8DAECgYEA+0EQN0lFOCIngowB/5QOxzHAg7UAQ7bXKsaAbZmJ5XGXrK3OltRi
|
||||||
|
jvxTw8z4BtW3+sW+5fkTmdCoz8Q9++Cg7eWecq5BWiToW8IB6hU73rpGoMTyLYX+
|
||||||
|
xmt3+QY84qVWFMHQfBB4tD3JHJc0otkyD4DbOOcxLE7pPO7dtlkEj8ECgYEA3vOr
|
||||||
|
xV3zO4Wn7nXh3eHmcdtcbTBFktOiP9zOGphgNFtELC2CvjSXE+WO4dORhfr8fAki
|
||||||
|
RJSstGI32RMOsYm11jUuMGKG3/a0p06wZZKWgsVMrtT5xme5nKTZeoFnDSKusApi
|
||||||
|
Tv1ueeM/fOSU7gElpP/XNxolbmmn7BIT0O80q0cCgYAPfBAKyDo63mf+9K/+11od
|
||||||
|
zqQludb9VVxnZ3psxsSn3ltRqhiMVSSJ2WU0MQKsMSm4i81bEQGzPhfZTiffyRVB
|
||||||
|
9B/Sw4iFjLbBSPCjQ5HABUzrBZhDU/nCssbfExJXiYcHmnZF3yIBQbmOSIdW7fnW
|
||||||
|
2MMp+52dNHPNbI8/eLb+QQKBgQCKMyFWxlXvn3npJslLcKwYB5HTXQjDasw4KfbY
|
||||||
|
JXv8+FXZzJwuqwq7GqDm+u5JzrqTaFYFjONBLWDQ/i8tfd9pWK248FoKmjKBQxW0
|
||||||
|
4JjLluFhvkLV6ZX0s3rbpTrruYPvO4CU1iOVCnhgQpDOt4dOMAiH730kY/njK5F9
|
||||||
|
Acn0IQKBgQCzZzRFkRDWn8H+79rW32arVSG+RBxrG3gPL4kLla5EHODeHYMlUf5I
|
||||||
|
qNypqfsw/wGTn9TWYyfx3SB4Of1B68T//BXghXMjm8YCWSGXJytBu/rFMgBpW5da
|
||||||
|
8OY8LpNaIIpmEKd8esq6B+VtEUEOoSlN1Yv84EG6yjCkHBNqpwqC4g==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
18
tests/pub.crt
Normal file
18
tests/pub.crt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhDCCAmygAwIBAgIEb2ZnLjANBgkqhkiG9w0BAQsFADBbMScwJQYDVQQDDB5SZWdlcnkgU2Vs
|
||||||
|
Zi1TaWduZWQgQ2VydGlmaWNhdGUxIzAhBgNVBAoMGlJlZ2VyeSwgaHR0cHM6Ly9yZWdlcnkuY29t
|
||||||
|
MQswCQYDVQQGEwJVQTAgFw0yNTAxMTEwMDAwMDBaGA8yMTI1MDExMTA2MzUxN1owRjESMBAGA1UE
|
||||||
|
AwwJbG9jYWxob3N0MSMwIQYDVQQKDBpSZWdlcnksIGh0dHBzOi8vcmVnZXJ5LmNvbTELMAkGA1UE
|
||||||
|
BhMCVUEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa0ZNogSBX8CvmEXmAvrSlQk9h
|
||||||
|
C5D/yoKsogHrmLYoJN38QgVOaVxdUX52tIrJ8Fo/F57wW67FH2BzDibM+pjBGuoLU498A1gSz5VT
|
||||||
|
MFhF0jq4GBQVY1W9awOqmYdwZaCJn6D8ZGt8YHA3fGqmT6M+RO5e6cHO1ly9LIYImdumApNsGt2e
|
||||||
|
JFPopfcrJmbDFKq85ADv55tQ5RKsVj63UF/4FcP9CsQInpFZt12tsUZI8T9AN6LwCU5YtVjxIcy1
|
||||||
|
wL/RPg1KidHTJVUng3GRQ7g/6S92upoqRjxRhdXgsta3zQnU+TJiqEariD2c2eCCm4O3heCni4wj
|
||||||
|
aGxvwfsefcmHAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1Ud
|
||||||
|
DgQWBBTrZLnKR4WyMdxMIHutC14pjwAfIzAfBgNVHSMEGDAWgBTrZLnKR4WyMdxMIHutC14pjwAf
|
||||||
|
IzANBgkqhkiG9w0BAQsFAAOCAQEAFxch816B5Y3qNnKSBiynDhFwZJoRGln4bcq/U4IACGczuzbI
|
||||||
|
3BMtFah6I3FHDF2rrLfzhwnWct5GRqfIQSdqR4jImXbIvDVxJh1EW/5IhnmRGc/xpVr8Pb2CgwU5
|
||||||
|
dh2DiIfQzEGId+BuP1M/t55x4rjI1bdRwmb1MdZ4sQbVXX+IY+zQ6rOx+BAJ5ne+TQMJYDQbhc3O
|
||||||
|
AETJ77uvSaROghKPYZ+S1GKQEXSPI+MgPV2SBRL6x5cCiV+Vwio2ld6v+E+V9m5dkQxv1Z1MMv6Z
|
||||||
|
vqmLsM3tUE1zB7+vnZcu/rZcm1OkTZ7lQLYm9KQQeeJ9q70SBSwqrLTOd1w5fEkKBw==
|
||||||
|
-----END CERTIFICATE-----
|
Loading…
Reference in New Issue
Block a user