Add tests and refactor session management

This commit is contained in:
netkas 2024-10-24 14:06:39 -04:00
parent d4eb083d77
commit 1d0cd21965
16 changed files with 447 additions and 92 deletions

View file

@ -1,4 +1,79 @@
package net.nosial.socialclient;
public class Client {
import net.nosial.socialclient.abstracts.RpcResult;
import net.nosial.socialclient.classes.Cryptography;
import net.nosial.socialclient.classes.RpcClient;
import net.nosial.socialclient.classes.Utilities;
import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
import net.nosial.socialclient.exceptions.RpcException;
import net.nosial.socialclient.objects.RpcRequest;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
public class Client extends RpcClient
{
protected Client(String domain) throws ResolutionException, CryptographyException
{
super(domain);
}
public Client(String endpoint, PublicKey serverPublicKey)
{
super(endpoint, serverPublicKey);
}
public String createSession(KeyPair keyPair) throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("create_session", Utilities.randomCrc32(), new HashMap<>(){{
put("public_key", Cryptography.exportPublicKey(keyPair));
}}));
if(response == null)
{
throw new RpcException("Response is null");
}
if(!response.isSuccess())
{
throw new RpcException(response);
}
return (String) response.getResponse().getResult();
}
public void setSession(String sessionUuid, PrivateKey privateKey)
{
this.sessionUuid = sessionUuid;
this.privateKey = privateKey;
}
public String createAndSetSession(KeyPair keyPair) throws RpcException
{
String sessionUuid = this.createSession(keyPair);
this.setSession(sessionUuid, keyPair.getPrivate());
return sessionUuid;
}
public boolean ping() throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("ping", Utilities.randomCrc32()));
if(response == null)
{
throw new RpcException("Response is null");
}
if(!response.isSuccess())
{
throw new RpcException(response);
}
return true;
}
}

View file

@ -7,6 +7,7 @@ import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
@ -79,15 +80,15 @@ public final class Cryptography
*/
public static String signContent(String content, PrivateKey privateKey) throws CryptographyException
{
// Convert content to sha1 hash
try
{
final Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(MessageDigest.getInstance("SHA-1").digest(content.getBytes()));
signature.update(sha1(content).getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = signature.sign();
return Base64.getEncoder().encodeToString(signature.sign());
// Base64 encode the signature
return Base64.getEncoder().encodeToString(signatureBytes);
}
catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e)
{
@ -95,6 +96,22 @@ public final class Cryptography
}
}
private static String bytesToHex(byte[] hash)
{
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash)
{
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1)
{
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* Verifies the content using the provided signature and public key.
*
@ -123,9 +140,8 @@ public final class Cryptography
try
{
final Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
sign.initVerify(publicKey);
sign.update(MessageDigest.getInstance("SHA-1").digest(content.getBytes()));
sign.update(sha1(content).getBytes(StandardCharsets.UTF_8));
return sign.verify(Base64.getDecoder().decode(signature));
}
@ -372,7 +388,7 @@ public final class Cryptography
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
return getKeyFactory().generatePublic(spec);
}
catch (InvalidKeySpecException | NoSuchAlgorithmException e)
catch (InvalidKeySpecException | NoSuchAlgorithmException | IllegalArgumentException e)
{
throw new CryptographyException("Failed to import public key", e);
}
@ -399,4 +415,11 @@ public final class Cryptography
}
}
public static String sha1(String data) throws NoSuchAlgorithmException
{
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
}
}

View file

