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 2018/09/06 16:03:53 UTC
[43/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common
utilities code from sshd-core into sshd-common (new artifact)
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
new file mode 100644
index 0000000..95db256
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.sshd.common.config.keys.loader.openssh;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Basic support for <A HREF="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?rev=1.1&content-type=text/x-cvsweb-markup">OpenSSH key file(s)</A>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser {
+ public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY";
+ public static final List<String> BEGINNERS =
+ Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+ public static final String END_MARKER = "END OPENSSH PRIVATE KEY";
+ public static final List<String> ENDERS =
+ Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+ public static final String AUTH_MAGIC = "openssh-key-v1";
+ public static final OpenSSHKeyPairResourceParser INSTANCE = new OpenSSHKeyPairResourceParser();
+
+ private static final byte[] AUTH_MAGIC_BYTES = AUTH_MAGIC.getBytes(StandardCharsets.UTF_8);
+ private static final Map<String, PrivateKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP =
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+ private static final Map<Class<?>, PrivateKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP =
+ new HashMap<>();
+
+ static {
+ registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyDecoder.INSTANCE);
+ registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE);
+
+ if (SecurityUtils.isECCSupported()) {
+ registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
+ }
+ if (SecurityUtils.isEDDSACurveSupported()) {
+ registerPrivateKeyEntryDecoder(SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder());
+ }
+ }
+
+ public OpenSSHKeyPairResourceParser() {
+ super(BEGINNERS, ENDERS);
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ stream = validateStreamMagicMarker(resourceKey, stream);
+
+ String cipher = KeyEntryResolver.decodeString(stream);
+ if (!OpenSSHParserContext.IS_NONE_CIPHER.test(cipher)) {
+ throw new NoSuchAlgorithmException("Unsupported cipher: " + cipher);
+ }
+
+ boolean debugEnabled = log.isDebugEnabled();
+ if (debugEnabled) {
+ log.debug("extractKeyPairs({}) cipher={}", resourceKey, cipher);
+ }
+
+ String kdfName = KeyEntryResolver.decodeString(stream);
+ if (!OpenSSHParserContext.IS_NONE_KDF.test(kdfName)) {
+ throw new NoSuchAlgorithmException("Unsupported KDF: " + kdfName);
+ }
+
+ byte[] kdfOptions = KeyEntryResolver.readRLEBytes(stream);
+ if (debugEnabled) {
+ log.debug("extractKeyPairs({}) KDF={}, options={}",
+ resourceKey, kdfName, BufferUtils.toHex(':', kdfOptions));
+ }
+
+ int numKeys = KeyEntryResolver.decodeInt(stream);
+ if (numKeys <= 0) {
+ if (debugEnabled) {
+ log.debug("extractKeyPairs({}) no encoded keys", resourceKey);
+ }
+ return Collections.emptyList();
+ }
+
+ List<PublicKey> publicKeys = new ArrayList<>(numKeys);
+ OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfName, kdfOptions);
+ boolean traceEnabled = log.isTraceEnabled();
+ for (int index = 1; index <= numKeys; index++) {
+ PublicKey pubKey = readPublicKey(resourceKey, context, stream);
+ ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", index, resourceKey);
+ if (traceEnabled) {
+ log.trace("extractKeyPairs({}) read public key #{}: {} {}",
+ resourceKey, index, KeyUtils.getKeyType(pubKey), KeyUtils.getFingerPrint(pubKey));
+ }
+ publicKeys.add(pubKey);
+ }
+
+ byte[] privateData = KeyEntryResolver.readRLEBytes(stream);
+ try (InputStream bais = new ByteArrayInputStream(privateData)) {
+ return readPrivateKeys(resourceKey, context, publicKeys, passwordProvider, bais);
+ }
+ }
+
+ protected PublicKey readPublicKey(
+ String resourceKey, OpenSSHParserContext context, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ byte[] keyData = KeyEntryResolver.readRLEBytes(stream);
+ try (InputStream bais = new ByteArrayInputStream(keyData)) {
+ String keyType = KeyEntryResolver.decodeString(bais);
+ PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
+ if (decoder == null) {
+ throw new NoSuchAlgorithmException("Unsupported key type (" + keyType + ") in " + resourceKey);
+ }
+
+ return decoder.decodePublicKey(keyType, bais);
+ }
+ }
+
+ protected List<KeyPair> readPrivateKeys(
+ String resourceKey, OpenSSHParserContext context, Collection<? extends PublicKey> publicKeys,
+ FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ if (GenericUtils.isEmpty(publicKeys)) {
+ return Collections.emptyList();
+ }
+
+ boolean traceEnabled = log.isTraceEnabled();
+ int check1 = KeyEntryResolver.decodeInt(stream);
+ int check2 = KeyEntryResolver.decodeInt(stream);
+ if (traceEnabled) {
+ log.trace("readPrivateKeys({}) check1=0x{}, check2=0x{}",
+ resourceKey, Integer.toHexString(check1), Integer.toHexString(check2));
+ }
+
+ List<KeyPair> keyPairs = new ArrayList<>(publicKeys.size());
+ for (PublicKey pubKey : publicKeys) {
+ String pubType = KeyUtils.getKeyType(pubKey);
+ int keyIndex = keyPairs.size() + 1;
+ if (traceEnabled) {
+ log.trace("extractKeyPairs({}) read private key #{}: {}",
+ resourceKey, keyIndex, pubType);
+ }
+
+ Map.Entry<PrivateKey, String> prvData = readPrivateKey(resourceKey, context, pubType, passwordProvider, stream);
+ PrivateKey prvKey = (prvData == null) ? null : prvData.getKey();
+ ValidateUtils.checkNotNull(prvKey, "Empty private key #%d in %s", keyIndex, resourceKey);
+
+ String prvType = KeyUtils.getKeyType(prvKey);
+ ValidateUtils.checkTrue(Objects.equals(pubType, prvType),
+ "Mismatched public (%s) vs. private (%s) key type #%d in %s",
+ pubType, prvType, keyIndex, resourceKey);
+
+ if (traceEnabled) {
+ log.trace("extractKeyPairs({}) add private key #{}: {} {}",
+ resourceKey, keyIndex, prvType, prvData.getValue());
+ }
+ keyPairs.add(new KeyPair(pubKey, prvKey));
+ }
+
+ return keyPairs;
+ }
+
+ protected SimpleImmutableEntry<PrivateKey, String> readPrivateKey(
+ String resourceKey, OpenSSHParserContext context, String keyType, FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ String prvType = KeyEntryResolver.decodeString(stream);
+ if (!Objects.equals(keyType, prvType)) {
+ throw new StreamCorruptedException("Mismatched private key type: "
+ + ", expected=" + keyType + ", actual=" + prvType
+ + " in " + resourceKey);
+ }
+
+ PrivateKeyEntryDecoder<?, ?> decoder = getPrivateKeyEntryDecoder(prvType);
+ if (decoder == null) {
+ throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey);
+ }
+
+ PrivateKey prvKey = decoder.decodePrivateKey(prvType, passwordProvider, stream);
+ if (prvKey == null) {
+ throw new InvalidKeyException("Cannot parse key type (" + prvType + ") in " + resourceKey);
+ }
+
+ String comment = KeyEntryResolver.decodeString(stream);
+ return new SimpleImmutableEntry<>(prvKey, comment);
+ }
+
+ protected <S extends InputStream> S validateStreamMagicMarker(String resourceKey, S stream) throws IOException {
+ byte[] actual = new byte[AUTH_MAGIC_BYTES.length];
+ IoUtils.readFully(stream, actual);
+ if (!Arrays.equals(AUTH_MAGIC_BYTES, actual)) {
+ throw new StreamCorruptedException(resourceKey + ": Mismatched magic marker value: " + BufferUtils.toHex(':', actual));
+ }
+
+ int eos = stream.read();
+ if (eos == -1) {
+ throw new EOFException(resourceKey + ": Premature EOF after magic marker value");
+ }
+
+ if (eos != 0) {
+ throw new StreamCorruptedException(resourceKey + ": Missing EOS after magic marker value: 0x" + Integer.toHexString(eos));
+ }
+
+ return stream;
+ }
+
+ /**
+ * @param decoder The decoder to register
+ * @throws IllegalArgumentException if no decoder or not key type or no
+ * supported names for the decoder
+ * @see PrivateKeyEntryDecoder#getPublicKeyType()
+ * @see PrivateKeyEntryDecoder#getSupportedTypeNames()
+ */
+ public static void registerPrivateKeyEntryDecoder(PrivateKeyEntryDecoder<?, ?> decoder) {
+ Objects.requireNonNull(decoder, "No decoder specified");
+
+ Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
+ Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
+ synchronized (BY_KEY_CLASS_DECODERS_MAP) {
+ BY_KEY_CLASS_DECODERS_MAP.put(pubType, decoder);
+ BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
+ }
+
+ Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key type");
+ synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+ for (String n : names) {
+ PrivateKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
+ if (prev != null) {
+ //noinspection UnnecessaryContinue
+ continue; // debug breakpoint
+ }
+ }
+ }
+ }
+
+ /**
+ * @param keyType The {@code OpenSSH} key type string - e.g., {@code ssh-rsa, ssh-dss}
+ * - ignored if {@code null}/empty
+ * @return The registered {@link PrivateKeyEntryDecoder} or {code null} if not found
+ */
+ public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(String keyType) {
+ if (GenericUtils.isEmpty(keyType)) {
+ return null;
+ }
+
+ synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+ return BY_KEY_TYPE_DECODERS_MAP.get(keyType);
+ }
+ }
+
+ /**
+ * @param kp The {@link KeyPair} to examine - ignored if {@code null}
+ * @return The matching {@link PrivateKeyEntryDecoder} provided <U>both</U>
+ * the public and private keys have the same decoder - {@code null} if no
+ * match found
+ * @see #getPrivateKeyEntryDecoder(Key)
+ */
+ public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(KeyPair kp) {
+ if (kp == null) {
+ return null;
+ }
+
+ PrivateKeyEntryDecoder<?, ?> d1 = getPrivateKeyEntryDecoder(kp.getPublic());
+ PrivateKeyEntryDecoder<?, ?> d2 = getPrivateKeyEntryDecoder(kp.getPrivate());
+ if (d1 == d2) {
+ return d1;
+ } else {
+ return null; // some kind of mixed keys...
+ }
+ }
+
+ /**
+ * @param key The {@link Key} (public or private) - ignored if {@code null}
+ * @return The registered {@link PrivateKeyEntryDecoder} for this key or {code null} if no match found
+ * @see #getPrivateKeyEntryDecoder(Class)
+ */
+ public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(Key key) {
+ if (key == null) {
+ return null;
+ } else {
+ return getPrivateKeyEntryDecoder(key.getClass());
+ }
+ }
+
+ /**
+ * @param keyType The key {@link Class} - ignored if {@code null} or not a {@link Key}
+ * compatible type
+ * @return The registered {@link PrivateKeyEntryDecoder} or {code null} if no match found
+ */
+ public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(Class<?> keyType) {
+ if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
+ return null;
+ }
+
+ synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+ PrivateKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
+ if (decoder != null) {
+ return decoder;
+ }
+
+ // in case it is a derived class
+ for (PrivateKeyEntryDecoder<?, ?> dec : BY_KEY_CLASS_DECODERS_MAP.values()) {
+ Class<?> pubType = dec.getPublicKeyType();
+ Class<?> prvType = dec.getPrivateKeyType();
+ if (pubType.isAssignableFrom(keyType) || prvType.isAssignableFrom(keyType)) {
+ return dec;
+ }
+ }
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java
new file mode 100644
index 0000000..07f2a9a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.openssh;
+
+import java.util.function.Predicate;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHParserContext {
+ public static final String NONE_CIPHER = "none";
+ public static final Predicate<String> IS_NONE_CIPHER = c -> GenericUtils.isEmpty(c) || NONE_CIPHER.equalsIgnoreCase(c);
+
+ public static final String NONE_KDF = "none";
+ public static final Predicate<String> IS_NONE_KDF = c -> GenericUtils.isEmpty(c) || NONE_KDF.equalsIgnoreCase(c);
+
+ private String cipherName;
+ private String kdfName;
+ private byte[] kdfOptions;
+
+ public OpenSSHParserContext() {
+ super();
+ }
+
+ public OpenSSHParserContext(String cipherName, String kdfName, byte... kdfOptions) {
+ this.cipherName = cipherName;
+ this.kdfName = kdfName;
+ this.kdfOptions = kdfOptions;
+ }
+
+ public String getCipherName() {
+ return cipherName;
+ }
+
+ public void setCipherName(String cipherName) {
+ this.cipherName = cipherName;
+ }
+
+ public String getKdfName() {
+ return kdfName;
+ }
+
+ public void setKdfName(String kdfName) {
+ this.kdfName = kdfName;
+ }
+
+ public byte[] getKdfOptions() {
+ return kdfOptions;
+ }
+
+ public void setKdfOptions(byte[] kdfOptions) {
+ this.kdfOptions = kdfOptions;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + "[cipher=" + getCipherName()
+ + ", kdfName=" + getKdfName()
+ + ", kdfOptions=" + BufferUtils.toHex(':', getKdfOptions())
+ + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
new file mode 100644
index 0000000..72e003f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.openssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
+ public static final BigInteger DEFAULT_PUBLIC_EXPONENT = new BigInteger("65537");
+ public static final OpenSSHRSAPrivateKeyDecoder INSTANCE = new OpenSSHRSAPrivateKeyDecoder();
+
+ public OpenSSHRSAPrivateKeyDecoder() {
+ super(RSAPublicKey.class, RSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+ }
+
+ @Override
+ public RSAPrivateKey decodePrivateKey(String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
+ throws IOException, GeneralSecurityException {
+ if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
+ throw new InvalidKeySpecException("Unexpected key type: " + keyType);
+ }
+
+ BigInteger n = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger e = KeyEntryResolver.decodeBigInt(keyData);
+ if (!Objects.equals(e, DEFAULT_PUBLIC_EXPONENT)) {
+ log.warn("decodePrivateKey({}) non-standard RSA exponent found: {}", keyType, e);
+ }
+
+ BigInteger d = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger inverseQmodP = KeyEntryResolver.decodeBigInt(keyData);
+ Objects.requireNonNull(inverseQmodP, "Missing iqmodp"); // TODO run some validation on it
+ BigInteger p = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger q = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger modulus = p.multiply(q);
+ if (!Objects.equals(n, modulus)) {
+ log.warn("decodePrivateKey({}) mismatched modulus values: encoded={}, calculated={}",
+ keyType, n, modulus);
+ }
+
+ return generatePrivateKey(new RSAPrivateKeySpec(n, d));
+ }
+
+ @Override
+ public boolean isPublicKeyRecoverySupported() {
+ return true;
+ }
+
+ @Override
+ public RSAPublicKey recoverPublicKey(RSAPrivateKey privateKey) throws GeneralSecurityException {
+ return KeyUtils.recoverRSAPublicKey(privateKey);
+ }
+
+ @Override
+ public RSAPublicKey clonePublicKey(RSAPublicKey key) throws GeneralSecurityException {
+ if (key == null) {
+ return null;
+ } else {
+ return generatePublicKey(new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()));
+ }
+ }
+
+ @Override
+ public RSAPrivateKey clonePrivateKey(RSAPrivateKey key) throws GeneralSecurityException {
+ if (key == null) {
+ return null;
+ }
+
+ if (!(key instanceof RSAPrivateCrtKey)) {
+ throw new InvalidKeyException("Cannot clone a non-RSAPrivateCrtKey: " + key.getClass().getSimpleName());
+ }
+
+ RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) key;
+ return generatePrivateKey(
+ new RSAPrivateCrtKeySpec(
+ rsaPrv.getModulus(),
+ rsaPrv.getPublicExponent(),
+ rsaPrv.getPrivateExponent(),
+ rsaPrv.getPrimeP(),
+ rsaPrv.getPrimeQ(),
+ rsaPrv.getPrimeExponentP(),
+ rsaPrv.getPrimeExponentQ(),
+ rsaPrv.getCrtCoefficient()));
+ }
+
+ @Override
+ public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+ return SecurityUtils.getKeyPairGenerator(KeyUtils.RSA_ALGORITHM);
+ }
+
+ @Override
+ public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+ return SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..bee13d6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.security.auth.login.CredentialException;
+import javax.security.auth.login.FailedLoginException;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.config.keys.loader.PrivateKeyEncryptionContext;
+import org.apache.sshd.common.config.keys.loader.PrivateKeyObfuscator;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * Base class for PEM file key-pair loaders
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPEMResourceKeyPairParser
+ extends AbstractKeyPairResourceParser
+ implements KeyPairPEMResourceParser {
+ private final String algo;
+ private final String algId;
+
+ protected AbstractPEMResourceKeyPairParser(String algo, String algId, List<String> beginners, List<String> enders) {
+ super(beginners, enders);
+ this.algo = ValidateUtils.checkNotNullAndNotEmpty(algo, "No encryption algorithm provided");
+ this.algId = ValidateUtils.checkNotNullAndNotEmpty(algId, "No algorithm identifier provided");
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return algo;
+ }
+
+ @Override
+ public String getAlgorithmIdentifier() {
+ return algId;
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, List<String> lines)
+ throws IOException, GeneralSecurityException {
+ if (GenericUtils.isEmpty(lines)) {
+ return Collections.emptyList();
+ }
+
+ Boolean encrypted = null;
+ byte[] initVector = null;
+ String algInfo = null;
+ int dataStartIndex = -1;
+ for (int index = 0; index < lines.size(); index++) {
+ String line = GenericUtils.trimToEmpty(lines.get(index));
+ if (GenericUtils.isEmpty(line)) {
+ continue;
+ }
+
+ // check if header line - if not, assume data lines follow
+ int headerPos = line.indexOf(':');
+ if (headerPos < 0) {
+ dataStartIndex = index;
+ break;
+ }
+
+ if (line.startsWith("Proc-Type:")) {
+ if (encrypted != null) {
+ throw new StreamCorruptedException("Multiple encryption indicators in " + resourceKey);
+ }
+
+ line = line.substring(headerPos + 1).trim();
+ line = line.toUpperCase();
+ encrypted = Boolean.valueOf(line.contains("ENCRYPTED"));
+ } else if (line.startsWith("DEK-Info:")) {
+ if ((initVector != null) || (algInfo != null)) {
+ throw new StreamCorruptedException("Multiple encryption settings in " + resourceKey);
+ }
+
+ line = line.substring(headerPos + 1).trim();
+ headerPos = line.indexOf(',');
+ if (headerPos < 0) {
+ throw new StreamCorruptedException(resourceKey + ": Missing encryption data values separator in line '" + line + "'");
+ }
+
+ algInfo = line.substring(0, headerPos).trim();
+
+ String algInitVector = line.substring(headerPos + 1).trim();
+ initVector = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, algInitVector);
+ }
+ }
+
+ if (dataStartIndex < 0) {
+ throw new StreamCorruptedException("No data lines (only headers or empty) found in " + resourceKey);
+ }
+
+ List<String> dataLines = lines.subList(dataStartIndex, lines.size());
+ if ((encrypted != null) || (algInfo != null) || (initVector != null)) {
+ if (passwordProvider == null) {
+ throw new CredentialException("Missing password provider for encrypted resource=" + resourceKey);
+ }
+
+ String password = passwordProvider.getPassword(resourceKey);
+ if (GenericUtils.isEmpty(password)) {
+ throw new FailedLoginException("No password data for encrypted resource=" + resourceKey);
+ }
+
+ PrivateKeyEncryptionContext encContext = new PrivateKeyEncryptionContext(algInfo);
+ encContext.setPassword(password);
+ encContext.setInitVector(initVector);
+ byte[] encryptedData = KeyPairResourceParser.extractDataBytes(dataLines);
+ byte[] decodedData = applyPrivateKeyCipher(encryptedData, encContext, false);
+ try (InputStream bais = new ByteArrayInputStream(decodedData)) {
+ return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, bais);
+ }
+ }
+
+ return super.extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, dataLines);
+ }
+
+ protected byte[] applyPrivateKeyCipher(byte[] bytes, PrivateKeyEncryptionContext encContext, boolean encryptIt) throws GeneralSecurityException {
+ String cipherName = encContext.getCipherName();
+ PrivateKeyObfuscator o = encContext.resolvePrivateKeyObfuscator();
+ if (o == null) {
+ throw new NoSuchAlgorithmException("decryptPrivateKeyData(" + encContext + ")[encrypt=" + encryptIt + "] unknown cipher: " + cipherName);
+ }
+
+ if (encryptIt) {
+ byte[] initVector = encContext.getInitVector();
+ if (GenericUtils.isEmpty(initVector)) {
+ initVector = o.generateInitializationVector(encContext);
+ encContext.setInitVector(initVector);
+ }
+ }
+
+ return o.applyPrivateKeyCipher(bytes, encContext, encryptIt);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..0c357af
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+ // Not exactly according to standard but good enough
+ public static final String BEGIN_MARKER = "BEGIN DSA PRIVATE KEY";
+ public static final List<String> BEGINNERS =
+ Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+ public static final String END_MARKER = "END DSA PRIVATE KEY";
+ public static final List<String> ENDERS =
+ Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+ /**
+ * @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</A>
+ */
+ public static final String DSS_OID = "1.2.840.10040.4.1";
+
+ public static final DSSPEMResourceKeyPairParser INSTANCE = new DSSPEMResourceKeyPairParser();
+
+ public DSSPEMResourceKeyPairParser() {
+ super(KeyUtils.DSS_ALGORITHM, DSS_OID, BEGINNERS, ENDERS);
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
+ return Collections.singletonList(kp);
+ }
+
+ /**
+ * <p>The ASN.1 syntax for the private key:</P>
+ * <pre><code>
+ * DSAPrivateKey ::= SEQUENCE {
+ * version Version,
+ * p INTEGER,
+ * q INTEGER,
+ * g INTEGER,
+ * y INTEGER,
+ * x INTEGER
+ * }
+ * </code></pre>
+ * @param kf The {@link KeyFactory} To use to generate the keys
+ * @param s The {@link InputStream} containing the encoded bytes
+ * @param okToClose <code>true</code> if the method may close the input
+ * stream regardless of success or failure
+ * @return The recovered {@link KeyPair}
+ * @throws IOException If failed to read or decode the bytes
+ * @throws GeneralSecurityException If failed to generate the keys
+ */
+ public static KeyPair decodeDSSKeyPair(KeyFactory kf, InputStream s, boolean okToClose)
+ throws IOException, GeneralSecurityException {
+ ASN1Object sequence;
+ try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) {
+ sequence = parser.readObject();
+ }
+
+ if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
+ throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+ }
+
+ // Parse inside the sequence
+ try (DERParser parser = sequence.createParser()) {
+ // Skip version
+ ASN1Object version = parser.readObject();
+ if (version == null) {
+ throw new StreamCorruptedException("No version");
+ }
+
+ BigInteger p = parser.readObject().asInteger();
+ BigInteger q = parser.readObject().asInteger();
+ BigInteger g = parser.readObject().asInteger();
+ BigInteger y = parser.readObject().asInteger();
+ BigInteger x = parser.readObject().asInteger();
+ PublicKey pubKey = kf.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+ PrivateKey prvKey = kf.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
+ return new KeyPair(pubKey, prvKey);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..dd8d2ea
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+ public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
+ public static final List<String> BEGINNERS =
+ Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+ public static final String END_MARKER = "END EC PRIVATE KEY";
+ public static final List<String> ENDERS =
+ Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+ /**
+ * @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.5">RFC-3279 section 2.3.5</A>
+ */
+ public static final String ECDSA_OID = "1.2.840.10045.2.1";
+
+ public static final ECDSAPEMResourceKeyPairParser INSTANCE = new ECDSAPEMResourceKeyPairParser();
+
+ public ECDSAPEMResourceKeyPairParser() {
+ super(KeyUtils.EC_ALGORITHM, ECDSA_OID, BEGINNERS, ENDERS);
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(stream, false);
+ if (!SecurityUtils.isECCSupported()) {
+ throw new NoSuchProviderException("ECC not supported");
+ }
+
+ KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
+ ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey());
+ ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue());
+ KeyPair kp = new KeyPair(pubKey, prvKey);
+ return Collections.singletonList(kp);
+ }
+
+ /**
+ * <P>ASN.1 syntax according to rfc5915 is:</P></BR>
+ * <PRE><CODE>
+ * ECPrivateKey ::= SEQUENCE {
+ * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ * privateKey OCTET STRING,
+ * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ * publicKey [1] BIT STRING OPTIONAL
+ * }
+ * </CODE></PRE>
+ * <P><I>ECParameters</I> syntax according to RFC5480:</P></BR>
+ * <PRE><CODE>
+ * ECParameters ::= CHOICE {
+ * namedCurve OBJECT IDENTIFIER
+ * -- implicitCurve NULL
+ * -- specifiedCurve SpecifiedECDomain
+ * }
+ * </CODE></PRE>
+ * @param inputStream The {@link InputStream} containing the DER encoded data
+ * @param okToClose {@code true} if OK to close the DER stream once parsing complete
+ * @return The decoded {@link SimpleImmutableEntry} of {@link ECPublicKeySpec} and {@link ECPrivateKeySpec}
+ * @throws IOException If failed to to decode the DER stream
+ */
+ public static SimpleImmutableEntry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(InputStream inputStream, boolean okToClose) throws IOException {
+ ASN1Object sequence;
+ try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
+ sequence = parser.readObject();
+ }
+
+ if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
+ throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+ }
+
+ // Parse inside the sequence
+ try (DERParser parser = sequence.createParser()) {
+ ECPrivateKeySpec prvSpec = decodeECPrivateKeySpec(parser);
+ ECCurves curve = ECCurves.fromCurveParameters(prvSpec.getParams());
+ if (curve == null) {
+ throw new StreamCorruptedException("Unknown curve");
+ }
+
+ ECPoint w = decodeECPublicKeyValue(curve, parser);
+ ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, prvSpec.getParams());
+ return new SimpleImmutableEntry<>(pubSpec, prvSpec);
+ }
+ }
+
+ public static final ECPrivateKeySpec decodeECPrivateKeySpec(DERParser parser) throws IOException {
+ // see openssl asn1parse -inform PEM -in ...file... -dump
+ ASN1Object versionObject = parser.readObject(); // Skip version
+ if (versionObject == null) {
+ throw new StreamCorruptedException("No version");
+ }
+
+ // as per RFC-5915 section 3
+ BigInteger version = versionObject.asInteger();
+ if (!BigInteger.ONE.equals(version)) {
+ throw new StreamCorruptedException("Bad version value: " + version);
+ }
+
+ ASN1Object keyObject = parser.readObject();
+ if (keyObject == null) {
+ throw new StreamCorruptedException("No private key value");
+ }
+
+ ASN1Type objType = keyObject.getObjType();
+ if (!ASN1Type.OCTET_STRING.equals(objType)) {
+ throw new StreamCorruptedException("Non-matching private key object type: " + objType);
+ }
+
+ ASN1Object paramsObject = parser.readObject();
+ if (paramsObject == null) {
+ throw new StreamCorruptedException("No parameters value");
+ }
+
+ // TODO make sure params object tag is 0xA0
+
+ final List<Integer> curveOID;
+ try (DERParser paramsParser = paramsObject.createParser()) {
+ ASN1Object namedCurve = paramsParser.readObject();
+ if (namedCurve == null) {
+ throw new StreamCorruptedException("Missing named curve parameter");
+ }
+
+ curveOID = namedCurve.asOID();
+ }
+
+ ECCurves curve = ECCurves.fromOIDValue(curveOID);
+ if (curve == null) {
+ throw new StreamCorruptedException("Unknown curve OID: " + curveOID);
+ }
+
+ BigInteger s = ECCurves.octetStringToInteger(keyObject.getPureValueBytes());
+ return new ECPrivateKeySpec(s, curve.getParameters());
+ }
+
+ /**
+ * <P>ASN.1 syntax according to rfc5915 is:</P></BR>
+ * <PRE>
+ * publicKey [1] BIT STRING OPTIONAL
+ * </PRE>
+ * @param curve The {@link ECCurves} curve
+ * @param parser The {@link DERParser} assumed to be positioned at the
+ * start of the data
+ * @return The encoded {@link ECPoint}
+ * @throws IOException If failed to create the point
+ */
+ public static final ECPoint decodeECPublicKeyValue(ECCurves curve, DERParser parser) throws IOException {
+ // see openssl asn1parse -inform PEM -in ...file... -dump
+ ASN1Object dataObject = parser.readObject();
+ if (dataObject == null) {
+ throw new StreamCorruptedException("No public key data bytes");
+ }
+
+ try (DERParser dataParser = dataObject.createParser()) {
+ ASN1Object pointData = dataParser.readObject();
+ if (pointData == null) {
+ throw new StreamCorruptedException("Missing public key data parameter");
+ }
+
+ ASN1Type objType = pointData.getObjType();
+ if (!ASN1Type.BIT_STRING.equals(objType)) {
+ throw new StreamCorruptedException("Non-matching public key object type: " + objType);
+ }
+
+ // see https://tools.ietf.org/html/rfc5480#section-2.2
+ byte[] octets = pointData.getValue();
+ return ECCurves.octetStringToEcPoint(octets);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
new file mode 100644
index 0000000..07c7e44
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyPairPEMResourceParser extends KeyPairResourceParser {
+ /**
+ * @return The encryption algorithm name - e.g., "RSA", "DSA"
+ */
+ String getAlgorithm();
+
+ /**
+ * @return The OID used to identify this algorithm in DER encodings - e.g., RSA=1.2.840.113549.1.1.1
+ */
+ String getAlgorithmIdentifier();
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java
new file mode 100644
index 0000000..b6749da
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class PEMResourceParserUtils {
+ public static final KeyPairResourceParser PROXY = new KeyPairResourceParser() {
+ @Override
+ public Collection<KeyPair> loadKeyPairs(
+ String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+ throws IOException, GeneralSecurityException {
+ @SuppressWarnings("synthetic-access")
+ KeyPairResourceParser proxy = PROXY_HOLDER.get();
+ return (proxy == null) ? Collections.<KeyPair>emptyList() : proxy.loadKeyPairs(resourceKey, passwordProvider, lines);
+ }
+
+ @Override
+ public boolean canExtractKeyPairs(String resourceKey, List<String> lines)
+ throws IOException, GeneralSecurityException {
+ @SuppressWarnings("synthetic-access")
+ KeyPairResourceParser proxy = PROXY_HOLDER.get();
+ return (proxy != null) && proxy.canExtractKeyPairs(resourceKey, lines);
+ }
+ };
+
+ private static final Map<String, KeyPairPEMResourceParser> BY_OID_MAP = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ private static final Map<String, KeyPairPEMResourceParser> BY_ALGORITHM_MAP = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ private static final AtomicReference<KeyPairResourceParser> PROXY_HOLDER = new AtomicReference<>(KeyPairResourceParser.EMPTY);
+
+ static {
+ registerPEMResourceParser(RSAPEMResourceKeyPairParser.INSTANCE);
+ registerPEMResourceParser(DSSPEMResourceKeyPairParser.INSTANCE);
+ registerPEMResourceParser(ECDSAPEMResourceKeyPairParser.INSTANCE);
+ registerPEMResourceParser(PKCS8PEMResourceKeyPairParser.INSTANCE);
+ }
+
+ private PEMResourceParserUtils() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void registerPEMResourceParser(KeyPairPEMResourceParser parser) {
+ Objects.requireNonNull(parser, "No parser to register");
+ synchronized (BY_OID_MAP) {
+ BY_OID_MAP.put(ValidateUtils.checkNotNullAndNotEmpty(parser.getAlgorithmIdentifier(), "No OID value"), parser);
+ }
+
+ synchronized (BY_ALGORITHM_MAP) {
+ BY_ALGORITHM_MAP.put(ValidateUtils.checkNotNullAndNotEmpty(parser.getAlgorithm(), "No algorithm value"), parser);
+ // Use a copy in order to avoid concurrent modifications
+ PROXY_HOLDER.set(KeyPairResourceParser.aggregate(new ArrayList<>(BY_ALGORITHM_MAP.values())));
+ }
+ }
+
+ public static KeyPairPEMResourceParser getPEMResourceParserByOid(String oid) {
+ if (GenericUtils.isEmpty(oid)) {
+ return null;
+ }
+
+ synchronized (BY_OID_MAP) {
+ return BY_OID_MAP.get(oid);
+ }
+ }
+
+ public static KeyPairPEMResourceParser getPEMResourceParserByAlgorithm(String algorithm) {
+ if (GenericUtils.isEmpty(algorithm)) {
+ return null;
+ }
+
+ synchronized (BY_ALGORITHM_MAP) {
+ return BY_ALGORITHM_MAP.get(algorithm);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
new file mode 100644
index 0000000..b333f23
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+ // Not exactly according to standard but good enough
+ public static final String BEGIN_MARKER = "BEGIN PRIVATE KEY";
+ public static final List<String> BEGINNERS =
+ Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+ public static final String END_MARKER = "END PRIVATE KEY";
+ public static final List<String> ENDERS =
+ Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+ public static final String PKCS8_FORMAT = "PKCS#8";
+
+ public static final PKCS8PEMResourceKeyPairParser INSTANCE = new PKCS8PEMResourceKeyPairParser();
+
+ public PKCS8PEMResourceKeyPairParser() {
+ super(PKCS8_FORMAT, PKCS8_FORMAT, BEGINNERS, ENDERS);
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ // Save the data before getting the algorithm OID since we will need it
+ byte[] encBytes = IoUtils.toByteArray(stream);
+ List<Integer> oidAlgorithm = getPKCS8AlgorithmIdentifier(encBytes);
+ PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes, passwordProvider);
+ PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
+ "Failed to recover public key of OID=%s", oidAlgorithm);
+ KeyPair kp = new KeyPair(pubKey, prvKey);
+ return Collections.singletonList(kp);
+ }
+
+ public static PrivateKey decodePEMPrivateKeyPKCS8(
+ List<Integer> oidAlgorithm, byte[] keyBytes, FilePasswordProvider passwordProvider)
+ throws GeneralSecurityException {
+ ValidateUtils.checkNotNullAndNotEmpty(oidAlgorithm, "No PKCS8 algorithm OID");
+ return decodePEMPrivateKeyPKCS8(GenericUtils.join(oidAlgorithm, '.'), keyBytes, passwordProvider);
+ }
+
+ public static PrivateKey decodePEMPrivateKeyPKCS8(
+ String oid, byte[] keyBytes, FilePasswordProvider passwordProvider)
+ throws GeneralSecurityException {
+ KeyPairPEMResourceParser parser =
+ PEMResourceParserUtils.getPEMResourceParserByOid(
+ ValidateUtils.checkNotNullAndNotEmpty(oid, "No PKCS8 algorithm OID"));
+ if (parser == null) {
+ throw new NoSuchAlgorithmException("decodePEMPrivateKeyPKCS8(" + oid + ") unknown algorithm identifier");
+ }
+
+ String algorithm = ValidateUtils.checkNotNullAndNotEmpty(parser.getAlgorithm(), "No parser algorithm");
+ KeyFactory factory = SecurityUtils.getKeyFactory(algorithm);
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+ return factory.generatePrivate(keySpec);
+ }
+
+ public static List<Integer> getPKCS8AlgorithmIdentifier(byte[] input) throws IOException {
+ try (DERParser parser = new DERParser(input)) {
+ return getPKCS8AlgorithmIdentifier(parser);
+ }
+ }
+
+ /**
+ * According to the standard:
+ * <PRE><CODE>
+ * PrivateKeyInfo ::= SEQUENCE {
+ * version Version,
+ * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+ * privateKey PrivateKey,
+ * attributes [0] IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER
+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ * PrivateKey ::= OCTET STRING
+ * Attributes ::= SET OF Attribute
+ * AlgorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ * </CODE></PRE>
+ * @param parser The {@link DERParser} to use
+ * @return The PKCS8 algorithm OID
+ * @throws IOException If malformed data
+ * @see #getPKCS8AlgorithmIdentifier(ASN1Object)
+ */
+ public static List<Integer> getPKCS8AlgorithmIdentifier(DERParser parser) throws IOException {
+ return getPKCS8AlgorithmIdentifier(parser.readObject());
+ }
+
+ public static List<Integer> getPKCS8AlgorithmIdentifier(ASN1Object privateKeyInfo) throws IOException {
+ try (DERParser parser = privateKeyInfo.createParser()) {
+ // Skip version
+ ASN1Object versionObject = parser.readObject();
+ if (versionObject == null) {
+ throw new StreamCorruptedException("No version");
+ }
+
+ ASN1Object privateKeyAlgorithm = parser.readObject();
+ if (privateKeyAlgorithm == null) {
+ throw new StreamCorruptedException("No private key algorithm");
+ }
+
+ try (DERParser oidParser = privateKeyAlgorithm.createParser()) {
+ ASN1Object oid = oidParser.readObject();
+ return oid.asOID();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..d760aaf
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.config.keys.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+ // Not exactly according to standard but good enough
+ public static final String BEGIN_MARKER = "BEGIN RSA PRIVATE KEY";
+ public static final List<String> BEGINNERS =
+ Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+ public static final String END_MARKER = "END RSA PRIVATE KEY";
+ public static final List<String> ENDERS =
+ Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+ /**
+ * @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</A>
+ */
+ public static final String RSA_OID = "1.2.840.113549.1.1.1";
+
+ public static final RSAPEMResourceKeyPairParser INSTANCE = new RSAPEMResourceKeyPairParser();
+
+ public RSAPEMResourceKeyPairParser() {
+ super(KeyUtils.RSA_ALGORITHM, RSA_OID, BEGINNERS, ENDERS);
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+ throws IOException, GeneralSecurityException {
+ KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
+ return Collections.singletonList(kp);
+ }
+
+ /**
+ * <p>The ASN.1 syntax for the private key as per RFC-3447 section A.1.2:</P>
+ * <pre><code>
+ * RSAPrivateKey ::= SEQUENCE {
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+ * }
+ * </code></pre>
+ * @param kf The {@link KeyFactory} To use to generate the keys
+ * @param s The {@link InputStream} containing the encoded bytes
+ * @param okToClose <code>true</code> if the method may close the input
+ * stream regardless of success or failure
+ * @return The recovered {@link KeyPair}
+ * @throws IOException If failed to read or decode the bytes
+ * @throws GeneralSecurityException If failed to generate the keys
+ */
+ public static KeyPair decodeRSAKeyPair(KeyFactory kf, InputStream s, boolean okToClose)
+ throws IOException, GeneralSecurityException {
+ ASN1Object sequence;
+ try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) {
+ sequence = parser.readObject();
+ }
+
+ if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
+ throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+ }
+
+ try (DERParser parser = sequence.createParser()) {
+ // Skip version
+ ASN1Object versionObject = parser.readObject();
+ if (versionObject == null) {
+ throw new StreamCorruptedException("No version");
+ }
+
+ // as per RFC-3447 section A.1.2
+ BigInteger version = versionObject.asInteger();
+ if (!BigInteger.ZERO.equals(version)) {
+ throw new StreamCorruptedException("Multi-primes N/A");
+ }
+
+ BigInteger modulus = parser.readObject().asInteger();
+ BigInteger publicExp = parser.readObject().asInteger();
+ PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, publicExp));
+
+ BigInteger privateExp = parser.readObject().asInteger();
+ BigInteger primeP = parser.readObject().asInteger();
+ BigInteger primeQ = parser.readObject().asInteger();
+ BigInteger primeExponentP = parser.readObject().asInteger();
+ BigInteger primeExponentQ = parser.readObject().asInteger();
+ BigInteger crtCoef = parser.readObject().asInteger();
+ RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec(
+ modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef);
+ PrivateKey prvKey = kf.generatePrivate(prvSpec);
+ return new KeyPair(pubKey, prvKey);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java
new file mode 100644
index 0000000..a5ef6f9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.sshd.common.digest;
+
+import java.security.MessageDigest;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Base class for Digest algorithms based on the JCE provider.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BaseDigest implements Digest {
+
+ private final String algorithm;
+ private final int bsize;
+ private int h;
+ private String s;
+ private MessageDigest md;
+
+ /**
+ * Create a new digest using the given algorithm and block size.
+ * The initialization and creation of the underlying {@link MessageDigest}
+ * object will be done in the {@link #init()} method.
+ *
+ * @param algorithm the JCE algorithm to use for this digest
+ * @param bsize the block size of this digest
+ */
+ public BaseDigest(String algorithm, int bsize) {
+ this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm");
+ ValidateUtils.checkTrue(bsize > 0, "Invalid block size: %d", bsize);
+ this.bsize = bsize;
+ }
+
+ @Override
+ public final String getAlgorithm() {
+ return algorithm;
+ }
+
+ @Override
+ public int getBlockSize() {
+ return bsize;
+ }
+
+ @Override
+ public void init() throws Exception {
+ this.md = SecurityUtils.getMessageDigest(getAlgorithm());
+ }
+
+ @Override
+ public void update(byte[] data) throws Exception {
+ update(data, 0, NumberUtils.length(data));
+ }
+
+ @Override
+ public void update(byte[] data, int start, int len) throws Exception {
+ Objects.requireNonNull(md, "Digest not initialized").update(data, start, len);
+ }
+
+ /**
+ * @return The current {@link MessageDigest} - may be {@code null} if {@link #init()} not called
+ */
+ protected MessageDigest getMessageDigest() {
+ return md;
+ }
+
+ @Override
+ public byte[] digest() throws Exception {
+ return Objects.requireNonNull(md, "Digest not initialized").digest();
+ }
+
+ @Override
+ public int hashCode() {
+ synchronized (this) {
+ if (h == 0) {
+ h = Objects.hashCode(getAlgorithm()) + getBlockSize();
+ if (h == 0) {
+ h = 1;
+ }
+ }
+ }
+
+ return h;
+ }
+
+ @Override
+ public int compareTo(Digest that) {
+ if (that == null) {
+ return -1; // push null(s) to end
+ } else if (this == that) {
+ return 0;
+ }
+
+ String thisAlg = getAlgorithm();
+ String thatAlg = that.getAlgorithm();
+ int nRes = GenericUtils.safeCompare(thisAlg, thatAlg, false);
+ if (nRes != 0) {
+ return nRes; // debug breakpoint
+ }
+
+ nRes = Integer.compare(this.getBlockSize(), that.getBlockSize());
+ if (nRes != 0) {
+ return nRes; // debug breakpoint
+ }
+
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ int nRes = compareTo((Digest) obj);
+ return nRes == 0;
+ }
+
+ @Override
+ public String toString() {
+ synchronized (this) {
+ if (s == null) {
+ s = getClass().getSimpleName() + "[" + getAlgorithm() + ":" + getBlockSize() + "]";
+ }
+ }
+
+ return s;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java
new file mode 100644
index 0000000..f469583
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.digest;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Provides easy access to the currently implemented digests
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinDigests implements DigestFactory {
+ md5(Constants.MD5, "MD5", 16),
+ sha1(Constants.SHA1, "SHA-1", 20),
+ sha224(Constants.SHA224, "SHA-224", 28),
+ sha256(Constants.SHA256, "SHA-256", 32),
+ sha384(Constants.SHA384, "SHA-384", 48),
+ sha512(Constants.SHA512, "SHA-512", 64);
+
+ public static final Set<BuiltinDigests> VALUES =
+ Collections.unmodifiableSet(EnumSet.allOf(BuiltinDigests.class));
+
+ private final String algorithm;
+ private final int blockSize;
+ private final String factoryName;
+ private final boolean supported;
+
+ BuiltinDigests(String factoryName, String algorithm, int blockSize) {
+ this.factoryName = factoryName;
+ this.algorithm = algorithm;
+ this.blockSize = blockSize;
+ /*
+ * This can be done once since in order to change the support the JVM
+ * needs to be stopped, some unlimited-strength files need be installed
+ * and then the JVM re-started. Therefore, the answer is not going to
+ * change while the JVM is running
+ */
+ this.supported = DigestUtils.checkSupported(algorithm);
+ }
+
+ @Override
+ public final String getName() {
+ return factoryName;
+ }
+
+ @Override
+ public final String getAlgorithm() {
+ return algorithm;
+ }
+
+ @Override
+ public final int getBlockSize() {
+ return blockSize;
+ }
+
+ @Override
+ public final String toString() {
+ return getName();
+ }
+
+ @Override
+ public final Digest create() {
+ return new BaseDigest(getAlgorithm(), getBlockSize());
+ }
+
+ @Override
+ public final boolean isSupported() {
+ return supported;
+ }
+
+ /**
+ * @param s The {@link Enum}'s name - ignored if {@code null}/empty
+ * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose {@link Enum#name()} matches
+ * (case <U>insensitive</U>) the provided argument - {@code null} if no match
+ */
+ public static BuiltinDigests fromString(String s) {
+ if (GenericUtils.isEmpty(s)) {
+ return null;
+ }
+
+ for (BuiltinDigests c : VALUES) {
+ if (s.equalsIgnoreCase(c.name())) {
+ return c;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param factory The {@link org.apache.sshd.common.NamedFactory} for the cipher - ignored if {@code null}
+ * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose factory name matches
+ * (case <U>insensitive</U>) the digest factory name
+ * @see #fromFactoryName(String)
+ */
+ public static BuiltinDigests fromFactory(NamedFactory<? extends Digest> factory) {
+ if (factory == null) {
+ return null;
+ } else {
+ return fromFactoryName(factory.getName());
+ }
+ }
+
+ /**
+ * @param name The factory name - ignored if {@code null}/empty
+ * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose factory name matches
+ * (case <U>insensitive</U>) the provided name - {@code null} if no match
+ */
+ public static BuiltinDigests fromFactoryName(String name) {
+ return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
+ }
+
+ /**
+ * @param d The {@link Digest} instance - ignored if {@code null}
+ * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose algorithm matches
+ * (case <U>insensitive</U>) the digets's algorithm - {@code null} if no match
+ */
+ public static BuiltinDigests fromDigest(Digest d) {
+ return fromAlgorithm((d == null) ? null : d.getAlgorithm());
+ }
+
+ /**
+ * @param algo The algorithm to find - ignored if {@code null}/empty
+ * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose algorithm matches
+ * (case <U>insensitive</U>) the provided name - {@code null} if no match
+ */
+ public static BuiltinDigests fromAlgorithm(String algo) {
+ return DigestUtils.findFactoryByAlgorithm(algo, String.CASE_INSENSITIVE_ORDER, VALUES);
+ }
+
+ public static final class Constants {
+ public static final String MD5 = "md5";
+ public static final String SHA1 = "sha1";
+ public static final String SHA224 = "sha224";
+ public static final String SHA256 = "sha256";
+ public static final String SHA384 = "sha384";
+ public static final String SHA512 = "sha512";
+
+ private Constants() {
+ throw new UnsupportedOperationException("No instance allowed");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java
new file mode 100644
index 0000000..27204ff
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.sshd.common.digest;
+
+/**
+ * Interface used to compute digests, based on algorithms such as MD5 or SHA1.
+ * The digest implementation are compared first by the algorithm name (case
+ * <U>insensitive</U> and second according to the block size
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface Digest extends DigestInformation, Comparable<Digest> {
+ void init() throws Exception;
+
+ void update(byte[] data) throws Exception;
+
+ void update(byte[] data, int start, int len) throws Exception;
+
+ byte[] digest() throws Exception;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java
new file mode 100644
index 0000000..f00e7ba
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.digest;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.OptionalFeature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+// CHECKSTYLE:OFF
+public interface DigestFactory extends DigestInformation, NamedFactory<Digest>, OptionalFeature {
+ // nothing extra
+}
+// CHECKSTYLE:ON
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
new file mode 100644
index 0000000..ba181dc
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sshd.common.digest;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface DigestInformation {
+ /**
+ * @return The digest algorithm name
+ */
+ String getAlgorithm();
+
+ /**
+ * @return The number of bytes in the digest's output
+ */
+ int getBlockSize();
+
+}