Skip to content

Commit

Permalink
test: add functional tests for mutualTLS security handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Macedo, John committed Jun 13, 2023
1 parent ada004b commit cc673c9
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 8 deletions.
70 changes: 66 additions & 4 deletions tests/suites/security.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import fs from 'fs';
import sinon from 'sinon';
import assert from 'assert';
import axios from 'axios';
import https from 'https';

export default () => {
let cfg;
let secureServerOpt;

describe('\n OAS-Security Middleware tests', () => {
describe('Initialization tests', () => {
Expand Down Expand Up @@ -65,7 +67,7 @@ export default () => {
});

describe('Function tests', () => {
before(async () => {
beforeEach(async () => {
cfg = JSON.parse(fs.readFileSync('tests/testServer/.oastoolsrc'));
cfg.logger.level = 'off'; // Set to 'error' if any test fail to see the error message
cfg.middleware.security = {
Expand All @@ -79,10 +81,16 @@ export default () => {
openID: (secDef, secScope, _setStatus) => {global.openIDCredentials = {secDef, secScope}}
}
}
await init(cfg); //init server with default config
secureServerOpt = {
key: fs.readFileSync('tests/testServer/testCerts/server.key'),
cert: fs.readFileSync('tests/testServer/testCerts/server.crt'),
requestCert: true,
rejectUnauthorized: false // don't care about valid chain
};
});

it('Should authenticate correctly with http basic auth and openID', async () => {
await init(cfg); //init server with default config
await axios.get('http://localhost:8080/api/v1/oasSecurity', {
headers: {
Authorization: 'Basic ' + Buffer.from('test:test').toString('base64')
Expand All @@ -98,6 +106,7 @@ export default () => {
});

it('Should authenticate correctly with http bearer auth', async () => {
await init(cfg); //init server with default config
await axios.get('http://localhost:8080/api/v1/oasSecurity/bearer', {
headers: {
Authorization: 'Bearer ' + Buffer.from('test:test').toString('base64')
Expand All @@ -109,6 +118,7 @@ export default () => {
});

it('Should authenticate correctly with all apiKeys defined in OAS Doc', async () => {
await init(cfg); //init server with default config
await axios.get('http://localhost:8080/api/v1/oasSecurity?apiKeyQuery=testApiKeyQuery', {
headers: {
apiKeyHeader: 'testApiKeyHeader',
Expand All @@ -123,6 +133,7 @@ export default () => {
});

it('Should fail with code 401 when missing the required auth types', async () => {
await init(cfg); //init server with default config
await axios.get('http://localhost:8080/api/v1/oasSecurity')
.then(() => assert.fail("Expected code 401 but got 2XX"))
.catch( err => {
Expand All @@ -132,18 +143,69 @@ export default () => {
});

it('Should fail with code 500 when using a non-declared security scheme', async () => {
await init(cfg); //init server with default config
await axios.get('http://localhost:8080/api/v1/oasSecurity/invalid')
.then(() => assert.fail("Expected code 500 but got 2XX"))
.catch( err => {
assert.deepStrictEqual(err.response.data, {error: "ConfigError: Security scheme 'undeclaredScheme' not found in OAS Document."});
assert.equal(err.response.status, 500);
});
});

it('Should authenticate correctly with mutualTLS under openapi 3.1', async () => {
cfg.middleware.security = {
disable: false,
auth: {
mutualTLSScheme: (cert) => {global.certCredentials = cert}
}
};
cfg.oasFile = 'tests/testServer/api/3.1.yaml';

await init(cfg,secureServerOpt,true); // init https server
await axios.get('https://localhost:8080/api/v1/oasSecurity', {
httpsAgent: new https.Agent({
rejectUnauthorized: false, // don't care about valid chain
cert: fs.readFileSync('tests/testServer/testCerts/client.crt'),
key: fs.readFileSync('tests/testServer/testCerts/client.key'),
})
})
.then( res => {
console.log(res.status);
assert("certCredentials" in global &&
"subject" in global.certCredentials &&
"CN" in global.certCredentials.subject,
"No client credentials");
assert.equal(global.certCredentials.subject.CN,'client.example.com');
})
});

it('Should get empty client cert when server not requesting one, and mutualTLS under openapi 3.1', async () => {
cfg.middleware.security = {
disable: false,
auth: {
mutualTLSScheme: (cert) => {global.certCredentials = cert}
}
};
cfg.oasFile = 'tests/testServer/api/3.1.yaml';
secureServerOpt.requestCert = false;
secureServerOpt.rejectUnauthorized = true;

await init(cfg,secureServerOpt,true); // init https server
await axios.get('https://localhost:8080/api/v1/oasSecurity', {
httpsAgent: new https.Agent({
rejectUnauthorized: false // don't care about valid chain
})
})
.then( res => {
assert.equal(res.status, 200);
assert.equal(Object.keys(global.certCredentials).length, 0);
})
});

after((done) => {
afterEach((done) => {
close().then(() => done());
});
})

})
}
}
15 changes: 14 additions & 1 deletion tests/testServer/api/3.1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,17 @@ paths:
operationId: getRequest
x-router-controller: oasRouterTestController
responses:
'200': {$ref: 'subschemas/responses.yaml#/200'}
'200': {$ref: 'subschemas/responses.yaml#/200'}
# OAS SECURITY TEST ENDPOINT
/api/v1/oasSecurity:
x-router-controller: oasSecurityTestController
get:
operationId: getRequest
security: # mutualTLS for openapi 3.1
- mutualTLSScheme: []
responses:
'200': {$ref: 'subschemas/responses.yaml#/200'}
components:
securitySchemes:
mutualTLSScheme:
type: mutualTLS
9 changes: 6 additions & 3 deletions tests/testServer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import express from 'express';
import http from 'http';
import https from 'https';
import fs from 'fs';

var server;
Expand All @@ -12,11 +13,13 @@ let defaults = JSON.parse(fs.readFileSync('tests/testServer/.oastoolsrc'));
let {use, initialize} = await importFresh('../../src/index.js');

export {use};
export async function init(config) {
export async function init(config,serverOpt,secureServer) {
app.use(express.json({limit: '50mb'}));
app.get('/status', (_req, res, _next) => res.status(200).send('Up'));
await initialize(app, config ?? defaults).then(() => {
server = http.createServer(app)
const serverType = (secureServer?https:http);
server = (serverOpt?serverType.createServer(serverOpt,app):
serverType.createServer(app));
server.listen(8080);
});
}
Expand All @@ -37,4 +40,4 @@ async function clearCache() {
async function importFresh(modulePath) {
const cacheBustingModulePath = `${modulePath}?update=${Date.now()}`
return (await import(cacheBustingModulePath))
}
}
80 changes: 80 additions & 0 deletions tests/testServer/testCerts/client.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
61:6d:74:c1:bb:83:7e:a5:7b:9b:e5:7b:53:fd:b9:a8:f3:03:c4:0a
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = XX, L = Default City, O = Default Company Ltd, CN = client.example.com
Validity
Not Before: Jun 12 19:48:36 2023 GMT
Not After : Jun 13 19:48:36 2023 GMT
Subject: C = XX, L = Default City, O = Default Company Ltd, CN = client.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:ce:2e:1c:2a:3d:2e:43:2a:63:14:12:62:8b:17:
fa:56:5f:65:49:2d:6b:0e:63:f0:86:73:02:9b:44:
86:1a:8f:8f:20:75:16:f2:0e:87:09:86:03:5e:7b:
48:b9:50:16:31:b5:d3:82:51:0d:88:fc:10:51:9f:
b8:8c:9c:1c:cd:3a:18:39:e3:27:b6:54:86:6a:27:
38:a9:06:0b:2b:a0:28:f0:91:cc:62:59:9f:8f:e1:
0a:b0:b6:f3:1b:b4:de:cd:41:b3:c3:57:94:49:47:
fb:50:e2:2d:96:9c:68:02:93:f4:d4:58:f2:dd:bf:
91:8a:cb:82:24:a9:60:21:cf:b7:c5:ef:f7:af:d8:
47:ed:e8:35:09:91:91:cd:e4:b0:71:f2:e7:67:0f:
a1:7b:27:cd:d5:07:ab:a4:39:7b:4f:7e:19:ef:d0:
fc:6c:4f:a8:77:99:51:a6:34:ea:00:58:26:dd:1d:
cc:58:5c:51:c0:f6:ae:b1:fd:a9:08:db:87:19:b8:
df:67:bd:30:71:43:61:af:ba:2e:55:f5:d1:db:ff:
a6:b8:18:cf:a4:b6:2f:6f:32:20:b8:20:98:02:e0:
7e:6c:b6:2b:c6:c5:85:b2:2f:44:0e:da:16:9b:c6:
30:92:ae:b5:d6:6d:9e:03:57:24:65:a9:7b:cf:3d:
7d:bf
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
D8:C9:39:76:51:21:D9:25:4E:12:CC:28:44:FB:54:B5:B5:66:C1:08
X509v3 Authority Key Identifier:
keyid:D8:C9:39:76:51:21:D9:25:4E:12:CC:28:44:FB:54:B5:B5:66:C1:08

X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
68:1d:ef:7a:78:c7:5c:58:e3:5a:b4:97:22:e4:79:7d:59:58:
4f:41:3a:95:98:42:4f:b4:35:13:3b:b9:53:74:89:f5:0f:83:
6d:70:47:a8:10:f9:e1:43:d2:5d:ea:ad:83:68:11:1d:99:96:
ad:e7:37:c3:69:8e:56:83:d0:86:3d:e8:df:b9:d6:76:18:58:
82:73:55:6d:5e:bc:c4:d3:63:18:73:ca:ad:4d:61:01:3d:d4:
39:70:ac:8f:20:6e:4c:29:6b:62:78:9d:c0:e3:50:a3:fc:1b:
ea:26:2e:bc:0f:b8:ea:12:5d:2c:63:03:33:1b:71:4f:01:cd:
a7:a6:13:17:4d:ff:95:a7:e4:8c:c2:14:52:cc:a4:c7:a8:45:
29:f3:72:8a:19:e9:52:5e:7f:6f:84:00:c5:69:5a:0a:51:9b:
b3:7f:5f:1b:e9:bd:95:37:5d:1b:06:9e:45:e3:68:cb:f7:a1:
c9:d6:f1:25:7a:bd:c9:44:a0:59:8e:0d:25:41:dc:a7:03:07:
af:7e:59:94:0c:4c:6d:e8:60:a0:15:1f:3c:18:56:0f:64:d6:
15:d1:0e:ba:ec:1a:5b:ae:6d:51:f6:45:0a:51:e7:b5:93:70:
01:4f:e6:39:b1:e9:1f:4f:37:ad:cd:13:08:72:90:6f:45:f0:
6c:81:18:17
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIUYW10wbuDfqV7m+V7U/25qPMDxAowDQYJKoZIhvcNAQEL
BQAwXzELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUu
Y29tMB4XDTIzMDYxMjE5NDgzNloXDTIzMDYxMzE5NDgzNlowXzELMAkGA1UEBhMC
WFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21w
YW55IEx0ZDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzi4cKj0uQypjFBJiixf6Vl9lSS1rDmPwhnMC
m0SGGo+PIHUW8g6HCYYDXntIuVAWMbXTglENiPwQUZ+4jJwczToYOeMntlSGaic4
qQYLK6Ao8JHMYlmfj+EKsLbzG7TezUGzw1eUSUf7UOItlpxoApP01Fjy3b+RisuC
JKlgIc+3xe/3r9hH7eg1CZGRzeSwcfLnZw+heyfN1QerpDl7T34Z79D8bE+od5lR
pjTqAFgm3R3MWFxRwPausf2pCNuHGbjfZ70wcUNhr7ouVfXR2/+muBjPpLYvbzIg
uCCYAuB+bLYrxsWFsi9EDtoWm8Ywkq611m2eA1ckZal7zz19vwIDAQABo1MwUTAd
BgNVHQ4EFgQU2Mk5dlEh2SVOEswoRPtUtbVmwQgwHwYDVR0jBBgwFoAU2Mk5dlEh
2SVOEswoRPtUtbVmwQgwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AQEAaB3venjHXFjjWrSXIuR5fVlYT0E6lZhCT7Q1Ezu5U3SJ9Q+DbXBHqBD54UPS
Xeqtg2gRHZmWrec3w2mOVoPQhj3o37nWdhhYgnNVbV68xNNjGHPKrU1hAT3UOXCs
jyBuTClrYnidwONQo/wb6iYuvA+46hJdLGMDMxtxTwHNp6YTF03/lafkjMIUUsyk
x6hFKfNyihnpUl5/b4QAxWlaClGbs39fG+m9lTddGwaeReNoy/ehydbxJXq9yUSg
WY4NJUHcpwMHr35ZlAxMbehgoBUfPBhWD2TWFdEOuuwaW65tUfZFClHntZNwAU/m
ObHpH083rc0TCHKQb0XwbIEYFw==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions tests/testServer/testCerts/client.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOLhwqPS5DKmMU
EmKLF/pWX2VJLWsOY/CGcwKbRIYaj48gdRbyDocJhgNee0i5UBYxtdOCUQ2I/BBR
n7iMnBzNOhg54ye2VIZqJzipBgsroCjwkcxiWZ+P4QqwtvMbtN7NQbPDV5RJR/tQ
4i2WnGgCk/TUWPLdv5GKy4IkqWAhz7fF7/ev2Eft6DUJkZHN5LBx8udnD6F7J83V
B6ukOXtPfhnv0PxsT6h3mVGmNOoAWCbdHcxYXFHA9q6x/akI24cZuN9nvTBxQ2Gv
ui5V9dHb/6a4GM+kti9vMiC4IJgC4H5stivGxYWyL0QO2habxjCSrrXWbZ4DVyRl
qXvPPX2/AgMBAAECggEAR/5Jz2X1lXZjikqxJiiFPUuqLlcxnqC/B/N44YaX3QZr
z8WS8YqpFGY7Hu6lgi+40DrRVBvBQifh+t5uroqEdBTpCK2M3Fm/Zam0WzDFDcKr
CPWgDGeHE9JczT/Uu8P4WGusvXUDTmNYGfIgQQr0NpI7VzqH825E/9X3551M+ADT
ao9x1t9MxG4XQd8xPwpvrx6SzJkEepvQjrJyGITjNZWOn6d1Y/F6uFCwQzYHQIh0
NJPLslcby6OEF/zrW4xi4Cb7BR0nBwU1DOaNVqukcUVNMfAZmO/SLavpIlMfZ32q
FNxIzNo8Jk5y72fsrxsFH2XD/Gn8q6o2bXOrZnqRgQKBgQDway7RBE3K2omQ1YWg
nrzACdvlnAGsNxCp0GOb4GqpDd/MERO0YQmPp19X/x6y8ywHvXdbdGLeVwbtibsv
x0jMwxnizhhKiItu08Hv8V3w2y4tFYxja6BccuQLL9TznVAXTh0Nq4jfIkceLLNi
Z3vBIq7lQLzg3b5z7SSQk7QE0QKBgQDbit7nU5l7pZKB1nPPjczkcQ8QtA5wMPre
oau0W4NV4CZg9rNhbColDdmsG0UsGrvueL4FEfGBs8cJkGualnY9zN4idJdh/eGn
CJZbJuAn6urRwEqD1okaean2xBM9t4eGdY+F6F6xNLotsYYD0h5EXQnvQc/IC/f6
Wj8adjk9jwKBgHjzw3LecnCqr2jRM1ASbC1FCH8klGbasSRttZ83MRHp6/yTXtFx
vEisULhJnl0zH6SpOkIldJQdPlwE3cp0vPhe80f6nZfpyBQyrrATQ0qoSjveSmm+
PMgl+PpzzvPc4e1HnDBDazGu4g6ZOIo6O7V3yHuHrT6H8IgL90hRNtDxAoGBAJ7+
NWvGKXNYd3+8NWbsxKP0FPr9yy8I6cXtw5H8bGL20BSM1WAKm4L2QtvXbAMC4XNU
0hiN5B8QeWa9xo/Pw5YCFiVR1ohK+u6i03Dz+IYwJRr+bXiJGAWqcqKZYW3iyi1g
Wi6aGGmxS9vnoqyFsRHFZ3p0aOvxbwdJ8EVTtSSVAoGACJsDWE+t4IJM1fnliup/
OeLJO4M3gIP7QAqXNybYG4A/JSdwS0PlqvcNvCZYuuPU2Ns1+AH3yj6ys5WOMCcx
TXYi+3yiAJfBUiSd7ClE2pu27VWkPqDBgF2+Pa+QZ/7wxAFOKkbi9QZE8HJzVNT+
Ik/f4mW7IOPLdQJ6G6lI3Vk=
-----END PRIVATE KEY-----
80 changes: 80 additions & 0 deletions tests/testServer/testCerts/server.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
69:12:78:fd:f3:e3:6b:50:ff:14:31:cd:56:3a:ac:46:ca:06:1c:b8
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = XX, L = Default City, O = Default Company Ltd, CN = server.example.com
Validity
Not Before: Jun 12 19:47:36 2023 GMT
Not After : Jun 13 19:47:36 2023 GMT
Subject: C = XX, L = Default City, O = Default Company Ltd, CN = server.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:e8:f4:b6:c0:5d:0b:a2:e3:e5:1b:3a:48:53:a9:
9e:e8:8d:70:93:8b:4d:a5:de:a8:fd:b7:59:9f:fd:
ea:35:19:fa:f2:a3:67:af:e7:c0:04:cc:8f:d9:4e:
e1:3f:0b:d1:4f:a3:db:f3:7e:b2:35:ad:c1:4d:0e:
35:92:c8:88:37:b7:5c:f1:79:5c:e7:a7:34:ef:c4:
1d:79:b2:85:63:e0:cc:05:34:0a:ce:0f:d2:18:1b:
1d:93:e0:50:80:6f:03:70:78:1e:b6:83:37:06:36:
a2:03:7c:36:29:8d:8c:ef:00:48:20:49:8d:f8:b0:
1f:c9:c8:13:f1:53:b4:b7:8d:92:1d:9b:82:bd:38:
4d:19:0e:66:7a:f0:15:31:8c:e3:55:65:ae:cd:e5:
fc:5d:52:4d:c4:94:dc:99:8b:3e:75:e7:da:26:84:
81:9f:dc:e1:ea:71:05:da:ca:8c:ea:ba:43:a1:1a:
0a:78:67:8b:32:5d:2c:1d:ee:05:f3:a7:7f:78:5e:
d4:cf:7d:3d:cc:af:9b:6d:ac:a0:06:12:54:92:4a:
90:1d:df:dc:b4:97:c4:c4:3d:9d:61:28:e5:a0:11:
b8:99:03:e4:7e:42:de:52:1f:28:b2:ca:c3:6e:23:
65:87:19:55:ac:5e:cc:8b:23:ba:d8:88:55:0f:e3:
35:5b
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
FB:70:62:F5:70:FA:44:EC:80:BB:94:AF:CE:C3:F1:61:12:44:60:32
X509v3 Authority Key Identifier:
keyid:FB:70:62:F5:70:FA:44:EC:80:BB:94:AF:CE:C3:F1:61:12:44:60:32

X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
8a:0e:33:1e:5f:9f:cb:ef:40:13:9d:78:8b:06:23:55:fd:6c:
4b:c0:80:34:fd:0e:f4:a2:2d:29:b2:26:6e:1f:d5:d9:b5:15:
44:09:cf:55:d5:75:4f:b9:ca:ce:19:b5:94:19:0e:19:17:13:
e8:78:9a:96:0c:89:75:63:a4:c4:db:cb:0b:d7:3a:98:4e:04:
1a:aa:83:a5:36:86:93:7f:35:d8:4f:89:0e:0b:8a:ee:dc:5d:
0c:24:90:09:4e:64:f8:31:f1:35:5b:4e:0f:fa:c5:67:db:7c:
4b:c0:af:55:5b:ed:4c:2a:85:67:53:93:94:51:17:8a:a2:a6:
42:24:be:83:6e:77:40:94:dd:65:b6:b3:73:67:66:12:11:51:
e8:02:fb:ec:ee:ff:03:3b:ac:79:a8:4b:f3:e3:a7:a2:5a:ae:
82:e5:28:b5:e6:e0:7b:bc:f9:21:3f:e7:14:a0:cf:ef:ce:83:
c8:c2:0f:b0:1b:ab:cd:ff:4b:d0:72:5d:46:0f:ec:18:e5:51:
f1:46:24:be:5a:33:3a:38:0e:d2:92:6f:3b:f9:89:90:ac:6d:
05:e7:63:6f:e1:0b:1b:40:e8:a2:23:60:d0:d5:e3:63:20:0b:
0c:a2:2a:18:f1:4d:5b:04:6f:fb:3c:f2:15:87:6f:a4:91:63:
b2:e6:93:fd
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIUaRJ4/fPja1D/FDHNVjqsRsoGHLgwDQYJKoZIhvcNAQEL
BQAwXzELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEbMBkGA1UEAwwSc2VydmVyLmV4YW1wbGUu
Y29tMB4XDTIzMDYxMjE5NDczNloXDTIzMDYxMzE5NDczNlowXzELMAkGA1UEBhMC
WFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21w
YW55IEx0ZDEbMBkGA1UEAwwSc2VydmVyLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6PS2wF0LouPlGzpIU6me6I1wk4tNpd6o/bdZ
n/3qNRn68qNnr+fABMyP2U7hPwvRT6Pb836yNa3BTQ41ksiIN7dc8Xlc56c078Qd
ebKFY+DMBTQKzg/SGBsdk+BQgG8DcHgetoM3BjaiA3w2KY2M7wBIIEmN+LAfycgT
8VO0t42SHZuCvThNGQ5mevAVMYzjVWWuzeX8XVJNxJTcmYs+defaJoSBn9zh6nEF
2sqM6rpDoRoKeGeLMl0sHe4F86d/eF7Uz309zK+bbaygBhJUkkqQHd/ctJfExD2d
YSjloBG4mQPkfkLeUh8ossrDbiNlhxlVrF7MiyO62IhVD+M1WwIDAQABo1MwUTAd
BgNVHQ4EFgQU+3Bi9XD6ROyAu5SvzsPxYRJEYDIwHwYDVR0jBBgwFoAU+3Bi9XD6
ROyAu5SvzsPxYRJEYDIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AQEAig4zHl+fy+9AE514iwYjVf1sS8CANP0O9KItKbImbh/V2bUVRAnPVdV1T7nK
zhm1lBkOGRcT6HialgyJdWOkxNvLC9c6mE4EGqqDpTaGk3812E+JDguK7txdDCSQ
CU5k+DHxNVtOD/rFZ9t8S8CvVVvtTCqFZ1OTlFEXiqKmQiS+g253QJTdZbazc2dm
EhFR6AL77O7/AzuseahL8+OnolquguUotebge7z5IT/nFKDP786DyMIPsBurzf9L
0HJdRg/sGOVR8UYkvlozOjgO0pJvO/mJkKxtBedjb+ELG0DooiNg0NXjYyALDKIq
GPFNWwRv+zzyFYdvpJFjsuaT/Q==
-----END CERTIFICATE-----

0 comments on commit cc673c9

Please sign in to comment.