@ -6,89 +6,54 @@ import net.nosial.socialclient.objects.ResolvedServer;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// Improved Resolver class
public class Resolver
{
/**
* Resolves the domain to obtain endpoint and public key from its DNS TXT records.
*
* @param domain The domain to be resolved.
* @return ResolvedServer An instance of ResolvedServer containing the endpoint and public key.
* @throws ResolutionException If the DNS TXT records cannot be resolved or if required information is missing.
*/
public static ResolvedServer resolveDomain(String domain) throws ResolutionException
{
final String endpointPrefix = "socialbox=";
final String keyPrefix = "socialbox-key=";
public class Resolver {
public static ResolvedServer resolveDomain(String domain) throws ResolutionException {
final Pattern fullPattern = Pattern.compile("v=socialbox;sb-rpc=(https?://[^;]+);sb-key=([^;]+)");
// Disable caching
Hashtable<String, String> env = new Hashtable<>();
env.put("com.sun.jndi.dns.timeout.retries", "1");
env.put("com.sun.jndi.dns.cache.ttl", "0");
env.put("com.sun.jndi.dns.cache.ttl", "1");
env.put("com.sun.jndi.dns.cache.negative.ttl", "0");
try
{
try {
DirContext dirContext = new InitialDirContext(env);
Attributes attributes = dirContext.getAttributes("dns:/" + domain, new String[]{"TXT"});
Attribute attributeTXT = attributes.get("TXT");
if (attributeTXT == null)
{
if (attributeTXT == null) {
throw new ResolutionException("Failed to resolve DNS TXT records for " + domain);
}
String endpoint = null;
StringBuilder publicKeyBuilder = new StringBuilder();
boolean publicKeyFound = false;
StringBuilder fullRecordBuilder = new StringBuilder();
for (int i = 0; i < attributeTXT.size(); i++)
{
for (int i = 0; i < attributeTXT.size(); i++) {
String value = (String) attributeTXT.get(i);
fullRecordBuilder.append(value.replaceAll("^\"|\"$", "").trim());
}
// Remove surrounding quotes if present
if (value.startsWith("\"") && value.endsWith("\""))
{
value = value.substring(1, value.length() - 1);
String fullRecord = fullRecordBuilder.toString();
Matcher matcher = fullPattern.matcher(fullRecord);
if (matcher.find()) {
String endpoint = matcher.group(1);
String publicKey = matcher.group(2).replaceAll("\\s+", "");
if (endpoint == null || endpoint.isEmpty()) {
throw new ResolutionException("Failed to resolve RPC endpoint for " + domain);
}
// Split the value into fragments to find the relevant keys
String[] fragments = value.split("\\s+");
for (String fragment : fragments)
{
if (fragment.startsWith(endpointPrefix))
{
endpoint = fragment.substring(endpointPrefix.length());
}
else if (fragment.startsWith(keyPrefix))
{
publicKeyBuilder.append(fragment.substring(keyPrefix.length()));
publicKeyFound = true;
}
else if (publicKeyFound)
{
// If the public key has already started, append the fragment
publicKeyBuilder.append(fragment);
}
if (publicKey == null || publicKey.isEmpty()) {
throw new ResolutionException("Failed to resolve public key for " + domain);
}
}
if (endpoint == null)
{
throw new ResolutionException("Failed to resolve RPC endpoint for " + domain);
return new ResolvedServer(endpoint, publicKey);
} else {
throw new ResolutionException("Failed to find valid SocialBox record for " + domain);
}
if (publicKeyBuilder.isEmpty())
{
throw new ResolutionException("Failed to resolve public key for " + domain);
}
String publicKey = publicKeyBuilder.toString();
return new ResolvedServer(endpoint, publicKey);
}
catch (NamingException e)
{
} catch (NamingException e) {
throw new ResolutionException("Error resolving domain: " + e.getMessage(), e);
}
}

View file

@ -12,6 +12,7 @@ import net.nosial.socialclient.objects.ResolvedServer;
import net.nosial.socialclient.objects.RpcRequest;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
@ -30,8 +31,8 @@ public class RpcClient
private final PublicKey serverPublicKey;
private final OkHttpClient httpClient;
private String sessionUuid;
private PrivateKey privateKey;
protected String sessionUuid;
protected PrivateKey privateKey;
/**
* Initializes a new instance of the RpcClient class using the specified domain.
@ -118,7 +119,7 @@ public class RpcClient
* @param privateKey the private key to be imported
* @throws CryptographyException if an error occurs while importing the private key
*/
public void setSession(String sessionUuid, String privateKey) throws CryptographyException
protected void setSession(String sessionUuid, String privateKey) throws CryptographyException
{
this.sessionUuid = sessionUuid;
this.privateKey = Cryptography.importPrivateKey(privateKey);
@ -130,7 +131,7 @@ public class RpcClient
* @param sessionUuid the UUID of the session
* @param privateKey the private key associated with the session
*/
public void setSession(String sessionUuid, PrivateKey privateKey)
protected void setSession(String sessionUuid, PrivateKey privateKey)
{
this.sessionUuid = sessionUuid;
this.privateKey = privateKey;
@ -165,6 +166,7 @@ public class RpcClient
if(sessionUuid != null)
{
// We are seeing the session UUID correctly.
this.addHeader("Session-UUID", sessionUuid);
}
@ -172,9 +174,12 @@ public class RpcClient
{
try
{
// NOTE: Signature looks okay, it's base64 encoded.
System.out.println("Hash: " + Cryptography.sha1(jsonData));
System.out.println("Signature: " + Cryptography.signContent(jsonData, privateKey));
this.addHeader("Signature", Cryptography.signContent(jsonData, privateKey));
}
catch(CryptographyException e)
catch(CryptographyException | NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
@ -193,7 +198,7 @@ public class RpcClient
throw new RuntimeException(responseString);
}
throw new RuntimeException("Failed to send request");
throw new RuntimeException("Failed to send request: " + response.code());
}
if(response.code() == 204)
@ -242,7 +247,14 @@ public class RpcClient
*/
public RpcResult sendRequest(RpcRequest request)
{
return sendRequest(encode(request.toMap())).getFirst();
List<RpcResult> results = sendRequest(encode(request.toMap()));
if(results.isEmpty())
{
return null;
}
return results.getFirst();
}
/**

View file

@ -0,0 +1,44 @@
package net.nosial.socialclient.classes;
import java.util.concurrent.ThreadLocalRandom;
public class Utilities
{
private static final int[] CRC32_TABLE = new int[256];
static
{
for (int i = 0; i < 256; i++)
{
int crc = i;
for (int j = 8; j > 0; j--)
{
if ((crc & 1) == 1)
{
crc = (crc >>> 1) ^ 0xEDB88320;
}
else
{
crc = crc >>> 1;
}
}
CRC32_TABLE[i] = crc;
}
}
public static String randomCrc32()
{
long randomValue = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE);
String valueString = Long.toString(randomValue);
int crc = 0xFFFFFFFF;
for (char c : valueString.toCharArray())
{
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ c) & 0xFF];
}
crc = ~crc;
return Integer.toHexString(crc);
}
}

View file

@ -1,6 +1,21 @@
package net.nosial.socialclient.exceptions;
import net.nosial.socialclient.abstracts.RpcResult;
public class RpcException extends Exception
{
public RpcException(String message)
{
super(message);
}
public RpcException(String message, Throwable cause)
{
super(message, cause);
}
public RpcException(RpcResult result)
{
super(result.getErrorResponse().getError());
}
}

View file

@ -7,6 +7,11 @@ public class ResolvedServer
public ResolvedServer(String endpoint, String publicKey)
{
if(endpoint == null || publicKey == null)
{
throw new IllegalArgumentException("Endpoint and public key must not be null");
}
this.endpoint = endpoint;
this.publicKey = publicKey;
}

View file

@ -7,7 +7,7 @@ public class RpcRequest
{
private final String method;
private final String id;
private final Map<String, Object> params;
private final Map<String, Object> parameters;
/**
* Initializes a new RpcRequest object using the provided data map.
@ -23,7 +23,7 @@ public class RpcRequest
{
this.method = (String) data.get("method");
this.id = (String) data.get("id");
this.params = (Map<String, Object>) data.get("params");
this.parameters = (Map<String, Object>) data.get("params");
}
/**
@ -31,13 +31,13 @@ public class RpcRequest
*
* @param method the RPC method to be called
* @param id the unique identifier for this request
* @param params the parameters for the RPC method
* @param parameters the parameters for the RPC method
*/
public RpcRequest(String method, String id, Map<String, Object> params)
public RpcRequest(String method, String id, Map<String, Object> parameters)
{
this.method = method;
this.id = id;
this.params = params;
this.parameters = parameters;
}
/**
@ -45,13 +45,13 @@ public class RpcRequest
* The request ID will be set to null.
*
* @param method the name of the method being called
* @param params the parameters to be sent with the method call
* @param parameters the parameters to be sent with the method call
*/
public RpcRequest(String method, Map<String, Object> params)
public RpcRequest(String method, Map<String, Object> parameters)
{
this.id = null;
this.method = method;
this.params = params;
this.parameters = parameters;
}
/**
@ -64,7 +64,7 @@ public class RpcRequest
{
this.method = method;
this.id = id;
this.params = null;
this.parameters = null;
}
/**
@ -76,7 +76,7 @@ public class RpcRequest
{
this.id = null;
this.method = method;
this.params = null;
this.parameters = null;
}
/**
@ -104,9 +104,9 @@ public class RpcRequest
*
* @return a Map containing the parameters of the RPC request.
*/
public Map<String, Object> getParams()
public Map<String, Object> getParameters()
{
return this.params;
return this.parameters;
}
/**
@ -124,9 +124,9 @@ public class RpcRequest
map.put("id", this.id);
}
if(this.params != null)
if(this.parameters != null)
{
map.put("params", this.params);
map.put("parameters", this.parameters);
}
return map;

View file

@ -0,0 +1,89 @@
package net.nosial.socialclient;
import net.nosial.socialclient.classes.Cryptography;
import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
import net.nosial.socialclient.exceptions.RpcException;
import org.junit.jupiter.api.Test;
import java.security.KeyPair;
import static org.junit.jupiter.api.Assertions.*;
class SessionTest
{
@Test
void createSessionTest()
{
Client socialClient;
KeyPair keyPair;
String sessionUuid;
try
{
socialClient = new Client("n64.cc");
}
catch (ResolutionException | CryptographyException e)
{
fail(e.getMessage(), e);
return;
}
try
{
keyPair = Cryptography.generateKeyPair();
}
catch (CryptographyException e)
{
fail(e.getMessage(), e);
return;
}
try
{
sessionUuid = socialClient.createSession(keyPair);
}
catch (RpcException e)
{
fail(e.getMessage(), e);
return;
}
assertNotNull(sessionUuid);
System.out.println("Session UUID: " + sessionUuid);
}
@Test
public void pingSignedTest()
{
final Client socialClient;
final KeyPair keyPair;
final String sessionUuid;
try
{
socialClient = new Client("n64.cc");
keyPair = Cryptography.generateKeyPair();
sessionUuid = socialClient.createAndSetSession(keyPair);
}
catch (ResolutionException | CryptographyException | RpcException e)
{
fail(e.getMessage(), e);
return;
}
System.out.println("Session UUID: " + sessionUuid);
assertNotNull(sessionUuid);
try
{
assertTrue(socialClient.ping());
}
catch (RpcException e)
{
fail(e.getMessage(), e);
}
}
}

View file

@ -4,8 +4,16 @@ import net.nosial.socialclient.exceptions.CryptographyException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collections;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.*;
@ -259,4 +267,78 @@ class CryptographyTest
// Assert
assertThrows(CryptographyException.class, () -> Cryptography.encrypt(content, invalidPublicKey), "Encrypt should throw CryptographyException when public key is invalid");
}
@Test
public void clientSignTest() {
ClassLoader classLoader = getClass().getClassLoader();
PrivateKey privateKey;
PublicKey publicKey;
String content;
try
{
privateKey = Cryptography.importPrivateKey(Files.readString(new File(Objects.requireNonNull(classLoader.getResource("client_private.der")).getFile()).toPath()));
publicKey = Cryptography.importPublicKey(Files.readString(new File(Objects.requireNonNull(classLoader.getResource("client_public.der")).getFile()).toPath()));
content = Files.readString(new File(Objects.requireNonNull(classLoader.getResource("content.txt")).getFile()).toPath());
}
catch(CryptographyException | IOException e)
{
fail(e.getMessage(), e);
return;
}
assertNotNull(privateKey, "Private key should not be null");
assertNotNull(publicKey, "Public key should not be null");
assertNotNull(content, "Content should not be null");
// Hash the content to sha1 string
String sha1Content;
try
{
sha1Content = Cryptography.sha1(content);
}
catch (NoSuchAlgorithmException e)
{
fail(e.getMessage(), e);
return;
}
assertNotNull(sha1Content, "SHA1 content should not be null");
assertEquals(40, sha1Content.length(), "SHA1 content should be 40 characters length");
assertEquals("fa2415f0735a8aa151195688852178e8fd6e77c5", sha1Content, "SHA1 content should be equal to expected value");
System.out.println("SHA1 content: " + sha1Content);
// Signature testing
String signature;
try
{
signature = Cryptography.signContent(content, privateKey);
}
catch (CryptographyException e)
{
fail(e.getMessage(), e);
return;
}
assertNotNull(signature, "Signature should not be null");
assertEquals("Gcnijq7V8AYXgdk/eP9IswXN7831FevlBNDTKN60Ku7xesPDuPX8e55+38WFGCQ87DbeiIr+61XIDoN4+bTM4Wl0YSUe7oHV9BBnBqGhyZTntDPedUYUomrF3IRcpVRK0SbQSRaYucIp/ZsSHdbQgQBtDCvH5pK1+5g+VK9ZFT16Isvk0PhMjZiLkUYxUklFuzak7agWiS3wllFPqYSM6ri0RF+5I5JbnR9fUAOfhOceax//5H7d2WsdLj6DwtuY+eL5WyHxSmGA04YeQF3JgOGJ3WX2DSH8L0zA7pkGOjz5y1Nu6+0U6KRUXcezU/iM4zy5OJOnD5eJH4pYZizkiA==", signature, "Signature should be equal to expected value");
System.out.println("Signature: " + signature);
// Verify the signature
boolean verified;
try
{
verified = Cryptography.verifyContent(content, signature, publicKey);
}
catch (CryptographyException e)
{
fail(e.getMessage(), e);
return;
}
assertTrue(verified, "Signature should be verified successfully");
}
}

View file

@ -0,0 +1,37 @@
package net.nosial.socialclient.classes;
import net.nosial.socialclient.classes.Utilities;
import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
public class UtilitiesTest {
@Test
public void testRandomCrc32_formatValidity() {
String crcOutput = Utilities.randomCrc32();
assertNotNull(crcOutput);
assertTrue(crcOutput.matches("[a-f0-9]+"), "Expected hexadecimal output");
}
@Test
public void testRandomCrc32_lengthValidity() {
String crcOutput = Utilities.randomCrc32();
assertEquals(8, crcOutput.length(), "Expected 8 characters length");
}
@Test
public void testRandomCrc32_randomness() {
Set<String> randomResults = new HashSet<>();
for (int i = 0; i < 10_000; i++) {
randomResults.add(Utilities.randomCrc32());
}
assertTrue(randomResults.size() > 1, "Expected more than one different results");
}
}

View file

@ -0,0 +1 @@
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDZb4K+28kKYe1CvHPWHJALJFS396HOFmBv+anpAVWMDGBUyAWbWEqxmTAV17cBHiICjDDCNFBpOZLWzIiUpdWKA0Jo+Vu9zgWSPUyGe/Lik4GFNZ38gfolfdKGnLNFnn4nFR/fsZQ7hg4wWDarJmhJ+ZSLShOz2uIb4LaKk2qy12c6Zepufgrbk9TwWZQiXkzqBWbrZDpw0pp50CzoIwEnYJ+a7vhb98jpeS+Jjnp5zWlFjv9RgzOQUOwwOK4We2gNAVeFC5BP9trklpTh1bJlit4CECH68fCGjgoTOU92UbgucgyA4O5FVPGQYPAMuiZMGFaqXE2E7z1XwYIMAL4VAgMBAAECggEAAKiJz3CYuO+gGnL+F7qjaSXCUE8VvPfoCwuNYHNEFXo9DJBmnL7EU2WrYG+wARCP7O7qd0dEidx9u36ytjyCcKT4nYni8lM1zU7rVvbnLbsuRZS/4RO/RaYfPxig94fDfSeJ2ma0i7G56onj+MBbyTZarZ7Bf8hpcmKg9pkNEcEVcklNIwwbXKBOGq75Vka/+W56JZKJD3G9YmfrAO5RGF1prh93MRXlxlN/91k/m2pqkN9xYofepn0ePmI8Ci18jrMpJbmeu8BkypzgvC/5EfHipn7y/yJ215o/EtB575muz2zngRXe+GVO5lB5d5PuEwmXoaV5o3BqkIcb3aiz4QKBgQD7P1AE2/3oATNUF1FwlXzvdCS7M2BB28jQWjzJvHus1d1+qA2StWPgCPG2D/YTtHPI3xefBnAmeSIFCFEub0YLONbRvtQAZdTt5SAaZuUyMprqD1sCUHCizyVO0wHxo3DS0sIFmo/Lpc+jnYHn3KcuRPRJk3ncZNCQhy9a/rrnxQKBgQDdjHY82YdkWQWj/xM1EuVtkVVeCJWJ6tSDn+Uq8d+hXILFAQ47GOUbzj4Ty4qGgsAgsaAGqja5t6CE+fYs8Q34FsxTsYgIRm0VXqtPm4aYTQ4PwKbmMPEOgEsXBywe5Y+QB0u/WuNyhgwgYP5cy1IS3HA1HmbTisi0zLEfkVWSEQKBgCuP36zoA88NHjwvStSNZrsR1SiMEN16YQgXDUEhKARglGXYd3n/b1Cx3E7n14+1Evo6DBtrf1h8WjSrK4A0lN1vPnfhcVqcTV3uAzHwsz6P3aJFhU8SaWUhK2POXCDsaKx1FGTqVpJFrom8zoBIFsiD9iMnqdJXvH3CoqhRUFDNAoGAEJdwU2ZHCXDRR1LW8WaU3/u+VOh3qnh3qdPTqb+ra74t3OsTUcGvhsGPTJQ1r5UjJk+nGFiu+IGT9+FwWjVDQo0SiEIHWfdMPAl28uNG1SkQIIXg+eQ4aUmaVgMnfrjaY4LoXVBFMFJxngslgXWIk/kGPjQkpzsBhOi/awnLSsECgYEAkSEb3CXfq1r/8qXMTzI+A9CGPr++aC2H6ytFNGJq4J+P40u1tcsfkwNGcQ0Hp+Qz3FHBYFuMxtjXDq0QSvVKEhdV9bjlZhTqN3lqWcCukU3ESqRbxsIj9izuncpxSP7G19WEU0anGD9ev+QWYdHPTBY9nn1+H0tkJjqh4XkRBuY=

View file

@ -0,0 +1 @@
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2W+CvtvJCmHtQrxz1hyQCyRUt/ehzhZgb/mp6QFVjAxgVMgFm1hKsZkwFde3AR4iAowwwjRQaTmS1syIlKXVigNCaPlbvc4Fkj1Mhnvy4pOBhTWd/IH6JX3ShpyzRZ5+JxUf37GUO4YOMFg2qyZoSfmUi0oTs9riG+C2ipNqstdnOmXqbn4K25PU8FmUIl5M6gVm62Q6cNKaedAs6CMBJ2Cfmu74W/fI6XkviY56ec1pRY7/UYMzkFDsMDiuFntoDQFXhQuQT/ba5JaU4dWyZYreAhAh+vHwho4KEzlPdlG4LnIMgODuRVTxkGDwDLomTBhWqlxNhO89V8GCDAC+FQIDAQAB

View file

@ -0,0 +1,4 @@
{
"method" : "ping",
"id" : "daa31852"
}

View file

@ -0,0 +1 @@
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDxcYLVvKYHpxJQe49Z7boilJfmp/uYAr4rQNN2El9nPG3hVRYIsmhzIeByU7ZJLn0EG5D2G6T4SlydfbHbHK3NMciTgmyAZRJl10z/KIlPG1n6DaK5Oo6VT9ty+uI7JKFdQQwzSrPP2u4KNERqK4vtfsxdiAcMXS/hUncID4ARvmigmcTOTcosdH57axSqf1xfJJ12zl3QPb6wppsBAJrZ811Ll8eZYhFoQwe0oE3T5Q0aqK0Ecgh4cYhF63nEAKkcPOMxkhcZWpr3YzqD7Rj9VAzk7xRM/QJ1SjsVwdB+YCQK1tbTloTY3BqtEib9ErZvjlaB26GZbV32kNsPnwHXAgMBAAECggEAJBVtUskzXRBsjcexmGSNfW6MtyWi1ciPKEKzd8FuLa0b1OHU/a7AKnjFJQD6zLwcZflCtG1UPeFLLyRiaNdD6FdI3TbQRW4Vjk/bi4TA5Kg3TcYs7BbiyVDagLgbCHDEhv3aN24yKl3TVoYSNXXVn0RkgZP7Ta89oSSkcnlyj/QFOA8RfIm5q+qiAPvOqFf8NKlm0hZDrxWHG/OduYHq25S9ohNzymyM+1CYTrVFZCTfscDvLBDd2MVpNRyxoQquiMlfIEUBGlu9uFWy9Hovv8Sd3irgvcBtjL8iPaMzJe3p6T83KL2AgXHcYT7r9Vlvqib5x1iTYvlid25zzQ19IQKBgQD1BISfPugEp+fAdoGHOygG+gzNE8/1ldhnA9bTCZZ3FQBTI2lPRZBFDKuirc6glbCHiWrd8HoJ3BO3kbGzq4EDBf0VDFby/7nkrroTW+RIn+THlfciWgjSATGgCPOHmvM6JmIpuYsbKkdmV4ITVWwvLPxDAwlMnHyJOuYTj8xJ4QKBgQD8Q/rdWoRBSVCrDb0QO/Or5FAJELmYFtFWBBCEpadr9ci0e/mSbbZlXjP98m4XesIIRpcpG3gU8P3hKB7H60ynPN6Jyw33YhIlJHaEjYISN/h5Vw0ybQkyFR9CBRjOp59CBcb8AsdA/OQjxFz8h46PbPLCWCR5kM3tKbuNobBytwKBgQC4Rr+gLWW/KrEolXhxxtIh/SpniwEbSanKQJ7vdgSOZ2MpJDbuAfmxlQf5gBMpv6tXJMkVRuniRH0n0RH/eXu8VGK10+QJOsAK+EbGjJQy8t7UJTwLv/9mQrOaE2FlmepYz8mAbCXtNm0g0avo8pQ9Hu5TUBNMZV1csMmd6MbSwQKBgQDit+X6kqNSWaXaVdqZgIga8HLN8u4aNkelWrnNvWOer6LWMqW2aEwJBoULsponF/jSnz6zfzCJAZ3qgbhITLzzgM0wYgIHV2ifYQnzT4qa/RqfUxFVRJGDJWCWYSZOdG+5Up/nVkflrGMNkilP/DSvymbTK4x8hRvODje1rp96OQKBgFmXLpHPN8WAXP7VVyb3RqYYRgtxXjY2yj/CYwnXl0k4Uji08S9Ke2AqljiSzmZs1Wh1UBLap90F0smRVHmYgwl2rPjNiXbyKd4W9R4vEVYgEmcnvzba107o76qFmEbyW/K7a7K/jKaH8KAytgR/cHd+SIBctcDv8uKmZ1MJT9p8

View file

@ -0,0 +1 @@
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8XGC1bymB6cSUHuPWe26IpSX5qf7mAK+K0DTdhJfZzxt4VUWCLJocyHgclO2SS59BBuQ9huk+EpcnX2x2xytzTHIk4JsgGUSZddM/yiJTxtZ+g2iuTqOlU/bcvriOyShXUEMM0qzz9ruCjREaiuL7X7MXYgHDF0v4VJ3CA+AEb5ooJnEzk3KLHR+e2sUqn9cXySdds5d0D2+sKabAQCa2fNdS5fHmWIRaEMHtKBN0+UNGqitBHIIeHGIRet5xACpHDzjMZIXGVqa92M6g+0Y/VQM5O8UTP0CdUo7FcHQfmAkCtbW05aE2NwarRIm/RK2b45WgduhmW1d9pDbD58B1wIDAQAB