Merge pull request #32 from keeshux/restore-pia-patches

Restore PIA patches
This commit is contained in:
Davide De Rosa 2018-10-18 16:34:40 +02:00 committed by GitHub
commit 69e5921163
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 204 additions and 9 deletions

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project _will soon adhere_ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Restore support for PIA patches. [#32](https://github.com/keeshux/tunnelkit/pull/32)
## 1.1.1 (2018-10-10)
### Fixed

View File

@ -91,11 +91,12 @@ extension ViewController {
var builder = TunnelKitProvider.ConfigurationBuilder(ca: ca)
let socketType: TunnelKitProvider.SocketType = switchTCP.isOn ? .tcp : .udp
builder.endpointProtocols = [TunnelKitProvider.EndpointProtocol(socketType, port)]
builder.cipher = .aes128cbc
builder.cipher = .aes256gcm
builder.digest = .sha1
builder.mtu = 1350
builder.compressionFraming = .compLZO
builder.renegotiatesAfterSeconds = nil
builder.usesPIAPatches = true
builder.shouldDebug = true
builder.debugLogKey = "Log"

View File

@ -1,13 +1,13 @@
PODS:
- OpenSSL-Apple (1.1.0i-v2)
- SwiftyBeaver (1.6.1)
- TunnelKit (1.1.0):
- TunnelKit/AppExtension (= 1.1.0)
- TunnelKit/Core (= 1.1.0)
- TunnelKit/AppExtension (1.1.0):
- TunnelKit (1.1.2):
- TunnelKit/AppExtension (= 1.1.2)
- TunnelKit/Core (= 1.1.2)
- TunnelKit/AppExtension (1.1.2):
- SwiftyBeaver
- TunnelKit/Core
- TunnelKit/Core (1.1.0):
- TunnelKit/Core (1.1.2):
- OpenSSL-Apple (~> 1.1.0h)
- SwiftyBeaver
@ -26,7 +26,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
OpenSSL-Apple: a93b8f2eec8783ff40d9a9304de180ab68bb647c
SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
TunnelKit: 21af89c08aadfa81d25835a1faa46ddf12374772
TunnelKit: 392e78dd45cded30a6f814200b8334c33901f677
PODFILE CHECKSUM: f66dfaaa92a8d04ab2743f3caeab0ac9f9f25859

View File

@ -45,6 +45,9 @@
0E3E0F212108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; };
0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; };
0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; };
0E749F622178911D00BB2701 /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; };
0E749F5F2178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; };
0E749F602178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; };
0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */; };
0E9379C91F819A4300CE91B6 /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */; };
0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */; };
@ -214,6 +217,8 @@
0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = "<group>"; };
0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0E6479E0212EACD6008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0E749F612178911C00BB2701 /* pia-2048.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = "pia-2048.pem"; sourceTree = "<group>"; };
0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PIA.swift"; sourceTree = "<group>"; };
0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppExtensionTests.swift; sourceTree = "<group>"; };
0E85A25B202CCA3D0059E9F9 /* TunnelKitHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TunnelKitHost.entitlements; sourceTree = "<group>"; };
0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = "<group>"; };
@ -340,6 +345,7 @@
0EB2B45620F0BD16004233D7 /* RandomTests.swift */,
0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */,
0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */,
0E749F612178911C00BB2701 /* pia-2048.pem */,
);
path = TunnelKitTests;
sourceTree = "<group>";
@ -472,6 +478,7 @@
0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */,
0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */,
0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */,
0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */,
0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */,
0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */,
0EFEB4442006D3C800F81029 /* TLSBox.h */,
@ -719,6 +726,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E749F622178911D00BB2701 /* pia-2048.pem in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -869,6 +877,7 @@
0EC1BBA520D71190007C4C7B /* DNSResolver.swift in Sources */,
0EFEB4AB200760EC00F81029 /* MemoryDestination.swift in Sources */,
0EFEB4AE2007625E00F81029 /* Keychain.swift in Sources */,
0E749F5F2178885500BB2701 /* SessionProxy+PIA.swift in Sources */,
0EBBF3002085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */,
0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */,
0EFEB45D2006D3C800F81029 /* CryptoBox.m in Sources */,
@ -924,6 +933,7 @@
0EFEB4B12007627700F81029 /* MemoryDestination.swift in Sources */,
0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */,
0EFEB4A02006D7F300F81029 /* ReplayProtector.m in Sources */,
0E749F602178885500BB2701 /* SessionProxy+PIA.swift in Sources */,
0EFEB4992006D7F300F81029 /* SessionProxy.swift in Sources */,
0EBBF3012085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */,
0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */,

