mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Support ED25519 and ECDSA keys in the PuTTY format (#660)
* Support ED25519 PuTTY keys. Fix #659 * PuTTYKeyFile: Use net.schmizz.sshj.common.Buffer instead of own KeyReader. A tiny refactoring made in order to allow usage of other utility methods which require Buffer. * Support ECDSA PuTTY keys. * Some code cleanup Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
@@ -16,9 +16,20 @@
|
||||
package net.schmizz.sshj.userauth.keyprovider;
|
||||
|
||||
import com.hierynomus.sshj.common.KeyAlgorithm;
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import net.schmizz.sshj.common.Base64;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
@@ -101,17 +112,17 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
||||
|
||||
protected KeyPair readKeyPair() throws IOException {
|
||||
this.parseKeyPair();
|
||||
final Buffer.PlainBuffer publicKeyReader = new Buffer.PlainBuffer(publicKey);
|
||||
final Buffer.PlainBuffer privateKeyReader = new Buffer.PlainBuffer(privateKey);
|
||||
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
|
||||
if (KeyType.RSA.equals(this.getType())) {
|
||||
final KeyReader publicKeyReader = new KeyReader(publicKey);
|
||||
publicKeyReader.skip(); // skip this
|
||||
// public key exponent
|
||||
BigInteger e = publicKeyReader.readInt();
|
||||
BigInteger e = publicKeyReader.readMPInt();
|
||||
// modulus
|
||||
BigInteger n = publicKeyReader.readInt();
|
||||
BigInteger n = publicKeyReader.readMPInt();
|
||||
|
||||
final KeyReader privateKeyReader = new KeyReader(privateKey);
|
||||
// private key exponent
|
||||
BigInteger d = privateKeyReader.readInt();
|
||||
BigInteger d = privateKeyReader.readMPInt();
|
||||
|
||||
final KeyFactory factory;
|
||||
try {
|
||||
@@ -129,16 +140,13 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
||||
}
|
||||
}
|
||||
if (KeyType.DSA.equals(this.getType())) {
|
||||
final KeyReader publicKeyReader = new KeyReader(publicKey);
|
||||
publicKeyReader.skip(); // skip this
|
||||
BigInteger p = publicKeyReader.readInt();
|
||||
BigInteger q = publicKeyReader.readInt();
|
||||
BigInteger g = publicKeyReader.readInt();
|
||||
BigInteger y = publicKeyReader.readInt();
|
||||
BigInteger p = publicKeyReader.readMPInt();
|
||||
BigInteger q = publicKeyReader.readMPInt();
|
||||
BigInteger g = publicKeyReader.readMPInt();
|
||||
BigInteger y = publicKeyReader.readMPInt();
|
||||
|
||||
final KeyReader privateKeyReader = new KeyReader(privateKey);
|
||||
// Private exponent from the private key
|
||||
BigInteger x = privateKeyReader.readInt();
|
||||
BigInteger x = privateKeyReader.readMPInt();
|
||||
|
||||
final KeyFactory factory;
|
||||
try {
|
||||
@@ -154,9 +162,42 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(String.format("Unknown key type %s", this.getType()));
|
||||
}
|
||||
if (KeyType.ED25519.equals(this.getType())) {
|
||||
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
|
||||
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519);
|
||||
EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519);
|
||||
return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec));
|
||||
}
|
||||
final String ecdsaCurve;
|
||||
switch (this.getType()) {
|
||||
case ECDSA256:
|
||||
ecdsaCurve = "P-256";
|
||||
break;
|
||||
case ECDSA384:
|
||||
ecdsaCurve = "P-384";
|
||||
break;
|
||||
case ECDSA521:
|
||||
ecdsaCurve = "P-521";
|
||||
break;
|
||||
default:
|
||||
ecdsaCurve = null;
|
||||
break;
|
||||
}
|
||||
if (ecdsaCurve != null) {
|
||||
BigInteger s = new BigInteger(1, privateKeyReader.readBytes());
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve);
|
||||
ECNamedCurveSpec ecCurveSpec =
|
||||
new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
|
||||
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
|
||||
try {
|
||||
PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
|
||||
return new KeyPair(getType().readPubKeyFromBuffer(publicKeyReader), privateKey);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
throw new IOException(String.format("Unknown key type %s", this.getType()));
|
||||
}
|
||||
|
||||
protected void parseKeyPair() throws IOException {
|
||||
@@ -297,40 +338,4 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the putty key bit vector, which is an encoded sequence
|
||||
* of {@link java.math.BigInteger}s.
|
||||
*/
|
||||
private final static class KeyReader {
|
||||
private final DataInput di;
|
||||
|
||||
public KeyReader(byte[] key) {
|
||||
this.di = new DataInputStream(new ByteArrayInputStream(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips an integer without reading it.
|
||||
*/
|
||||
public void skip() throws IOException {
|
||||
final int read = di.readInt();
|
||||
if (read != di.skipBytes(read)) {
|
||||
throw new IOException(String.format("Failed to skip %d bytes", read));
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] read() throws IOException {
|
||||
int len = di.readInt();
|
||||
byte[] r = new byte[len];
|
||||
di.readFully(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next integer.
|
||||
*/
|
||||
public BigInteger readInt() throws IOException {
|
||||
return new BigInteger(read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,18 @@
|
||||
*/
|
||||
package net.schmizz.sshj.keyprovider;
|
||||
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@@ -246,6 +250,97 @@ public class PuTTYKeyFileTest {
|
||||
assertNotNull(key.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEd25519() throws Exception {
|
||||
// Generated with
|
||||
// puttygen src/test/resources/keytypes/test_ed25519 -O private \
|
||||
// -o src/test/resources/keytypes/test_ed25519_puttygen.ppk
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen.ppk"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_ed25519"));
|
||||
assertEquals(key.getPrivate(), referenceKey.getPrivate());
|
||||
assertEquals(key.getPublic(), referenceKey.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEd25519Encrypted() throws Exception {
|
||||
// Generated with
|
||||
// puttygen src/test/resources/keytypes/test_ed25519 -O private \
|
||||
// -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \
|
||||
// --new-passphrase <(echo 123456)
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"), new PasswordFinder() {
|
||||
@Override
|
||||
public char[] reqPassword(Resource<?> resource) {
|
||||
return "123456".toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry(Resource<?> resource) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_ed25519"));
|
||||
assertEquals(key.getPrivate(), referenceKey.getPrivate());
|
||||
assertEquals(key.getPublic(), referenceKey.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEcDsa256() throws Exception {
|
||||
// Generated with
|
||||
// puttygen src/test/resources/keytypes/test_ecdsa_nistp256 -O private \
|
||||
// -o src/test/resources/keytypes/test_ecdsa_nistp256_puttygen.ppk
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256_puttygen.ppk"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
|
||||
PKCS8KeyFile referenceKey = new PKCS8KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
|
||||
assertEquals(key.getPrivate(), referenceKey.getPrivate());
|
||||
assertEquals(key.getPublic(), referenceKey.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEcDsa384() throws Exception {
|
||||
// Generated with
|
||||
// puttygen src/test/resources/keytypes/test_ecdsa_nistp384_2 -O private \
|
||||
// -o src/test/resources/keytypes/test_ecdsa_nistp384_2_puttygen.ppk
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384_2_puttygen.ppk"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384_2"));
|
||||
assertEquals(key.getPrivate(), referenceKey.getPrivate());
|
||||
assertEquals(key.getPublic(), referenceKey.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEcDsa521() throws Exception {
|
||||
// Generated with
|
||||
// puttygen src/test/resources/keytypes/test_ecdsa_nistp521_2 -O private \
|
||||
// -o src/test/resources/keytypes/test_ecdsa_nistp521_2_puttygen.ppk
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521_2_puttygen.ppk"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521_2"));
|
||||
assertEquals(key.getPrivate(), referenceKey.getPrivate());
|
||||
assertEquals(key.getPublic(), referenceKey.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectPassphraseRsa() throws Exception {
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
|
||||
10
src/test/resources/keytypes/test_ecdsa_nistp256_puttygen.ppk
Normal file
10
src/test/resources/keytypes/test_ecdsa_nistp256_puttygen.ppk
Normal file
@@ -0,0 +1,10 @@
|
||||
PuTTY-User-Key-File-2: ecdsa-sha2-nistp256
|
||||
Encryption: none
|
||||
Comment: imported-openssh-key
|
||||
Public-Lines: 3
|
||||
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOEQcvowiV3i
|
||||
gdRO7rKPrZrao1hCQrnC4tgsxqSJdQCbABI+vHrdbJRfWZNuSk48aAtARJzJVmkn
|
||||
/r63EPJgkh8=
|
||||
Private-Lines: 1
|
||||
AAAAIQCVDJbEpV6gmZgo5TeJFe4cz/qfabtH8CfK+JtapXufEg==
|
||||
Private-MAC: 48f3a17cf5f65f4f225e7a21f007d8270d7c8c8f
|
||||
10
src/test/resources/keytypes/test_ecdsa_nistp384_2
Normal file
10
src/test/resources/keytypes/test_ecdsa_nistp384_2
Normal file
@@ -0,0 +1,10 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTItEGNGyMGn9tCIM4oC3fpU7jVxDQP
|
||||
RRkB/Qv8lfM4mmSuYLPcakV6av0ATlM6mKD/TObWQNOJAYzp3MsUn1EMgVLe/sd9TY/hP6
|
||||
8Vn+zumMqjmtdX70Ty5ftEoH9zBlgAAADYhfSye4X0snsAAAATZWNkc2Etc2hhMi1uaXN0
|
||||
cDM4NAAAAAhuaXN0cDM4NAAAAGEEyLRBjRsjBp/bQiDOKAt36VO41cQ0D0UZAf0L/JXzOJ
|
||||
pkrmCz3GpFemr9AE5TOpig/0zm1kDTiQGM6dzLFJ9RDIFS3v7HfU2P4T+vFZ/s7pjKo5rX
|
||||
V+9E8uX7RKB/cwZYAAAAMGvH38HMnj6cELCBVQnAQYHlA/Vz1+RVZHj08cey/P3PALx7MR
|
||||
pV135UZNZAtWQm+wAAAAlyb290QHNzaGoBAgMEBQYH
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
src/test/resources/keytypes/test_ecdsa_nistp384_2.pub
Normal file
1
src/test/resources/keytypes/test_ecdsa_nistp384_2.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMi0QY0bIwaf20IgzigLd+lTuNXENA9FGQH9C/yV8ziaZK5gs9xqRXpq/QBOUzqYoP9M5tZA04kBjOncyxSfUQyBUt7+x31Nj+E/rxWf7O6YyqOa11fvRPLl+0Sgf3MGWA== root@sshj
|
||||
@@ -0,0 +1,11 @@
|
||||
PuTTY-User-Key-File-2: ecdsa-sha2-nistp384
|
||||
Encryption: none
|
||||
Comment: root@sshj
|
||||
Public-Lines: 3
|
||||
AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMi0QY0bIwaf
|
||||
20IgzigLd+lTuNXENA9FGQH9C/yV8ziaZK5gs9xqRXpq/QBOUzqYoP9M5tZA04kB
|
||||
jOncyxSfUQyBUt7+x31Nj+E/rxWf7O6YyqOa11fvRPLl+0Sgf3MGWA==
|
||||
Private-Lines: 2
|
||||
AAAAMGvH38HMnj6cELCBVQnAQYHlA/Vz1+RVZHj08cey/P3PALx7MRpV135UZNZA
|
||||
tWQm+w==
|
||||
Private-MAC: aa4d48441934e15491af0a30f75a02f4e324e652
|
||||
12
src/test/resources/keytypes/test_ecdsa_nistp521_2
Normal file
12
src/test/resources/keytypes/test_ecdsa_nistp521_2
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA3ilD2XkhjkSuEj8KcIXWjhjKSOfQ
|
||||
QEZBFZyoPT4QV8oRiGT1NRVcN86Paymq8M8WgANFVEAZp7eDqTnsKJ6LEpoAM93DJa1ERO
|
||||
RWwSeDTDy5GIxMDYgg+CKZVhAMJmS/iavsSXyKUf1ibYo9b5S8y8rpzvmiRg/dQGkfloJR
|
||||
BLu7czAAAAEI8uaocPLmqHAAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
|
||||
AAAIUEAN4pQ9l5IY5ErhI/CnCF1o4Yykjn0EBGQRWcqD0+EFfKEYhk9TUVXDfOj2spqvDP
|
||||
FoADRVRAGae3g6k57CieixKaADPdwyWtRETkVsEng0w8uRiMTA2IIPgimVYQDCZkv4mr7E
|
||||
l8ilH9Ym2KPW+UvMvK6c75okYP3UBpH5aCUQS7u3MwAAAAQSlrwjeSrVTc6OyiA3OTfac4
|
||||
+3nKcf/PRSjIhOLsGUIs2pVCxGYP8/ZfbVfkv7nHMn5Cc0fDZEs2cSWi2QhVKBSfAAAACX
|
||||
Jvb3RAc3NoagEC
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
src/test/resources/keytypes/test_ecdsa_nistp521_2.pub
Normal file
1
src/test/resources/keytypes/test_ecdsa_nistp521_2.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADeKUPZeSGORK4SPwpwhdaOGMpI59BARkEVnKg9PhBXyhGIZPU1FVw3zo9rKarwzxaAA0VUQBmnt4OpOewonosSmgAz3cMlrURE5FbBJ4NMPLkYjEwNiCD4IplWEAwmZL+Jq+xJfIpR/WJtij1vlLzLyunO+aJGD91AaR+WglEEu7tzMA== root@sshj
|
||||
@@ -0,0 +1,12 @@
|
||||
PuTTY-User-Key-File-2: ecdsa-sha2-nistp521
|
||||
Encryption: none
|
||||
Comment: root@sshj
|
||||
Public-Lines: 4
|
||||
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADeKUPZeSGO
|
||||
RK4SPwpwhdaOGMpI59BARkEVnKg9PhBXyhGIZPU1FVw3zo9rKarwzxaAA0VUQBmn
|
||||
t4OpOewonosSmgAz3cMlrURE5FbBJ4NMPLkYjEwNiCD4IplWEAwmZL+Jq+xJfIpR
|
||||
/WJtij1vlLzLyunO+aJGD91AaR+WglEEu7tzMA==
|
||||
Private-Lines: 2
|
||||
AAAAQSlrwjeSrVTc6OyiA3OTfac4+3nKcf/PRSjIhOLsGUIs2pVCxGYP8/ZfbVfk
|
||||
v7nHMn5Cc0fDZEs2cSWi2QhVKBSf
|
||||
Private-MAC: 052d1a2fe2c5837aec9dbe0bf10f2ccc376eda43
|
||||
9
src/test/resources/keytypes/test_ed25519_puttygen.ppk
Normal file
9
src/test/resources/keytypes/test_ed25519_puttygen.ppk
Normal file
@@ -0,0 +1,9 @@
|
||||
PuTTY-User-Key-File-2: ssh-ed25519
|
||||
Encryption: none
|
||||
Comment: root@sshj
|
||||
Public-Lines: 2
|
||||
AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPo
|
||||
WICJ
|
||||
Private-Lines: 1
|
||||
AAAAIKaxyRDJxad8ZArpe1ClowY4NsCQxA50k0rpclKKkHt0
|
||||
Private-MAC: 388f807649f181243015cad9650633ec28b25208
|
||||
@@ -0,0 +1,9 @@
|
||||
PuTTY-User-Key-File-2: ssh-ed25519
|
||||
Encryption: aes256-cbc
|
||||
Comment: root@sshj
|
||||
Public-Lines: 2
|
||||
AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPo
|
||||
WICJ
|
||||
Private-Lines: 1
|
||||
XFJyRzRt5NjuCVhDEyb50sI+gRn8FB65hh0U8uhGvP3VBl4haChinQasOTBYa4pj
|
||||
Private-MAC: 80f50e1a7075567980742644460edffeb67ca829
|
||||
Reference in New Issue
Block a user