mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-08 16:18:05 +03:00
* Add support for authentication with DSA & RSA user certificates (#153) Updates: - KeyType.java - add support for two certificate key types ssh-rsa-cert-v01@openssh.com ssh-dsa-cert-v01@openssh.com - Buffer.java - allow uint64s that overflow Long.MAX_VALUE, otherwise we break on certificates with serial numbers greater Long.MAX_VALUE - OpenSSHKeyFile, KeyProviderUtil - prefer public key files that end "-cert.pub" if they exist Added new class Certificate, which represents certificate key Reference: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD * Use BigInteger for certificate serial numbers, address Codacy issues * Address code review concerns
This commit is contained in:
@@ -0,0 +1,258 @@
|
|||||||
|
package com.hierynomus.sshj.userauth.certificate;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C)2009 - SSHJ Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Certificate wrapper for public keys, created to help implement
|
||||||
|
* protocol described here:
|
||||||
|
*
|
||||||
|
* https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
|
||||||
|
*
|
||||||
|
* Consumed primarily by net.shmizz.sshj.common.KeyType
|
||||||
|
*
|
||||||
|
* @param <T> inner public key type
|
||||||
|
*/
|
||||||
|
public class Certificate<T extends PublicKey> implements PublicKey {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final T publicKey;
|
||||||
|
private final byte[] nonce;
|
||||||
|
private final BigInteger serial;
|
||||||
|
private final long type;
|
||||||
|
private final String id;
|
||||||
|
private final List<String> validPrincipals;
|
||||||
|
private final Date validAfter;
|
||||||
|
private final Date validBefore;
|
||||||
|
private final Map<String, String> critOptions;
|
||||||
|
private final Map<String, String> extensions;
|
||||||
|
private final byte[] signatureKey;
|
||||||
|
private final byte[] signature;
|
||||||
|
|
||||||
|
Certificate(Builder<T> builder) {
|
||||||
|
this.publicKey = builder.getPublicKey();
|
||||||
|
this.nonce = builder.getNonce();
|
||||||
|
this.serial = builder.getSerial();
|
||||||
|
this.type = builder.getType();
|
||||||
|
this.id = builder.getId();
|
||||||
|
this.validPrincipals = builder.getValidPrincipals();
|
||||||
|
this.validAfter = builder.getValidAfter();
|
||||||
|
this.validBefore = builder.getValidBefore();
|
||||||
|
this.critOptions = builder.getCritOptions();
|
||||||
|
this.extensions = builder.getExtensions();
|
||||||
|
this.signatureKey = builder.getSignatureKey();
|
||||||
|
this.signature = builder.getSignature();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <P extends PublicKey> Builder<P> getBuilder() {
|
||||||
|
return new Builder<P>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getSerial() {
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getValidPrincipals() {
|
||||||
|
return validPrincipals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getValidAfter() {
|
||||||
|
return validAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getValidBefore() {
|
||||||
|
return validBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getCritOptions() {
|
||||||
|
return critOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getExtensions() {
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignatureKey() {
|
||||||
|
return signatureKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
return publicKey.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return publicKey.getAlgorithm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFormat() {
|
||||||
|
return publicKey.getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder<T extends PublicKey> {
|
||||||
|
private T publicKey;
|
||||||
|
private byte[] nonce;
|
||||||
|
private BigInteger serial;
|
||||||
|
private long type;
|
||||||
|
private String id;
|
||||||
|
private List<String> validPrincipals;
|
||||||
|
private Date validAfter;
|
||||||
|
private Date validBefore;
|
||||||
|
private Map<String, String> critOptions;
|
||||||
|
private Map<String, String> extensions;
|
||||||
|
private byte[] signatureKey;
|
||||||
|
private byte[] signature;
|
||||||
|
|
||||||
|
public Certificate<T> build() {
|
||||||
|
return new Certificate<T>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> publicKey(T publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> nonce(byte[] nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getSerial() {
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> serial(BigInteger serial) {
|
||||||
|
this.serial = serial;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> type(long type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> id(String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getValidPrincipals() {
|
||||||
|
return validPrincipals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> validPrincipals(List<String> validPrincipals) {
|
||||||
|
this.validPrincipals = validPrincipals;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getValidAfter() {
|
||||||
|
return validAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> validAfter(Date validAfter) {
|
||||||
|
this.validAfter = validAfter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getValidBefore() {
|
||||||
|
return validBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> validBefore(Date validBefore) {
|
||||||
|
this.validBefore = validBefore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getCritOptions() {
|
||||||
|
return critOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> critOptions(Map<String, String> critOptions) {
|
||||||
|
this.critOptions = critOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getExtensions() {
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> extensions(Map<String, String> extensions) {
|
||||||
|
this.extensions = extensions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignatureKey() {
|
||||||
|
return signatureKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> signatureKey(byte[] signatureKey) {
|
||||||
|
this.signatureKey = signatureKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> signature(byte[] signature) {
|
||||||
|
this.signature = signature;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,11 @@ public class Buffer<T extends Buffer<T>> {
|
|||||||
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
|
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
|
||||||
public static final int MAX_SIZE = (1 << 30);
|
public static final int MAX_SIZE = (1 << 30);
|
||||||
|
|
||||||
|
/** Maximum size of a uint64 */
|
||||||
|
private static final BigInteger MAX_UINT64_VALUE = BigInteger.ONE
|
||||||
|
.shiftLeft(64)
|
||||||
|
.subtract(BigInteger.ONE);
|
||||||
|
|
||||||
protected static int getNextPowerOf2(int i) {
|
protected static int getNextPowerOf2(int i) {
|
||||||
int j = 1;
|
int j = 1;
|
||||||
while (j < i) {
|
while (j < i) {
|
||||||
@@ -343,10 +348,29 @@ public class Buffer<T extends Buffer<T>> {
|
|||||||
return uint64;
|
return uint64;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public BigInteger readUInt64AsBigInteger()
|
||||||
|
throws BufferException {
|
||||||
|
byte[] magnitude = new byte[8];
|
||||||
|
readRawBytes(magnitude);
|
||||||
|
return new BigInteger(1, magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
public T putUInt64(long uint64) {
|
public T putUInt64(long uint64) {
|
||||||
if (uint64 < 0)
|
if (uint64 < 0)
|
||||||
throw new IllegalArgumentException("Invalid value: " + uint64);
|
throw new IllegalArgumentException("Invalid value: " + uint64);
|
||||||
|
return putUInt64Unchecked(uint64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T putUInt64(BigInteger uint64) {
|
||||||
|
if (uint64.compareTo(MAX_UINT64_VALUE) > 0 ||
|
||||||
|
uint64.compareTo(BigInteger.ZERO) < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid value: " + uint64);
|
||||||
|
}
|
||||||
|
return putUInt64Unchecked(uint64.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private T putUInt64Unchecked(long uint64) {
|
||||||
data[wpos++] = (byte) (uint64 >> 56);
|
data[wpos++] = (byte) (uint64 >> 56);
|
||||||
data[wpos++] = (byte) (uint64 >> 48);
|
data[wpos++] = (byte) (uint64 >> 48);
|
||||||
data[wpos++] = (byte) (uint64 >> 40);
|
data[wpos++] = (byte) (uint64 >> 40);
|
||||||
|
|||||||
@@ -15,12 +15,26 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.common;
|
package net.schmizz.sshj.common;
|
||||||
|
|
||||||
import com.hierynomus.sshj.secg.SecgUtils;
|
import java.math.BigInteger;
|
||||||
import com.hierynomus.sshj.signature.Ed25519PublicKey;
|
import java.security.GeneralSecurityException;
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
import java.security.Key;
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
import java.security.KeyFactory;
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
import java.security.PublicKey;
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
import java.security.interfaces.DSAPrivateKey;
|
||||||
|
import java.security.interfaces.DSAPublicKey;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.security.spec.DSAPublicKeySpec;
|
||||||
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
@@ -29,20 +43,19 @@ import org.bouncycastle.math.ec.ECPoint;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import com.hierynomus.sshj.secg.SecgUtils;
|
||||||
import java.security.GeneralSecurityException;
|
import com.hierynomus.sshj.signature.Ed25519PublicKey;
|
||||||
import java.security.Key;
|
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.PublicKey;
|
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||||
import java.security.interfaces.*;
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||||
import java.security.spec.DSAPublicKeySpec;
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||||
import java.security.spec.RSAPublicKeySpec;
|
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||||
import java.util.Arrays;
|
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||||
|
|
||||||
/** Type of key e.g. rsa, dsa */
|
/** Type of key e.g. rsa, dsa */
|
||||||
public enum KeyType {
|
public enum KeyType {
|
||||||
|
|
||||||
|
|
||||||
/** SSH identifier for RSA keys */
|
/** SSH identifier for RSA keys */
|
||||||
RSA("ssh-rsa") {
|
RSA("ssh-rsa") {
|
||||||
@Override
|
@Override
|
||||||
@@ -60,10 +73,9 @@ public enum KeyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
final RSAPublicKey rsaKey = (RSAPublicKey) pk;
|
final RSAPublicKey rsaKey = (RSAPublicKey) pk;
|
||||||
buf.putString(sType)
|
buf.putMPInt(rsaKey.getPublicExponent()) // e
|
||||||
.putMPInt(rsaKey.getPublicExponent()) // e
|
|
||||||
.putMPInt(rsaKey.getModulus()); // n
|
.putMPInt(rsaKey.getModulus()); // n
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +83,6 @@ public enum KeyType {
|
|||||||
protected boolean isMyType(Key key) {
|
protected boolean isMyType(Key key) {
|
||||||
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
|
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** SSH identifier for DSA keys */
|
/** SSH identifier for DSA keys */
|
||||||
@@ -93,10 +104,9 @@ public enum KeyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
|
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
|
||||||
buf.putString(sType)
|
buf.putMPInt(dsaKey.getParams().getP()) // p
|
||||||
.putMPInt(dsaKey.getParams().getP()) // p
|
|
||||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||||
.putMPInt(dsaKey.getParams().getG()) // g
|
.putMPInt(dsaKey.getParams().getG()) // g
|
||||||
.putMPInt(dsaKey.getY()); // y
|
.putMPInt(dsaKey.getY()); // y
|
||||||
@@ -161,12 +171,11 @@ public enum KeyType {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
||||||
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
|
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
|
||||||
|
|
||||||
buf.putString(sType)
|
buf.putString(NISTP_CURVE)
|
||||||
.putString(NISTP_CURVE)
|
|
||||||
.putBytes(encoded);
|
.putBytes(encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,9 +211,9 @@ public enum KeyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
EdDSAPublicKey key = (EdDSAPublicKey) pk;
|
EdDSAPublicKey key = (EdDSAPublicKey) pk;
|
||||||
buf.putString(sType).putBytes(key.getAbyte());
|
buf.putBytes(key.getAbyte());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -213,6 +222,44 @@ public enum KeyType {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Signed rsa certificate */
|
||||||
|
RSA_CERT("ssh-rsa-cert-v01@openssh.com") {
|
||||||
|
@Override
|
||||||
|
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
return CertUtils.readPubKey(buf, RSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
|
CertUtils.writePubKeyContentsIntoBuffer(pk, RSA, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isMyType(Key key) {
|
||||||
|
return CertUtils.isCertificateOfType(key, RSA);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Signed dsa certificate */
|
||||||
|
DSA_CERT("ssh-dss-cert-v01@openssh.com") {
|
||||||
|
@Override
|
||||||
|
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
return CertUtils.readPubKey(buf, DSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
|
CertUtils.writePubKeyContentsIntoBuffer(pk, DSA, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isMyType(Key key) {
|
||||||
|
return CertUtils.isCertificateOfType(key, DSA);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/** Unrecognized */
|
/** Unrecognized */
|
||||||
UNKNOWN("unknown") {
|
UNKNOWN("unknown") {
|
||||||
@Override
|
@Override
|
||||||
@@ -226,6 +273,11 @@ public enum KeyType {
|
|||||||
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
|
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isMyType(Key key) {
|
protected boolean isMyType(Key key) {
|
||||||
return false;
|
return false;
|
||||||
@@ -244,7 +296,11 @@ public enum KeyType {
|
|||||||
public abstract PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
public abstract PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
public abstract void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf);
|
protected abstract void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf);
|
||||||
|
|
||||||
|
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
|
writePubKeyContentsIntoBuffer(pk, buf.putString(sType));
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract boolean isMyType(Key key);
|
protected abstract boolean isMyType(Key key);
|
||||||
|
|
||||||
@@ -266,4 +322,129 @@ public enum KeyType {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return sType;
|
return sType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class CertUtils {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T extends PublicKey> Certificate<T> readPubKey(Buffer<?> buf, KeyType innerKeyType) throws GeneralSecurityException {
|
||||||
|
Certificate.Builder<T> builder = Certificate.getBuilder();
|
||||||
|
|
||||||
|
try {
|
||||||
|
builder.nonce(buf.readBytes());
|
||||||
|
builder.publicKey((T) innerKeyType.readPubKeyFromBuffer(buf));
|
||||||
|
builder.serial(buf.readUInt64AsBigInteger());
|
||||||
|
builder.type(buf.readUInt32());
|
||||||
|
builder.id(buf.readString());
|
||||||
|
builder.validPrincipals(unpackList(buf.readBytes()));
|
||||||
|
builder.validAfter(dateFromEpoch(buf.readUInt64()));
|
||||||
|
builder.validBefore(dateFromEpoch(buf.readUInt64()));
|
||||||
|
builder.critOptions(unpackMap(buf.readBytes()));
|
||||||
|
builder.extensions(unpackMap(buf.readBytes()));
|
||||||
|
buf.readString(); // reserved
|
||||||
|
builder.signatureKey(buf.readBytes());
|
||||||
|
builder.signature(buf.readBytes());
|
||||||
|
} catch (Buffer.BufferException be) {
|
||||||
|
throw new GeneralSecurityException(be);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writePubKeyContentsIntoBuffer(PublicKey publicKey, KeyType innerKeyType, Buffer<?> buf) {
|
||||||
|
Certificate<PublicKey> certificate = toCertificate(publicKey);
|
||||||
|
buf.putBytes(certificate.getNonce());
|
||||||
|
innerKeyType.writePubKeyContentsIntoBuffer(certificate.getKey(), buf);
|
||||||
|
buf.putUInt64(certificate.getSerial())
|
||||||
|
.putUInt32(certificate.getType())
|
||||||
|
.putString(certificate.getId())
|
||||||
|
.putBytes(packList(certificate.getValidPrincipals()))
|
||||||
|
.putUInt64(epochFromDate(certificate.getValidAfter()))
|
||||||
|
.putUInt64(epochFromDate(certificate.getValidBefore()))
|
||||||
|
.putBytes(packMap(certificate.getCritOptions()))
|
||||||
|
.putBytes(packMap(certificate.getExtensions()))
|
||||||
|
.putString("") // reserved
|
||||||
|
.putBytes(certificate.getSignatureKey())
|
||||||
|
.putBytes(certificate.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isCertificateOfType(Key key, KeyType innerKeyType) {
|
||||||
|
if (!(key instanceof Certificate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Key innerKey = ((Certificate<PublicKey>) key).getKey();
|
||||||
|
return innerKeyType.isMyType(innerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static Certificate<PublicKey> toCertificate(PublicKey key) {
|
||||||
|
if (!(key instanceof Certificate)) {
|
||||||
|
throw new UnsupportedOperationException("Can't convert non-certificate key " +
|
||||||
|
key.getAlgorithm() + " to certificate");
|
||||||
|
}
|
||||||
|
return ((Certificate<PublicKey>) key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date dateFromEpoch(long seconds) {
|
||||||
|
return new Date(seconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long epochFromDate(Date date) {
|
||||||
|
return date.getTime() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String unpackString(byte[] packedString) throws BufferException {
|
||||||
|
if (packedString.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return new Buffer.PlainBuffer(packedString).readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> unpackList(byte[] packedString) throws BufferException {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
|
||||||
|
while (buf.available() > 0) {
|
||||||
|
list.add(buf.readString());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> unpackMap(byte[] packedString) throws BufferException {
|
||||||
|
Map<String, String> map = new LinkedHashMap<String, String>();
|
||||||
|
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
|
||||||
|
while (buf.available() > 0) {
|
||||||
|
String name = buf.readString();
|
||||||
|
String data = unpackString(buf.readStringAsBytes());
|
||||||
|
map.put(name, data);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] packString(String data) {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return "".getBytes();
|
||||||
|
}
|
||||||
|
return new Buffer.PlainBuffer().putString(data).getCompactData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] packList(Iterable<String> strings) {
|
||||||
|
Buffer<?> buf = new Buffer.PlainBuffer();
|
||||||
|
for (String string : strings) {
|
||||||
|
buf.putString(string);
|
||||||
|
}
|
||||||
|
return buf.getCompactData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] packMap(Map<String, String> map) {
|
||||||
|
Buffer<?> buf = new Buffer.PlainBuffer();
|
||||||
|
List<String> keys = new ArrayList<String>(map.keySet());
|
||||||
|
Collections.sort(keys);
|
||||||
|
for (String key : keys) {
|
||||||
|
buf.putString(key);
|
||||||
|
String value = map.get(key);
|
||||||
|
buf.putString(packString(value));
|
||||||
|
}
|
||||||
|
return buf.getCompactData();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class KeyProviderUtil {
|
|||||||
public static KeyFormat detectKeyFileFormat(File location)
|
public static KeyFormat detectKeyFileFormat(File location)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return detectKeyFileFormat(new FileReader(location),
|
return detectKeyFileFormat(new FileReader(location),
|
||||||
new File(location + ".pub").exists());
|
new File(location + ".pub").exists() || new File(location + "-cert.pub").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,10 +57,14 @@ public class OpenSSHKeyFile
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(File location) {
|
public void init(File location) {
|
||||||
final File f = new File(location + ".pub");
|
// try cert key location first
|
||||||
if (f.exists())
|
File pubKey = new File(location + "-cert.pub");
|
||||||
|
if (!pubKey.exists()) {
|
||||||
|
pubKey = new File(location + ".pub");
|
||||||
|
}
|
||||||
|
if (pubKey.exists())
|
||||||
try {
|
try {
|
||||||
initPubKey(new FileReader(f));
|
initPubKey(new FileReader(pubKey));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// let super provide both public & private key
|
// let super provide both public & private key
|
||||||
log.warn("Error reading public key file: {}", e.toString());
|
log.warn("Error reading public key file: {}", e.toString());
|
||||||
|
|||||||
@@ -15,10 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.common;
|
package net.schmizz.sshj.common;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||||
import net.schmizz.sshj.common.Buffer.PlainBuffer;
|
import net.schmizz.sshj.common.Buffer.PlainBuffer;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class BufferTest {
|
public class BufferTest {
|
||||||
|
|
||||||
@@ -44,4 +48,103 @@ public class BufferTest {
|
|||||||
// success
|
// success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldThrowOnPutNegativeLongUInt64() {
|
||||||
|
try {
|
||||||
|
new PlainBuffer().putUInt64(-1l);
|
||||||
|
fail("Added negative uint64 to buffer?");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldThrowOnReadNegativeLongUInt64() {
|
||||||
|
byte[] negativeLong = new byte[] { (byte) 0x80,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x01 };
|
||||||
|
Buffer<?> buff = new PlainBuffer(negativeLong);
|
||||||
|
|
||||||
|
try {
|
||||||
|
buff.readUInt64();
|
||||||
|
fail("Read negative uint64 from buffer?");
|
||||||
|
} catch (BufferException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldThrowOnPutNegativeBigIntegerUInt64() {
|
||||||
|
try {
|
||||||
|
new PlainBuffer().putUInt64(new BigInteger("-1"));
|
||||||
|
fail("Added negative uint64 to buffer?");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHaveCorrectValueForMaxUInt64() {
|
||||||
|
byte[] maxUInt64InBytes = new byte[] { (byte) 0xFF, (byte) 0xFF,
|
||||||
|
(byte) 0xFF, (byte) 0xFF,
|
||||||
|
(byte) 0xFF, (byte) 0xFF,
|
||||||
|
(byte) 0xFF, (byte) 0xFF };
|
||||||
|
BigInteger maxUInt64 = new BigInteger(1, maxUInt64InBytes);
|
||||||
|
new PlainBuffer().putUInt64(maxUInt64); // no exception
|
||||||
|
|
||||||
|
BigInteger tooBig = maxUInt64.add(BigInteger.ONE);
|
||||||
|
try {
|
||||||
|
new PlainBuffer().putUInt64(tooBig);
|
||||||
|
fail("Added 2^64 (too big) as uint64 to buffer?");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCorrectlyEncodeAndDecodeUInt64Types() throws BufferException {
|
||||||
|
// This number fits into a unsigned 64 bit integer but not a signed one.
|
||||||
|
BigInteger bigUint64 = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).add(BigInteger.ONE);
|
||||||
|
assertEquals(0x8000000000000001l, bigUint64.longValue());
|
||||||
|
|
||||||
|
Buffer<PlainBuffer> buff = new PlainBuffer();
|
||||||
|
buff.putUInt64(bigUint64);
|
||||||
|
byte[] data = buff.getCompactData();
|
||||||
|
assertEquals(8, data.length);
|
||||||
|
assertEquals((byte) 0x80, data[0]);
|
||||||
|
assertEquals((byte) 0x00, data[1]);
|
||||||
|
assertEquals((byte) 0x00, data[2]);
|
||||||
|
assertEquals((byte) 0x00, data[3]);
|
||||||
|
assertEquals((byte) 0x00, data[4]);
|
||||||
|
assertEquals((byte) 0x00, data[5]);
|
||||||
|
assertEquals((byte) 0x00, data[6]);
|
||||||
|
assertEquals((byte) 0x01, data[7]);
|
||||||
|
|
||||||
|
byte[] asBinary = new byte[] { (byte) 0x80,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x01 };
|
||||||
|
buff = new PlainBuffer(asBinary);
|
||||||
|
assertEquals(bigUint64, buff.readUInt64AsBigInteger());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHaveSameUInt64EncodingForBigIntegerAndLong() {
|
||||||
|
long[] values = { 0l, 1l, 232634978082517765l, Long.MAX_VALUE - 1, Long.MAX_VALUE };
|
||||||
|
for (long value : values) {
|
||||||
|
byte[] bytesBigInt = new PlainBuffer().putUInt64(BigInteger.valueOf(value)).getCompactData();
|
||||||
|
byte[] bytesLong = new PlainBuffer().putUInt64(value).getCompactData();
|
||||||
|
assertArrayEquals("Value: " + value, bytesLong, bytesBigInt);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,4 +57,10 @@ public class KeyProviderUtilTest {
|
|||||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs8-blanks"));
|
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs8-blanks"));
|
||||||
assertEquals(KeyFormat.PKCS8, format);
|
assertEquals(KeyFormat.PKCS8, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenSshSigned() throws IOException {
|
||||||
|
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "signed"));
|
||||||
|
assertEquals(KeyFormat.OpenSSH, format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,32 +15,44 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.keyprovider;
|
package net.schmizz.sshj.keyprovider;
|
||||||
|
|
||||||
import net.schmizz.sshj.common.KeyType;
|
|
||||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
|
||||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
|
||||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
|
||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
|
||||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
|
||||||
import net.schmizz.sshj.util.KeyUtil;
|
|
||||||
import org.apache.sshd.common.util.SecurityUtils;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import org.apache.sshd.common.util.SecurityUtils;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||||
|
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.KeyType;
|
||||||
|
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||||
|
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||||
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
|
import net.schmizz.sshj.util.KeyUtil;
|
||||||
|
|
||||||
public class OpenSSHKeyFileTest {
|
public class OpenSSHKeyFileTest {
|
||||||
|
|
||||||
@@ -161,6 +173,68 @@ public class OpenSSHKeyFileTest {
|
|||||||
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
|
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSuccessfullyLoadSignedRSAPublicKey() throws IOException {
|
||||||
|
FileKeyProvider keyFile = new OpenSSHKeyFile();
|
||||||
|
keyFile.init(new File("src/test/resources/keytypes/certificate/test_rsa"),
|
||||||
|
PasswordUtils.createOneOff(correctPassphrase));
|
||||||
|
assertNotNull(keyFile.getPrivate());
|
||||||
|
PublicKey pubKey = keyFile.getPublic();
|
||||||
|
assertEquals("RSA", pubKey.getAlgorithm());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Certificate<RSAPublicKey> certificate = (Certificate<RSAPublicKey>) pubKey;
|
||||||
|
|
||||||
|
assertEquals(new BigInteger("9223372036854775809"), certificate.getSerial());
|
||||||
|
assertEquals("testrsa", certificate.getId());
|
||||||
|
|
||||||
|
assertEquals(2, certificate.getValidPrincipals().size());
|
||||||
|
assertTrue(certificate.getValidPrincipals().contains("jeroen"));
|
||||||
|
assertTrue(certificate.getValidPrincipals().contains("nobody"));
|
||||||
|
|
||||||
|
assertEquals(parseDate("2017-04-11 17:38:00 -0400"), certificate.getValidAfter());
|
||||||
|
assertEquals(parseDate("2017-04-11 18:09:27 -0400"), certificate.getValidBefore());
|
||||||
|
|
||||||
|
assertEquals(0, certificate.getCritOptions().size());
|
||||||
|
|
||||||
|
Map<String, String> extensions = certificate.getExtensions();
|
||||||
|
assertEquals(5, extensions.size());
|
||||||
|
assertEquals("", extensions.get("permit-X11-forwarding"));
|
||||||
|
assertEquals("", extensions.get("permit-agent-forwarding"));
|
||||||
|
assertEquals("", extensions.get("permit-port-forwarding"));
|
||||||
|
assertEquals("", extensions.get("permit-pty"));
|
||||||
|
assertEquals("", extensions.get("permit-user-rc"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSuccessfullyLoadSignedDSAPublicKey() throws IOException {
|
||||||
|
FileKeyProvider keyFile = new OpenSSHKeyFile();
|
||||||
|
keyFile.init(new File("src/test/resources/keytypes/certificate/test_dsa"),
|
||||||
|
PasswordUtils.createOneOff(correctPassphrase));
|
||||||
|
assertNotNull(keyFile.getPrivate());
|
||||||
|
PublicKey pubKey = keyFile.getPublic();
|
||||||
|
assertEquals("DSA", pubKey.getAlgorithm());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Certificate<RSAPublicKey> certificate = (Certificate<RSAPublicKey>) pubKey;
|
||||||
|
|
||||||
|
assertEquals(new BigInteger("123"), certificate.getSerial());
|
||||||
|
assertEquals("testdsa", certificate.getId());
|
||||||
|
|
||||||
|
assertEquals(1, certificate.getValidPrincipals().size());
|
||||||
|
assertTrue(certificate.getValidPrincipals().contains("jeroen"));
|
||||||
|
|
||||||
|
assertEquals(parseDate("2017-04-11 17:37:00 -0400"), certificate.getValidAfter());
|
||||||
|
assertEquals(parseDate("2017-04-12 03:38:49 -0400"), certificate.getValidBefore());
|
||||||
|
|
||||||
|
assertEquals(1, certificate.getCritOptions().size());
|
||||||
|
assertEquals("10.0.0.0/8", certificate.getCritOptions().get("source-address"));
|
||||||
|
|
||||||
|
assertEquals(1, certificate.getExtensions().size());
|
||||||
|
assertEquals("", certificate.getExtensions().get("permit-pty"));
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup()
|
public void setup()
|
||||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
throws UnsupportedEncodingException, GeneralSecurityException {
|
||||||
@@ -182,4 +256,13 @@ public class OpenSSHKeyFileTest {
|
|||||||
scanner.close();
|
scanner.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Date parseDate(String date) {
|
||||||
|
DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
|
||||||
|
try {
|
||||||
|
return f.parse(date);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/test/resources/keyformats/signed
Normal file
30
src/test/resources/keyformats/signed
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-128-CBC,DEF5F558DB233F512527D38775CF90A6
|
||||||
|
|
||||||
|
EQb59FGxEYNaLoYZ1GRtLdQVYe3DtQyAq42OvJyPF2xFpiV+U63TDzHBeSJnf4yK
|
||||||
|
FWcWRbmFM5XL5jpuw7oUtg+bFOYsSjRMTGNpxcXDoByfRubLb3RPMlmVCENcwTXa
|
||||||
|
pF1QuKQYj2+DXRam5y2w7A6rznd5lFDRM57kApGSMcrWwNz2WDyvuqlPTo6Wsj1j
|
||||||
|
SWOQb9Te/ww1t8iEHryAITzUSRhbZG2epGh85QvuKhBebBd9TNRZwqwaZPx+j87/
|
||||||
|
JGvq2RzttIydciLRBx3kYFI7JV1TGTbe+Hd10Yis9jBttqmEpB9Zyoug1Lubg3E2
|
||||||
|
s45jk0CVAFp+44dKhk6K0uX4cjhC9uok6cAGZ7DYMokxEfCiJc7zJJgLvbil2lvK
|
||||||
|
fbewUoiXGLtCPaDe1UXhmkYXBL2BqrBa2PTYlB0JQzFn//9qWW6RVqLpltWSFcFT
|
||||||
|
nGQpRKZLSQhHLn+90X4lAuolUlrpWqgREiGSHlIgihv8mz9uAbHWSvSQE3q0dKSb
|
||||||
|
OgU1CDVsxd0mdkb6ZNeS1iT50uwCpwiUw4Cx9xZp0xdzjiN15ED8eLI/nDFCZXhA
|
||||||
|
jA0AK/cPzlO/Vc3uoM8+3PUkiMKd4glJzWkkE9pEiPlTQ3xivxUM4wQq6pgrjT13
|
||||||
|
YI5PH5FkGNXYEeNxGnL+VXrWnxItd7ZVctG+3p6OKr9ShDxfYfw7WHk2n2o/s9rP
|
||||||
|
j8eSq0G/Dr5ZoaMipPX0aP/BXzZZVVsnFc0SmGfcuIGDug+hjs5OKcrvi6QteJ6u
|
||||||
|
dlsZJUy1YYnc/7T43TMllnouCHQ4TN01JTJSFS0IuKUrDoXI2DBSf2nE4J+04Cno
|
||||||
|
bC5WZCmThM2tWdFiqsRn4I5oZ8vEl6ffhzgwLs/8fJSwzwCwLraMSMWJMJibnG/8
|
||||||
|
cn3/Mwzm6aDMpRqu7h6s2tDctdZJEdRcwjD5tdPg09CLsNvG7nfJWi8RL/PxSuC1
|
||||||
|
m5KKK9rbXVRzg011QELrxTBUAGcH/YHEsOZNrIexyWG99eJ5Y3tEpyaxHVVIsT/o
|
||||||
|
+bsC8SEhADWKmfQmzZz8UbUQLs5SOxa6mutxudzmvdLnGmHk8fsBO7MbxRyBjqau
|
||||||
|
+o5/ClbNzwSUSQQ8W3dpEpU/7udYAtHjwIWwVk7lwUqe/s7p2G9f5LDegJfJAXiU
|
||||||
|
zGetpnYYFd1n4xQs22UPHS0+RaFLsYszvSv+LUEpVJ+zIWSB5hp7OrWLiQpGnQVH
|
||||||
|
YydQUrxt8AYnhdrBbsxk652XFhBZzzbA9AlEHLhiMXDIh7XamFNk/S6fVGgsACGo
|
||||||
|
hu2Ui50lHIRgNKds2tp41G74Vgv20lu3htU0wN9nDwjxATu+sX/0IaWwZIj1iuFf
|
||||||
|
8YK6P0yP5rWyzAfDQWnh15SHE6zAvKYwyZtG7OWBYmd3whyR+VGuzCKw2uLh6JBx
|
||||||
|
5GdNScf5szD1KSxqWwfY5tVLSn/gsSgCAptp5tiIdazOID1OkuM24yYRWoCvxT52
|
||||||
|
qlfG5LHIhYUCzOdRK9DDSgSBqXCaiN3VD3KLOBfeSKSoen0h+CsIz9kmL73liisM
|
||||||
|
+V8knyffjXfb/yCtV1b2xD9bds2si49Mif4aS1SX6bnBT+A5z/f/N3BGrdtIaMFl
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
1
src/test/resources/keyformats/signed-cert.pub
Normal file
1
src/test/resources/keyformats/signed-cert.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3ZotKIydji2P4WBzJjEHH1+n0VBVEyeZDb1AHj4HbUoAAAADAQABAAABAQDu1ngN8Oe+N32oDQ9CSw59fEM4Xo2HaAM/LFZffSj6gwS+BrKVYcupZCoUsLjNaOXudeivrI7+MTtfpPeCXKfNw6Bhmh16OXDdkFuEraLotSkibqNleGPrJW5KB6OBgTv7bSx249oobPLZ9Xog2fO888lsCFMCLMuoiEQM1Y3ZDcoqVRGBSAjki/QLtqWFeouiYdfCdzk80TccxORqNanK6ePQ1pTcOR/OtGToXeJEqqxX51wdSvWjCHGXmjjuQSM/Eznb86LbQtG79koqL4tuDVYvtrnIXUX7/aXicVmCT8gNFxZBP3uX6OJLT0CCZol2F/mu6pq7JLbhhfRCv6/NgAAAAAAAAAEAAAABAAAADHVzZXJfamVyb2VucwAAABYAAAAHamVyb2VuMQAAAAdqZXJvZW4yAAAAAFjtLVAAAAAAWO00lQAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDZ0a09cp+hsK7F5eg4etwemWyJjA97LlSxAsWOxNNOskNi2FDrhYCpMejr1rdrB1yHy3UYlG0AG3bmzX10uyad17/AHlKvNMQh6rysbe6DDgdPOVQtZv5UCCuvPDnnUAn++SXqxYbvVFSK48BJXeRk2mKrnC2iWsnJCW81j9ICA2S4LEtYE0DiYRrM2UT9lUgmcwxLGqL6JkWbJTF+5KLyRvPGtb06Uo5KOdFlyjEVZ437j/wNTrn2tbQJaMT5qaNuh4Py8WV7aCnX73YfJHdrnCbhOP1MBX9nT+dpMsLYXnCi0K/LLCKJIWn8yNwxPmrUc0OkqOYQWbjGkKn2dt/3AAABDwAAAAdzc2gtcnNhAAABACvwMQl9HsmjpLEt2MMhwyyTcwYsy6lUNa0lWs/YJlR70e/EzriZZVwXGpNYnXW5KgXOA020bhXgpPijBqWOxOAoHyE1CpLAuMDpYAxjnlNy391GzcUrQM74nPeBuE1TGpatS314Gx+iv98EaPdlvtommgh/Ggsb48n9xNrOuonYXwgUOpjvPYlDWvB3klUea4qvhru/23xzKf7DuIGSffB++IANkm1+TJ8vskE44F/VvVo4CCKEY7HaRHTSvfQ4yaNJCEWg0KcFbI79ICbB3mpHRKeDaqjJj4+6858D4/SYrBPOoCr8QNWR/kvYjrWD5qvHZMLO6x+3lEDuh76MNbk= choover@ma-lt-choover
|
||||||
15
src/test/resources/keytypes/certificate/test_dsa
Normal file
15
src/test/resources/keytypes/certificate/test_dsa
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN DSA PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-128-CBC,F30B96BBE72D14E7E408E43D484642E1
|
||||||
|
|
||||||
|
A+fs86aamKouxGIQLbJc8dF6wzUqXo5stco6SjGFA78mutESj6EWWnU49/JAyWmK
|
||||||
|
t20qKnnOlRNVVGw8h+FoRR1ukA9hkcc4Yg8bvHw21B45bswd165gJPLoOsacocr+
|
||||||
|
487GztRao44+bT5tzkS/pCo0ianjJpfmlRPr8tVkrM0lA9SYWuVjhzm20mKfW8Q0
|
||||||
|
iQk4xZMDS01BZ2BM1cEs8YsR5xXwV84i9iS9Evr5J3+V3xhilZiNSiYLT2kqQ+u7
|
||||||
|
ccqVgybUb7OF2nd1GDY75E3hY0V+pXjPzFn0Er2hK8o76W61s52baVr2xOTq/wmH
|
||||||
|
Ra3FCzj8M7xagprOYsVqza7oLt6lOK4VJQzFntoCDNpAqZDL77vFJz+0E2ZI/+cG
|
||||||
|
1HSt889w0obu6D1XorsBx+LuNJZqwMtwYQMbjr1fXvRLktM4E0gUfyFgeGYJqvl+
|
||||||
|
4AV24MII/+D1K5pnA3Q0Ban+dpLUqH9dGd7dplol12gzSpRtLHRgjv/GggZUIUjh
|
||||||
|
MBTGdLkMHjteph0VFxeNiahydV707Gz9oc35e1MzeAi8dDqPNM7T1XDLV6Tqm2+x
|
||||||
|
j2KBlXpkVhHEJ3IvDxO+1g==
|
||||||
|
-----END DSA PRIVATE KEY-----
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguS/sL4az6garEmUIM9ELqcaEfRJlPICG3lGthEJsWwEAAACBAJuvlcNNkgWfJV8y4HWxzith+6EZLGfd3jYqfXmITYDW8830cAmU7xC/aXK5r0LaSKxpyW/G/Z1396j69yxN1R1EC4fDvcEjTzmkcxKTrm8FQzFew7rkpm5H6MZq7PfrA/ph1nrDNLzO8Pzbw4trrr5yHuh3pQHt/WBSz+PiZPULAAAAFQDyrWY0qq7RR2NOslTkL7iBRVihKwAAAIA68Qp50ZqMvlGbnQQdYlH5HG1+tTs56Arc1Dgau8npyH9kTb0txpdijIwXhbwRN2eq0KQ3iF1XOzJSI5nWH2ONaaxK3Da4o8yt7/PQUFBDYqDoE+59XGOsupVKwpFiRPJURigoT943XdYaTRP5lX9nztVb/lFqppG/hdH8iMVJVwAAAIAbgIwWCMroqlEdsXKyM3wzYEHj8jTPTdr00WpV/WBghYittcfV3Cwp8HNSe1OyiA240CMFVLaKGr9PdoqU/an9WHb1skTuz2UNBs8uKJY3pISAZXHiOkza07iwTUcvQTZS+CctIlL1kxj2LTkI/Auy+Eiup40lrbIm87F6+LwSXgAAAAAAAAB7AAAAAQAAAAd0ZXN0ZHNhAAAACgAAAAZqZXJvZW4AAAAAWO1MfAAAAABY7dmJAAAAJAAAAA5zb3VyY2UtYWRkcmVzcwAAAA4AAAAKMTAuMC4wLjAvOAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAABsgAAAAdzc2gtZHNzAAAAgQCVc14O81iWe8XfsffXLYwzGr39QrlPQum2WLr3TfRCT+yKkyu3sLBO+3d25XpD1U2MQ1RJ7aJGET0m3QrO3U4CZGV2Fl3e6aLVrzvppR+LHoZa+kOl1bHfiCJ6NLib2bYNa1lIJ5L74lwYK9ZZ85M0Sun0DbNyNOgUX3OcG7oZiQAAABUA1x8Y8ZPR70C/BtQDnjq5Lx+p/cMAAACAGcSOjhD111FJMmi2T1nqyAjMreZx7CfQyfmg+s3dsePm8qsBZNiGRUVgCen14293O1AcMiBsGxI2a2+Kb4V3VzuT64zucrw51ZuqXu79T+KXDdTifgNoNFVPg+sorXyuzc0Z33BTdY8NAkpjJbjhKCYs3FVa4ygGNDC9pyh7nHsAAACBAIxQmlDKKcu0chmM9WjDqSosKJxADRtLmD1Drv1ag2DF3cKFPH5xtFqSpF9bsKeuhIhWeXiwytfeeHufdvno2VTvBJkOFFj5b4jsB2s3GyyJT9Z9TWFYecIj3t8t3QLlmDnGr8cHwmHpcbZmPBnm1hM9DA9OWkQaJWHccsjwSLI0AAAANwAAAAdzc2gtZHNzAAAAKDyWWSqjzoS2RmaBcm1CXVU7ZIx4yg3SAJEGP+ss0Jw27PjDhe54OJ4= choover@ma-lt-choover
|
||||||
30
src/test/resources/keytypes/certificate/test_rsa
Normal file
30
src/test/resources/keytypes/certificate/test_rsa
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-128-CBC,5F1AEDBE1E9E4D83EBD9C7B71CD84DB5
|
||||||
|
|
||||||
|
N8SuKdWK3dEA7K6dNodjE++qqXPpVXJlDhiJbrXm1vbs2u+hckzZflK9OQZU1SsY
|
||||||
|
hs6ZyFg/jagdMNEKKrCEK60QMcMg1iPlkMvTwEvQmdWSWtMiTFes9Ec4njUbcbgN
|
||||||
|
qHzixGfWjpTXCRq3QC2Bydq7+njvIEUeLIjTaCIXqwoZoMcBc0N6Aj529gbgDD/i
|
||||||
|
JC0auaSfejSZQm4Ppn+OuO1JKgNkwsjoTrrZjbKHuayYQaioaR5OcAKgSSs5YDdo
|
||||||
|
SJI+PDeIOVb6aMcXwYP5J7C9zzkZU1Ml3dGHk2JMs0Omu2gICRm3mXXmoHRkiBKj
|
||||||
|
2h7EsghiV/Be4lkOF+T3HN19F4G44EIUj7DotbtqNPEFRZa+SSpN3OZmLJQSwBXD
|
||||||
|
A0zek5BxDu7M4VfbLryxNWcHinLe8fBKLQJe3F2/c5ZbMA4Z9098FIL1xWOqvbzS
|
||||||
|
SjiuWhIDxdYVeVe579mUlWjPrMF3NnD4BP9oQlYmbSTxL7B486Ckh7ACFzQhUXVV
|
||||||
|
t2JNZC2FcxM2WU7cHABvGUQNeq/Y2pB5Xq8AmzdpdAs2VZLHtgOvmnPjfo+Q3s1Y
|
||||||
|
rlRe5sCkyn0Ju4M09sMBIQfwszVKa3/l/ZVrG5LCOEHkF+35ex6Gz8ePRlm5jhnU
|
||||||
|
ZQVuLjxbnjnDMaJ0c0IoW7s44cdEDkv3fDzfQHrPzM50oFUN5WOKKWFnT4V1DBLJ
|
||||||
|
9YW8AcNL6bSlgZNkLntn5Wvl3BBKbf5SMmqegfR3eqz50KNwzKKQU8dcIwkJsK2s
|
||||||
|
Z5fNWbydgNDZhrebJL/jpuXpXcvfO0cVMja7y/O0bQ6M37Arpk8bQhTBKZZSaXeF
|
||||||
|
I0Bx3IOxsodWpA9O3Yum8fqqPGrCxsAsP/mWNG0Ov29Myi3MeZ0zgQH283En0Pnx
|
||||||
|
8hvDlw8vNcFe22Jycgg4846FlIWzkT7VfDVxDoYCyjlMTZ2ASys8llH5in4i1w4m
|
||||||
|
j0ZREJE/+evCXBHLoTO96Rgnjt7hp9g62FHJ+ivRlCQVY6sTB03GNv//4v7xGMPF
|
||||||
|
S4eBB9gnGKEEe8zRcOPgUEhUOrFv9cUpdhYz/SLuTZIPPiNwkAMZ6zM77FMsrdWw
|
||||||
|
18wiemOhizCd/JZDlH9COue6EXnr7rexmTp7rUsJOTq8q3rpYmz2s0rGJbqxyxlq
|
||||||
|
BiCjJqN004ZmtFCTD0wCGuNsVLiNyWSspIfzWXkfCyO0SbiH8QjlkUZVr456/5sI
|
||||||
|
cvCQ3ltAFfxR8wMZlfgtT3mEu8JAQCml89yMbttisdfz48XnLeXAzZuZzEr4OllN
|
||||||
|
AMQ8RINlZC2fxqV25jJi+E1da0yvwM9x0NPaM/ZUQqGzMf+dGldcVWdlNFBsthx0
|
||||||
|
wULaGfTYmmn6rBdbeGlt0JKGGL8Ak8+adNSHHxaJuL9W/5wLH41HsE89ZldlTHuI
|
||||||
|
GeHM1lDA/DwO85c4Fk7Ai0Ny44PjdGn27pLNhAfoT2HivF7zMNV+SUXU57iL/qdR
|
||||||
|
yJYGZJyXdreK4qaSCx7BdaK0AFvhBTXW/uRXJCX/XW6zdzdqy5/wkqWt1sWHKTxf
|
||||||
|
Z0CBKWd/se6JCQoixG2Cpo87SeLZsJEscK8eDdLDQwJdKdmIqgw6LddF7RPpq/5D
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb0+Llm+3BKq6n5YoMepI1/Iqp4WnSFYYbpm9Y0jLbJMAAAADAQABAAABAQDF9lt1iAGZWGqNtKR05HRMSyRSG2li0Hq732U2yQ9MmWSAEwltBXILCUrTnkJOWIMYyKY4Y22K+aIWMmnJdE8sehfZ6d+gz20yZ8kHndB7CnKn6oxh6kW5NAGye3kDR808L836U2VxMMgpTH/lBi4909tKf6vAY2qoG/LW4ftFWcf0jU7U8lwlvJ4mjJ9Ld5ObaJOa1h3URHWpiXuSa/JFVTMVAz8Z7h6hXpwruv2HQlKJGs2axP71pGR6Vni2fDZ4kWn5dObv2OA5VLiTD+YZtQ0edAEKORyETaCDwgCgX4qNwdGE/A37c1B6K6tGZrWGjXaPACpOFjwjVVhEI8TPgAAAAAAAAAEAAAABAAAAB3Rlc3Ryc2EAAAAUAAAABmplcm9lbgAAAAZub2JvZHkAAAAAWO1MuAAAAABY7VQXAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBANnRrT1yn6GwrsXl6Dh63B6ZbImMD3suVLECxY7E006yQ2LYUOuFgKkx6OvWt2sHXIfLdRiUbQAbdubNfXS7Jp3Xv8AeUq80xCHqvKxt7oMOB085VC1m/lQIK688OedQCf75JerFhu9UVIrjwEld5GTaYqucLaJayckJbzWP0gIDZLgsS1gTQOJhGszZRP2VSCZzDEsaovomRZslMX7kovJG88a1vTpSjko50WXKMRVnjfuP/A1Oufa1tAloxPmpo26Hg/LxZXtoKdfvdh8kd2ucJuE4/UwFf2dP52kywthecKLQr8ssIokhafzI3DE+atRzQ6So5hBZuMaQqfZ23/cAAAEPAAAAB3NzaC1yc2EAAAEA0uyAwWCFNFEZXLuy1+lwd9VtgCcC50HKMyHywKzkyKlDIbsKbCLcQG74f0nDP7c+8jAFRCxLfaGZ55SYXkPoXMOPqFsln7YvidC7nlKhF2Q7FgOBd04SoriAvi1M24EAE+Y9LUz6idLEuegXt32cobv+fqVKRBZ0V6UpDc5W58/6lDk2YMNOIbQQNy34Lf1HqIhxaWC6pmnT3rPcZylvZI7wP58DdhInWoEVMZawZ0m0Py2C2Pb7zOwFY81nXucCHUfCL4UngN1QOLg9/0vwGZmUVkTnPsbeH6gcBoQY6XdjbPB8HP/a1Ugr2wsTyFJKdc1+5r/KtcCLiXe1SmmHQA== choover@ma-lt-choover
|
||||||
1
src/test/resources/keytypes/certificate/test_rsa.pub
Normal file
1
src/test/resources/keytypes/certificate/test_rsa.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF9lt1iAGZWGqNtKR05HRMSyRSG2li0Hq732U2yQ9MmWSAEwltBXILCUrTnkJOWIMYyKY4Y22K+aIWMmnJdE8sehfZ6d+gz20yZ8kHndB7CnKn6oxh6kW5NAGye3kDR808L836U2VxMMgpTH/lBi4909tKf6vAY2qoG/LW4ftFWcf0jU7U8lwlvJ4mjJ9Ld5ObaJOa1h3URHWpiXuSa/JFVTMVAz8Z7h6hXpwruv2HQlKJGs2axP71pGR6Vni2fDZ4kWn5dObv2OA5VLiTD+YZtQ0edAEKORyETaCDwgCgX4qNwdGE/A37c1B6K6tGZrWGjXaPACpOFjwjVVhEI8TP choover@ma-lt-choover
|
||||||
Reference in New Issue
Block a user