You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/06/08 15:50:26 UTC
mina-sshd git commit: [SSHD-487] Add support for writing public keys
in OpenSSH format
Repository: mina-sshd
Updated Branches:
refs/heads/master 9b98f342d -> 6380cc8d8
[SSHD-487] Add support for writing public keys in OpenSSH format
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/6380cc8d
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/6380cc8d
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/6380cc8d
Branch: refs/heads/master
Commit: 6380cc8d860ebc5686b161ed238889fe1c13bd49
Parents: 9b98f34
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jun 8 16:50:16 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jun 8 16:50:16 2015 +0300
----------------------------------------------------------------------
.../org/apache/sshd/common/cipher/ECCurves.java | 61 +++++++++++++++---
.../keys/AbstractPublicKeyEntryDecoder.java | 39 +++++++++++-
.../config/keys/DSSPublicKeyEntryDecoder.java | 18 ++++++
.../config/keys/ECDSAPublicKeyEntryDecoder.java | 67 ++++++++++++++++++++
.../sshd/common/config/keys/KeyUtils.java | 4 +-
.../sshd/common/config/keys/PublicKeyEntry.java | 51 +++++++++++++++
.../config/keys/PublicKeyEntryDecoder.java | 11 ++++
.../common/config/keys/RSAPublicKeyDecoder.java | 13 ++++
.../server/config/keys/AuthorizedKeyEntry.java | 33 ++++++++++
.../config/keys/AuthorizedKeyEntryTest.java | 41 +++++++++++-
10 files changed, 325 insertions(+), 13 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index 2c1cecc..0c0c66f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -19,6 +19,7 @@
package org.apache.sshd.common.cipher;
import java.math.BigInteger;
+import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
@@ -30,6 +31,7 @@ import java.util.TreeMap;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
/**
* Utilities for working with elliptic curves.
@@ -45,12 +47,6 @@ public class ECCurves {
public static final String NISTP521 = "nistp521";
- private static final Map<Integer, String> CURVE_SIZES = new TreeMap<Integer, String>();
- static {
- CURVE_SIZES.put(Integer.valueOf(256), NISTP256);
- CURVE_SIZES.put(Integer.valueOf(384), NISTP384);
- CURVE_SIZES.put(Integer.valueOf(521), NISTP521);
- }
public static String getCurveName(ECParameterSpec params) {
int fieldSize = getCurveSize(params);
@@ -61,12 +57,57 @@ public class ECCurves {
return curveName;
}
+ /**
+ * Key=num. of bits, value=curve name
+ */
+ private static final Map<Integer, String> SIZE2CURVENAME =
+ Collections.unmodifiableMap(new TreeMap<Integer, String>() {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ put(Integer.valueOf(256), NISTP256);
+ put(Integer.valueOf(384), NISTP384);
+ put(Integer.valueOf(521), NISTP521);
+ }
+ });
+
+ /**
+ * @param fieldSize The key size in bits
+ * @return The name of the curve - {@code null/empty} if no match found
+ */
public static String getCurveName(int fieldSize) {
- return CURVE_SIZES.get(Integer.valueOf(fieldSize));
+ return SIZE2CURVENAME.get(Integer.valueOf(fieldSize));
+ }
+
+ private static final Map<String, Integer> CURVENAME2OCTECTCOUNT =
+ Collections.unmodifiableMap(new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER) {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ put(NISTP256, Integer.valueOf(32));
+ put(NISTP384, Integer.valueOf(48));
+ put(NISTP521, Integer.valueOf(66));
+ }
+ });
+
+ /**
+ * @param curveName Curve name - case <U>insensitive</U> - ignored
+ * if {@code null}/empty
+ * @return The number of octets used to represent the point(s) for
+ * the curve - {@code null} if no match found
+ */
+ public static Integer getNumPointOctets(String curveName) {
+ if (GenericUtils.isEmpty(curveName)) {
+ return null;
+ } else {
+ return CURVENAME2OCTECTCOUNT.get(curveName);
+ }
}
public static int getCurveSize(ECParameterSpec params) {
- return params.getCurve().getField().getFieldSize();
+ EllipticCurve curve = ValidateUtils.checkNotNull(params, "No EC params", GenericUtils.EMPTY_OBJECT_ARRAY).getCurve();
+ ECField field = ValidateUtils.checkNotNull(curve, "No EC curve", GenericUtils.EMPTY_OBJECT_ARRAY).getField();
+ return ValidateUtils.checkNotNull(field, "No EC field", GenericUtils.EMPTY_OBJECT_ARRAY).getFieldSize();
}
public static Digest getDigestForParams(ECParameterSpec params) {
@@ -75,8 +116,10 @@ public class ECCurves {
return BuiltinDigests.sha256.create();
} else if (size <= 384) {
return BuiltinDigests.sha384.create();
- } else {
+ } else if (size <= 521) {
return BuiltinDigests.sha512.create();
+ } else {
+ throw new UnsupportedOperationException("Unsupported curve size for digest: " + size);
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
index fc97f13..763a8ce 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
@@ -22,6 +22,7 @@ package org.apache.sshd.common.config.keys;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.math.BigInteger;
import java.nio.charset.Charset;
@@ -114,6 +115,43 @@ public abstract class AbstractPublicKeyEntryDecoder<K extends PublicKey> impleme
return getKeyType().getSimpleName() + ": " + getSupportedTypeNames();
}
+ public static final int encodeString(OutputStream s, String v) throws IOException {
+ return encodeString(s, v, StandardCharsets.UTF_8);
+ }
+
+ public static final int encodeString(OutputStream s, String v, String charset) throws IOException {
+ return encodeString(s, v, Charset.forName(charset));
+ }
+
+ public static final int encodeString(OutputStream s, String v, Charset cs) throws IOException {
+ return writeRLEBytes(s, v.getBytes(cs));
+ }
+
+ public static final int encodeBigInt(OutputStream s, BigInteger v) throws IOException {
+ return writeRLEBytes(s, v.toByteArray());
+ }
+
+ public static final int writeRLEBytes(OutputStream s, byte ... bytes) throws IOException {
+ return writeRLEBytes(s, bytes, 0, bytes.length);
+ }
+
+ public static final int writeRLEBytes(OutputStream s, byte[] bytes, int off, int len) throws IOException {
+ byte[] lenBytes=encodeInt(s, len);
+ s.write(bytes, off, len);
+ return lenBytes.length + len;
+ }
+
+ public static final byte[] encodeInt(OutputStream s, int v) throws IOException {
+ byte[] bytes={
+ (byte) ((v >> 24) & 0xFF),
+ (byte) ((v >> 16) & 0xFF),
+ (byte) ((v >> 8) & 0xFF),
+ (byte) ( v & 0xFF)
+ };
+ s.write(bytes);
+ return bytes;
+ }
+
public static final String decodeString(InputStream s) throws IOException {
return decodeString(s, StandardCharsets.UTF_8);
}
@@ -146,5 +184,4 @@ public abstract class AbstractPublicKeyEntryDecoder<K extends PublicKey> impleme
| ((bytes[2] & 0xFF) << 8)
| (bytes[3] & 0xFF);
}
-
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
index 62acb4c..05af8ba 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
@@ -21,16 +21,20 @@ package org.apache.sshd.common.config.keys;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
+import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Collections;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.ValidateUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -57,6 +61,20 @@ public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAP
}
@Override
+ public String encodePublicKey(OutputStream s, DSAPublicKey key) throws IOException {
+ ValidateUtils.checkNotNull(key, "No public key provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+ DSAParams keyParams = ValidateUtils.checkNotNull(key.getParams(), "No DSA params available", GenericUtils.EMPTY_OBJECT_ARRAY);
+ encodeString(s, KeyPairProvider.SSH_DSS);
+ encodeBigInt(s, keyParams.getP());
+ encodeBigInt(s, keyParams.getQ());
+ encodeBigInt(s, keyParams.getG());
+ encodeBigInt(s, key.getY());
+
+ return KeyPairProvider.SSH_DSS;
+ }
+
+ @Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("DSA");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
index 29be249..3325a26 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
@@ -19,8 +19,11 @@
package org.apache.sshd.common.config.keys;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
@@ -39,6 +42,7 @@ import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;
/**
@@ -92,6 +96,20 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
}
@Override
+ public String encodePublicKey(OutputStream s, ECPublicKey key) throws IOException {
+ ValidateUtils.checkNotNull(key, "No public key provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+ ECParameterSpec params = ValidateUtils.checkNotNull(key.getParams(), "No EC parameters available", GenericUtils.EMPTY_OBJECT_ARRAY);
+ String curveName = ValidateUtils.checkNotNullAndNotEmpty(ECCurves.getCurveName(params), "Cannot determine curve name", GenericUtils.EMPTY_OBJECT_ARRAY);
+ String keyType = ECCurves.ECDSA_SHA2_PREFIX + curveName;
+ encodeString(s, keyType);
+ // see rfc5656 section 3.1
+ encodeString(s, curveName);
+ ECPointCompression.UNCOMPRESSED.writeECPoint(s, curveName, key.getW());
+ return keyType;
+ }
+
+ @Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
if (SecurityUtils.hasEcc()) {
return SecurityUtils.getKeyFactory("EC");
@@ -186,6 +204,21 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
BigInteger y=octetStringToInteger(yp);
return new ECPoint(x, y);
}
+
+ @Override
+ public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException {
+ Integer elems = ECCurves.getNumPointOctets(curveName);
+ if (elems == null) {
+ throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + curveName + "] cannot determine octets count");
+ }
+
+ int numElements = elems.intValue();
+ AbstractPublicKeyEntryDecoder.encodeInt(s, 1 /* the indicator */ + 2 * numElements);
+ s.write(getIndicatorValue());
+ writeCoordinate(s, "X", p.getAffineX(), numElements);
+ writeCoordinate(s, "Y", p.getAffineY(), numElements);
+ }
+
};
private final byte indicatorValue;
@@ -199,6 +232,40 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
public abstract ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len);
+ public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException {
+ if (s == null) {
+ throw new EOFException("No output stream");
+ }
+
+ throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + p + "] N/A");
+ }
+
+ protected void writeCoordinate(OutputStream s, String n, BigInteger v, int numElements) throws IOException {
+ byte[] vp=v.toByteArray();
+ int startIndex=0;
+ int vLen=vp.length;
+ if (vLen > numElements) {
+ if (vp[0] == 0) { // skip artificial positive sign
+ startIndex++;
+ vLen--;
+ }
+ }
+
+ if (vLen > numElements) {
+ throw new StreamCorruptedException("writeCoordinate(" + name() + ")[" + n + "]"
+ + " value length (" + vLen + ") exceeds max. (" + numElements + ")"
+ + " for " + v);
+ }
+
+ if (vLen < numElements) {
+ byte[] tmp=new byte[numElements];
+ System.arraycopy(vp, startIndex, tmp, numElements - vLen, vLen);
+ vp = tmp;
+ }
+
+ s.write(vp, startIndex, vLen);
+ }
+
public static final Set<ECPointCompression> VALUES=
Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
public static final ECPointCompression fromIndicatorValue(int value) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 1f1de20..c4f808d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -125,14 +125,14 @@ public class KeyUtils {
synchronized(byKeyTypeDecodersMap) {
{
- PublicKeyEntryDecoder<? extends PublicKey> decoder=byKeyTypeDecodersMap.get(keyType);
+ PublicKeyEntryDecoder<? extends PublicKey> decoder=byKeyClassDecodersMap.get(keyType);
if (decoder != null) {
return decoder;
}
}
// in case it is a derived class
- for (PublicKeyEntryDecoder<? extends PublicKey> decoder : byKeyTypeDecodersMap.values()) {
+ for (PublicKeyEntryDecoder<? extends PublicKey> decoder : byKeyClassDecodersMap.values()) {
Class<?> t = decoder.getKeyType();
if (t.isAssignableFrom(keyType)) {
return decoder;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
index a3dbf8b..28708fb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
@@ -19,9 +19,11 @@
package org.apache.sshd.common.config.keys;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
+import java.io.StreamCorruptedException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.PublicKey;
@@ -95,6 +97,19 @@ public class PublicKeyEntry implements Serializable {
return key;
}
+ /**
+ * @param sb The {@link Appendable} instance to encode the data into
+ * @return The {@link PublicKey}
+ * @throws IOException If failed to decode/encode the key
+ * @throws GeneralSecurityException If failed to generate the key
+ * @see #resolvePublicKey()
+ */
+ public PublicKey appendPublicKey(Appendable sb) throws IOException, GeneralSecurityException {
+ PublicKey key = resolvePublicKey();
+ appendPublicKeyEntry(sb, key);
+ return key;
+ }
+
@Override
public int hashCode() {
return Objects.hashCode(getKeyType())
@@ -192,6 +207,42 @@ public class PublicKeyEntry implements Serializable {
}
/**
+ * @param key The {@link PublicKey}
+ * @return The {@code OpenSSH} encoded data
+ * @throws IllegalArgumentException
+ */
+ public static String toString(PublicKey key) throws IllegalArgumentException {
+ try {
+ return appendPublicKeyEntry(new StringBuilder(Byte.MAX_VALUE), key).toString();
+ } catch(IOException e) {
+ throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ") to encode: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes a public key data the same way as the {@link #parsePublicKeyEntry(String)} expects it
+ * @param sb The {@link Appendable} instance to encode the data into
+ * @param key The {@link PublicKey}
+ * @return The updated appendable instance
+ * @throws IOException If failed to append the data
+ */
+ public static final <A extends Appendable> A appendPublicKeyEntry(A sb, PublicKey key) throws IOException {
+ @SuppressWarnings("unchecked")
+ PublicKeyEntryDecoder<PublicKey> decoder = (PublicKeyEntryDecoder<PublicKey>) KeyUtils.getPublicKeyEntryDecoder(key);
+ if (decoder == null) {
+ throw new StreamCorruptedException("Cannot retrived decoder for key=" + key.getAlgorithm());
+ }
+
+ try(ByteArrayOutputStream s=new ByteArrayOutputStream(Byte.MAX_VALUE)) {
+ String keyType = decoder.encodePublicKey(s, key);
+ byte[] bytes = s.toByteArray();
+ String b64Data = Base64.encodeToString(bytes);
+ sb.append(keyType).append(' ').append(b64Data);
+ }
+
+ return sb;
+ }
+ /**
* Character used to denote a comment line in the keys file
*/
public static final char COMMENT_CHAR='#';
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
index 1ac3ab3..fc43bb3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
@@ -21,6 +21,7 @@ package org.apache.sshd.common.config.keys;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Collection;
@@ -53,4 +54,14 @@ public interface PublicKeyEntryDecoder<K extends PublicKey> {
K decodePublicKey(byte ... keyData) throws IOException, GeneralSecurityException;
K decodePublicKey(byte[] keyData, int offset, int length) throws IOException, GeneralSecurityException;
K decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException;
+
+ /**
+ * Encodes the {@link PublicKey} using the {@code OpenSSH} format - same
+ * one used by the {@code decodePublicKey} method(s)
+ * @param s The {@link OutputStream} to write the data to
+ * @param key The {@link PublicKey} - may not be {@code null}
+ * @return The key type value - one of the {@link #getSupportedTypeNames()}
+ * @throws IOException If failed to generate the encoding
+ */
+ String encodePublicKey(OutputStream s, K key) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
index 08d5bc7..f1b5dcb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
@@ -21,6 +21,7 @@ package org.apache.sshd.common.config.keys;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
@@ -30,7 +31,9 @@ import java.security.spec.RSAPublicKeySpec;
import java.util.Collections;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.ValidateUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -55,6 +58,16 @@ public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublic
}
@Override
+ public String encodePublicKey(OutputStream s, RSAPublicKey key) throws IOException {
+ ValidateUtils.checkNotNull(key, "No public key provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+ encodeString(s, KeyPairProvider.SSH_RSA);
+ encodeBigInt(s, key.getPublicExponent());
+ encodeBigInt(s, key.getModulus());
+
+ return KeyPairProvider.SSH_RSA;
+ }
+
+ @Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("RSA");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
index 6421dc0..e817405 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
@@ -87,6 +87,39 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
}
@Override
+ public PublicKey appendPublicKey(Appendable sb) throws IOException, GeneralSecurityException {
+ Map<String,String> options=getLoginOptions();
+ if (!GenericUtils.isEmpty(options)) {
+ int index = 0;
+ for (Map.Entry<String,String> oe : options.entrySet()) {
+ String key = oe.getKey(), value = oe.getValue();
+ if (index > 0) {
+ sb.append(',');
+ }
+ sb.append(key);
+ // TODO figure out a way to remember which options where quoted
+ // TODO figure out a way to remember which options had no value
+ if (!Boolean.TRUE.toString().equals(value)) {
+ sb.append('=').append(value);
+ }
+ index++;
+ }
+
+ if (index > 0) {
+ sb.append(' ');
+ }
+ }
+
+ PublicKey key = super.appendPublicKey(sb);
+ String kc = getComment();
+ if (!GenericUtils.isEmpty(kc)) {
+ sb.append(' ').append(kc);
+ }
+
+ return key;
+ }
+
+ @Override
public String toString() {
String entry = super.toString();
String kc = getComment();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6380cc8d/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
index 1fd66c9..328a257 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
@@ -19,14 +19,19 @@
package org.apache.sshd.server.config.keys;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.InputStreamReader;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.security.PublicKey;
import java.util.Collection;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.IoUtils;
import org.apache.sshd.common.util.ValidateUtils;
@@ -53,7 +58,41 @@ public class AuthorizedKeyEntryTest extends BaseTestSupport {
runAuthorizedKeysTests(AuthorizedKeyEntry.readAuthorizedKeys(url));
}
-
+
+ @Test
+ public void testEncodePublicKeyEntry() throws Exception {
+ URL url = getClass().getResource(AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME);
+ assertNotNull("Missing " + AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME + " resource", url);
+
+ StringBuilder sb = new StringBuilder(Byte.MAX_VALUE);
+ try(BufferedReader rdr = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+ for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
+ line = GenericUtils.trimToEmpty(line);
+ if (GenericUtils.isEmpty(line) || (line.charAt(0) == PublicKeyEntry.COMMENT_CHAR)) {
+ continue;
+ }
+
+ int pos = line.indexOf(' ');
+ String keyType = line.substring(0, pos), data = line;
+ // assume this happens if starts with login options
+ if (KeyUtils.getPublicKeyEntryDecoder(keyType) == null) {
+ data = line.substring(pos + 1).trim();
+ }
+
+ AuthorizedKeyEntry entry = AuthorizedKeyEntry.parseAuthorizedKeyEntry(data);
+ if (sb.length() > 0) {
+ sb.setLength(0);
+ }
+
+ PublicKey key = entry.appendPublicKey(sb);
+ assertNotNull("No key for line=" + line, key);
+
+ String encoded = sb.toString();
+ assertEquals("Mismatched encoded form for line=" + line, data, encoded);
+ }
+ }
+ }
+
@Test
@Ignore("It might cause some exceptions if user's file contains unsupported keys")
public void testReadDefaultAuthorizedKeysFile() throws Exception {