View File

@ -146,6 +146,9 @@ extension TunnelKitProvider {
/// The number of seconds after which a renegotiation is started. Set to `nil` to disable renegotiation (default).
public var renegotiatesAfterSeconds: Int?
/// Server is patched for the PIA VPN provider.
public var usesPIAPatches: Bool?
// MARK: Debugging
/// Enables debugging. If `true`, then `debugLogKey` is a mandatory field.
@ -177,6 +180,7 @@ extension TunnelKitProvider {
compressionFraming = .disabled
keepAliveSeconds = nil
renegotiatesAfterSeconds = nil
usesPIAPatches = false
shouldDebug = false
debugLogKey = nil
debugLogFormat = nil
@ -232,6 +236,7 @@ extension TunnelKitProvider {
}
keepAliveSeconds = providerConfiguration[S.keepAlive] as? Int
renegotiatesAfterSeconds = providerConfiguration[S.renegotiatesAfter] as? Int
usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? false
shouldDebug = providerConfiguration[S.debug] as? Bool ?? false
if shouldDebug {
@ -268,6 +273,7 @@ extension TunnelKitProvider {
compressionFraming: compressionFraming,
keepAliveSeconds: keepAliveSeconds,
renegotiatesAfterSeconds: renegotiatesAfterSeconds,
usesPIAPatches: usesPIAPatches,
shouldDebug: shouldDebug,
debugLogKey: shouldDebug ? debugLogKey : nil,
debugLogFormat: shouldDebug ? debugLogFormat : nil
@ -304,6 +310,8 @@ extension TunnelKitProvider {
static let renegotiatesAfter = "RenegotiatesAfter"
static let usesPIAPatches = "UsesPIAPatches"
static let debug = "Debug"
static let debugLogKey = "DebugLogKey"
@ -347,6 +355,9 @@ extension TunnelKitProvider {
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.renegotiatesAfterSeconds`
public let renegotiatesAfterSeconds: Int?
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.usesPIAPatches`
public let usesPIAPatches: Bool?
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.shouldDebug`
public let shouldDebug: Bool
@ -428,6 +439,9 @@ extension TunnelKitProvider {
if let renegotiatesAfterSeconds = renegotiatesAfterSeconds {
dict[S.renegotiatesAfter] = renegotiatesAfterSeconds
}
if let usesPIAPatches = usesPIAPatches {
dict[S.usesPIAPatches] = usesPIAPatches
}
if let debugLogKey = debugLogKey {
dict[S.debugLogKey] = debugLogKey
}
@ -518,6 +532,7 @@ extension TunnelKitProvider.Configuration: Equatable {
builder.compressionFraming = compressionFraming
builder.keepAliveSeconds = keepAliveSeconds
builder.renegotiatesAfterSeconds = renegotiatesAfterSeconds
builder.usesPIAPatches = usesPIAPatches
builder.shouldDebug = shouldDebug
builder.debugLogKey = debugLogKey
builder.debugLogFormat = debugLogFormat

View File

@ -234,6 +234,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
if let renegotiatesAfterSeconds = cfg.renegotiatesAfterSeconds {
sessionConfiguration.renegotiatesAfter = TimeInterval(renegotiatesAfterSeconds)
}
sessionConfiguration.usesPIAPatches = cfg.usesPIAPatches ?? false
let proxy: SessionProxy
do {

View File

@ -162,6 +162,9 @@ extension SessionProxy {
/// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation.
public var renegotiatesAfter: TimeInterval?
/// Server is patched for the PIA VPN provider.
public var usesPIAPatches: Bool
/// :nodoc:
public init(caPath: String) {
credentials = nil
@ -173,6 +176,7 @@ extension SessionProxy {
compressionFraming = .disabled
keepAliveInterval = nil
renegotiatesAfter = nil
usesPIAPatches = false
}
/**
@ -190,7 +194,8 @@ extension SessionProxy {
clientKeyPath: clientKeyPath,
compressionFraming: compressionFraming,
keepAliveInterval: keepAliveInterval,
renegotiatesAfter: renegotiatesAfter
renegotiatesAfter: renegotiatesAfter,
usesPIAPatches: usesPIAPatches
)
}
}
@ -224,5 +229,8 @@ extension SessionProxy {
/// - Seealso: `SessionProxy.ConfigurationBuilder.renegotiatesAfter`
public let renegotiatesAfter: TimeInterval?
/// - Seealso: `SessionProxy.ConfigurationBuilder.usesPIAPatches`
public let usesPIAPatches: Bool
}
}

View File

@ -0,0 +1,77 @@
//
// SessionProxy+PIA.swift
// TunnelKit
//
// Created by Davide De Rosa on 10/18/18.
// Copyright (c) 2018 Davide De Rosa. All rights reserved.
//
// https://github.com/keeshux
//
// This file is part of TunnelKit.
//
// TunnelKit is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// TunnelKit is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (c) 2018-Present Private Internet Access
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//
import Foundation
extension SessionProxy {
struct PIAHardReset {
private static let obfuscationKeyLength = 3
private static let magic = "53eo0rk92gxic98p1asgl5auh59r1vp4lmry1e3chzi100qntd"
private static let encodedFormat = "\(magic)crypto\t%@|%@\tca\t%@"
private let caMd5Digest: String
private let cipherName: String
private let digestName: String
init(caMd5Digest: String, cipher: Cipher, digest: Digest) {
self.caMd5Digest = caMd5Digest
cipherName = cipher.rawValue.lowercased()
digestName = digest.rawValue.lowercased()
}
// Ruby: pia_settings
func encodedData() throws -> Data {
guard let plainData = String(format: PIAHardReset.encodedFormat, cipherName, digestName, caMd5Digest).data(using: .ascii) else {
fatalError("Unable to encode string to ASCII")
}
let keyBytes = try SecureRandom.data(length: PIAHardReset.obfuscationKeyLength)
var encodedData = Data(keyBytes)
for (i, b) in plainData.enumerated() {
let keyChar = keyBytes[i % keyBytes.count]
let xorredB = b ^ keyChar
encodedData.append(xorredB)
}
return encodedData
}
}
}

View File

@ -556,8 +556,22 @@ public class SessionProxy {
keys[negotiationKeyIdx] = newKey
log.debug("Negotiation key index is \(negotiationKeyIdx)")
let payload = hardResetPayload() ?? Data()
negotiationKey.state = .hardReset
enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: Data())
enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload)
}
private func hardResetPayload() -> Data? {
guard !configuration.usesPIAPatches else {
let caMD5 = TLSBox.md5(forCertificatePath: configuration.caPath)
log.debug("CA MD5 is: \(caMD5)")
return try? PIAHardReset(
caMd5Digest: caMD5,
cipher: configuration.cipher,
digest: configuration.digest
).encodedData()
}
return nil
}
// Ruby: soft_reset

View File

@ -51,6 +51,8 @@ extern NSString *const TLSBoxPeerVerificationErrorNotification;
//
@interface TLSBox : NSObject
+ (NSString *)md5ForCertificatePath:(NSString *)path;
- (instancetype)initWithCAPath:(NSString *)caPath
clientCertificatePath:(nullable NSString *)clientCertificatePath
clientKeyPath:(nullable NSString *)clientKeyPath;

View File

@ -75,6 +75,25 @@ int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) {
@implementation TLSBox
+ (NSString *)md5ForCertificatePath:(NSString *)path
{
const EVP_MD *alg = EVP_get_digestbyname("MD5");
uint8_t md[16];
unsigned int len;
FILE *pem = fopen([path cStringUsingEncoding:NSASCIIStringEncoding], "r");
X509 *cert = PEM_read_X509(pem, NULL, NULL, NULL);
X509_digest(cert, alg, md, &len);
X509_free(cert);
NSCAssert2(len == sizeof(md), @"Unexpected MD5 size (%d != %lu)", len, sizeof(md));
NSMutableString *hex = [[NSMutableString alloc] initWithCapacity:2 * sizeof(md)];
for (int i = 0; i < sizeof(md); ++i) {
[hex appendFormat:@"%02x", md[i]];
}
return hex;
}
- (instancetype)init
{
[NSException raise:NSInvalidArgumentException format:@"Use initWithCAPath:clientCertificatePath:clientKeyPath:"];

View File

@ -87,6 +87,14 @@ class EncryptionTests: XCTestCase {
XCTAssertEqual(plain, decrypted)
}
func testCertificateMD5() {
let path = Bundle(for: EncryptionTests.self).path(forResource: "pia-2048", ofType: "pem")!
let md5 = TLSBox.md5(forCertificatePath: path)
let exp = "e2fccccaba712ccc68449b1c56427ac1"
print(md5)
XCTAssertEqual(md5, exp)
}
private func clientServer(_ c: String?, _ d: String?) -> (CryptoBox, CryptoBox) {
let client = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d)
let server = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d)

View File

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1
MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD
L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX
lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp
cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/
8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB
/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC
OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL
y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO
sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM
b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G
A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg
SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz
czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j
b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn
a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU
ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3
7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC
GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz
1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt
YDQ8z9v+DMO6iwyIDRiU
-----END CERTIFICATE-----