http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java new file mode 100644 index 0000000..6677685 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java @@ -0,0 +1,482 @@ +/* + * 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; + +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Properties; +import java.util.TreeMap; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class PropertyResolverUtils { + private PropertyResolverUtils() { + throw new UnsupportedOperationException("No instance allowed"); + } + + /** + * @param resolver The {@link PropertyResolver} instance - ignored if {@code null} + * @param name The property name + * @param defaultValue The default value to return if the specified property + * does not exist in the properties map + * @return The resolved property + * @throws NumberFormatException if malformed value + * @see #toLong(Object, long) + */ + public static long getLongProperty(PropertyResolver resolver, String name, long defaultValue) { + return toLong(resolvePropertyValue(resolver, name), defaultValue); + } + + public static long getLongProperty(Map<String, ?> props, String name, long defaultValue) { + return toLong(resolvePropertyValue(props, name), defaultValue); + } + + /** + * Converts a generic object value to a {@code long} if possible: + * <UL> + * <LI> + * If value is {@code null} the default is returned + * </LI> + * + * <LI> + * If value is a {@link Number} then its {@link Number#longValue()} is returned + * </LI> + * + * <LI> + * Otherwise, the value's {@link #toString()} is parsed as a {@code long} + * </LI> + * </UL> + * + * @param value The resolved value - may be {@code null} + * @param defaultValue The default to use if {@code null} resolved value + * @return The resolved value + * @throws NumberFormatException if malformed value + * @see Long#parseLong(String) + */ + public static long toLong(Object value, long defaultValue) { + if (value == null) { + return defaultValue; + } else if (value instanceof Number) { + return ((Number) value).longValue(); + } else { // we parse the string in case it is not a valid long value + return Long.parseLong(value.toString()); + } + } + + /** + * @param resolver The {@link PropertyResolver} instance - ignored if {@code null} + * @param name The property name + * @return The {@link Long} value or {@code null} if property not found + * @throws NumberFormatException if malformed value + * @see #toLong(Object) + */ + public static Long getLong(PropertyResolver resolver, String name) { + return toLong(resolvePropertyValue(resolver, name)); + } + + public static Long getLong(Map<String, ?> props, String name) { + return toLong(resolvePropertyValue(props, name)); + } + + /** + * Converts a generic object into a {@link Long}: + * <UL> + * <LI> + * If the value is {@code null} then returns {@code null}. + * </LI> + * + * <LI> + * If the value is already a {@link Long} then it is returned as such. + * </LI> + + * <LI> + * If value is a {@link Number} then its {@link Number#longValue()} is + * wrapped as a {@link Long} + * </LI> + * + * <LI> + * Otherwise, the value's {@link #toString()} is parsed as a {@link Long} + * </LI> + * </UL> + * + * @param value The resolved value - may be {@code null} + * @return The {@link Long} value or {@code null} if property not found + * @throws NumberFormatException if malformed value + * @see Long#valueOf(long) + * @see Long#valueOf(String) + */ + public static Long toLong(Object value) { + if (value == null) { + return null; + } else if (value instanceof Long) { + return (Long) value; + } else if (value instanceof Number) { + return ((Number) value).longValue(); + } else { // we parse the string in case it is not a valid long value + return Long.valueOf(value.toString()); + } + } + + /** + * Converts an enumerated configuration value: + * <UL> + * <P><LI> + * If value is {@code null} then return {@code null} + * </LI></P> + * + * <P><LI> + * If value already of the expected type then simply + * cast and return it. + * </LI></P> + * + * <P><LI> + * If value is a {@link CharSequence} then convert it + * to a string and look for a matching enumerated value + * name - case <U>insensitive</U>. + * </LI></P>> + * </UL> + * + * @param <E> Type of enumerated value + * @param enumType The enumerated class type + * @param value The configured value - ignored if {@code null} + * @param failIfNoMatch Whether to fail if no matching name found + * @param available The available values to compare the name + * @return The matching enumerated value - {@code null} if no match found + * @throws IllegalArgumentException If value is neither {@code null}, + * nor the enumerated type nor a {@link CharSequence} + * @throws NoSuchElementException If no matching string name found and + * <tt>failIfNoMatch</tt> is {@code true} + */ + public static <E extends Enum<E>> E toEnum(Class<E> enumType, Object value, boolean failIfNoMatch, Collection<E> available) { + if (value == null) { + return null; + } else if (enumType.isInstance(value)) { + return enumType.cast(value); + } else if (value instanceof CharSequence) { + String name = value.toString(); + if (GenericUtils.size(available) > 0) { + for (E v : available) { + if (name.equalsIgnoreCase(v.name())) { + return v; + } + } + } + + if (failIfNoMatch) { + throw new NoSuchElementException("No match found for " + enumType.getSimpleName() + "[" + name + "]"); + } + + return null; + } else { + throw new IllegalArgumentException("Bad value type for enum conversion: " + value.getClass().getSimpleName()); + } + } + + public static Object updateProperty(PropertyResolver resolver, String name, long value) { + return updateProperty(resolver.getProperties(), name, value); + } + + public static Object updateProperty(Map<String, Object> props, String name, long value) { + return updateProperty(props, name, Long.valueOf(value)); + } + + public static int getIntProperty(PropertyResolver resolver, String name, int defaultValue) { + return toInteger(resolvePropertyValue(resolver, name), defaultValue); + } + + public static int getIntProperty(Map<String, ?> props, String name, int defaultValue) { + return toInteger(resolvePropertyValue(props, name), defaultValue); + } + + public static int toInteger(Object value, int defaultValue) { + if (value == null) { + return defaultValue; + } else if (value instanceof Number) { + return ((Number) value).intValue(); + } else { // we parse the string in case this is NOT an integer + return Integer.parseInt(value.toString()); + } + } + + public static Integer getInteger(PropertyResolver resolver, String name) { + return toInteger(resolvePropertyValue(resolver, name)); + } + + public static Integer getInteger(Map<String, ?> props, String name) { + return toInteger(resolvePropertyValue(props, name)); + } + + public static Integer toInteger(Object value) { + if (value == null) { + return null; + } else if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof Number) { + return ((Number) value).intValue(); + } else { // we parse the string in case this is NOT an integer + return Integer.valueOf(value.toString()); + } + } + + public static Object updateProperty(PropertyResolver resolver, String name, int value) { + return updateProperty(resolver.getProperties(), name, value); + } + + public static Object updateProperty(Map<String, Object> props, String name, int value) { + return updateProperty(props, name, Integer.valueOf(value)); + } + + public static boolean getBooleanProperty(PropertyResolver resolver, String name, boolean defaultValue) { + return toBoolean(getObject(resolver, name), defaultValue); + } + + public static boolean getBooleanProperty(Map<String, ?> props, String name, boolean defaultValue) { + return toBoolean(getObject(props, name), defaultValue); + } + + public static boolean toBoolean(Object value, boolean defaultValue) { + if (value == null) { + return defaultValue; + } else { + return toBoolean(value); + } + } + + public static Boolean getBoolean(PropertyResolver resolver, String name) { + return toBoolean(resolvePropertyValue(resolver, name)); + } + + public static Boolean getBoolean(Map<String, ?> props, String name) { + return toBoolean(resolvePropertyValue(props, name)); + } + + public static Boolean toBoolean(Object value) { + if (value == null) { + return null; + } else if (value instanceof Boolean) { + return (Boolean) value; + } else { + return Boolean.valueOf(value.toString()); + } + } + + public static Object updateProperty(PropertyResolver resolver, String name, boolean value) { + return updateProperty(resolver.getProperties(), name, value); + } + + public static Object updateProperty(Map<String, Object> props, String name, boolean value) { + return updateProperty(props, name, Boolean.valueOf(value)); + } + + /** + * @param resolver The {@link PropertyResolver} to use - ignored if {@code null} + * @param name The property name + * @param defaultValue The default value to return if property not set or empty + * @return The set value (if not {@code null}/empty) or default one + */ + public static String getStringProperty(PropertyResolver resolver, String name, String defaultValue) { + String value = getString(resolver, name); + if (GenericUtils.isEmpty(value)) { + return defaultValue; + } else { + return value; + } + } + + public static String getStringProperty(Map<String, ?> props, String name, String defaultValue) { + Object value = resolvePropertyValue(props, name); + if (value == null) { + return defaultValue; + } else { + return Objects.toString(value); + } + } + + public static Charset getCharset(PropertyResolver resolver, String name, Charset defaultValue) { + Object value = getObject(resolver, name); + return (value == null) ? defaultValue : toCharset(value); + } + + public static Charset getCharset(Map<String, ?> props, String name, Charset defaultValue) { + Object value = getObject(props, name); + return (value == null) ? defaultValue : toCharset(value); + } + + public static Charset toCharset(Object value) { + if (value == null) { + return null; + } else if (value instanceof Charset) { + return (Charset) value; + } else if (value instanceof CharSequence) { + return Charset.forName(value.toString()); + } else { + throw new IllegalArgumentException("Invalid charset conversion value: " + value); + } + } + + public static String getString(PropertyResolver resolver, String name) { + Object value = getObject(resolver, name); + return Objects.toString(value, null); + } + + public static String getString(Map<String, ?> props, String name) { + Object value = getObject(props, name); + return Objects.toString(value, null); + } + + public static Object getObject(PropertyResolver resolver, String name) { + return resolvePropertyValue(resolver, name); + } + + // for symmetrical reasons... + public static Object getObject(Map<String, ?> props, String name) { + return resolvePropertyValue(props, name); + } + + public static Object resolvePropertyValue(Map<String, ?> props, String name) { + String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); + return props != null ? props.get(key) : null; + } + + /** + * @param resolver The {@link PropertyResolver} instance + * @param name The property name + * @param value The new value - if {@code null} or an empty {@link CharSequence} + * the property is <U>removed</U> + * @return The previous value - {@code null} if none + */ + public static Object updateProperty(PropertyResolver resolver, String name, Object value) { + return updateProperty(resolver.getProperties(), name, value); + } + + public static Object updateProperty(Map<String, Object> props, String name, Object value) { + String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); + if ((value == null) || ((value instanceof CharSequence) && GenericUtils.isEmpty((CharSequence) value))) { + return props.remove(key); + } else { + return props.put(key, value); + } + } + + /** + * Unwinds the resolvers hierarchy until found one with a non-{@code null} value + * for the requested property or reached top. If still no value found and the key + * starts with "org.apache.sshd" then the system properties are also + * consulted + * + * @param resolver The {@link PropertyResolver} to start from - ignored if {@code null} + * @param name The requested property name + * @return The found value or {@code null} + */ + public static Object resolvePropertyValue(PropertyResolver resolver, String name) { + String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); + for (PropertyResolver r = resolver; r != null; r = r.getParentPropertyResolver()) { + Map<String, ?> props = r.getProperties(); + if (props != null) { + Object value = props.get(key); + if (value != null) { + return value; + } + } + } + + return null; + } + + /** + * Unwinds the resolvers hierarchy until found one with a non-{@code null} value + * for the requested property or reached top. + * + * @param resolver The {@link PropertyResolver} to start from - ignored if {@code null} + * @param name The requested property name + * @return The found properties {@link Map} or {@code null} + */ + public static Map<String, Object> resolvePropertiesSource(PropertyResolver resolver, String name) { + String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); + for (PropertyResolver r = resolver; r != null; r = r.getParentPropertyResolver()) { + Map<String, Object> props = r.getProperties(); + if (props != null) { + Object value = props.get(key); + if (value != null) { + return props; + } + } + } + + return null; + } + + public static PropertyResolver toPropertyResolver(Properties props) { + if (GenericUtils.isEmpty(props)) { + return PropertyResolver.EMPTY; + } + + Map<String, Object> propsMap = new TreeMap<>(Comparator.naturalOrder()); + Collection<String> names = props.stringPropertyNames(); + for (String key : names) { + String value = props.getProperty(key); + if (value == null) { + continue; + } + propsMap.put(key, value); + } + + return toPropertyResolver(propsMap); + } + + /** + * Wraps a {@link Map} into a {@link PropertyResolver} so it can be used + * with these utilities + * + * @param props The properties map - may be {@code null}/empty if no properties + * are updated + * @return The resolver wrapper + */ + public static PropertyResolver toPropertyResolver(Map<String, Object> props) { + return toPropertyResolver(props, null); + } + + public static PropertyResolver toPropertyResolver(Map<String, Object> props, PropertyResolver parent) { + return new PropertyResolver() { + @Override + public PropertyResolver getParentPropertyResolver() { + return parent; + } + + @Override + public Map<String, Object> getProperties() { + return props; + } + + @Override + public String toString() { + return Objects.toString(props); + } + }; + } +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/RuntimeSshException.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/RuntimeSshException.java b/sshd-common/src/main/java/org/apache/sshd/common/RuntimeSshException.java new file mode 100644 index 0000000..8c9164f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/RuntimeSshException.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * Exception used in the SSH client or server. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class RuntimeSshException extends RuntimeException { + private static final long serialVersionUID = -2423550196146939503L; + + public RuntimeSshException() { + this(null, null); + } + + public RuntimeSshException(String message) { + this(message, null); + } + + public RuntimeSshException(Throwable cause) { + this(null, cause); + } + + public RuntimeSshException(String message, Throwable cause) { + super(message); + if (cause != null) { + initCause(cause); + } + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/SshConstants.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/SshConstants.java b/sshd-common/src/main/java/org/apache/sshd/common/SshConstants.java new file mode 100644 index 0000000..01e8ae7 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/SshConstants.java @@ -0,0 +1,245 @@ +/* + * 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; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.logging.LoggingUtils; + +/** + * This interface defines constants for the SSH protocol. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class SshConstants { + // + // SSH message identifiers + // + + public static final byte SSH_MSG_DISCONNECT = 1; + public static final byte SSH_MSG_IGNORE = 2; + public static final byte SSH_MSG_UNIMPLEMENTED = 3; + public static final byte SSH_MSG_DEBUG = 4; + public static final byte SSH_MSG_SERVICE_REQUEST = 5; + public static final byte SSH_MSG_SERVICE_ACCEPT = 6; + public static final byte SSH_MSG_KEXINIT = 20; + public static final int MSG_KEX_COOKIE_SIZE = 16; + public static final byte SSH_MSG_NEWKEYS = 21; + + public static final byte SSH_MSG_KEX_FIRST = 30; + public static final byte SSH_MSG_KEX_LAST = 49; + + public static final byte SSH_MSG_KEXDH_INIT = 30; + public static final byte SSH_MSG_KEXDH_REPLY = 31; + + public static final byte SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; + public static final byte SSH_MSG_KEX_DH_GEX_GROUP = 31; + public static final byte SSH_MSG_KEX_DH_GEX_INIT = 32; + public static final byte SSH_MSG_KEX_DH_GEX_REPLY = 33; + public static final byte SSH_MSG_KEX_DH_GEX_REQUEST = 34; + + public static final byte SSH_MSG_USERAUTH_REQUEST = 50; + public static final byte SSH_MSG_USERAUTH_FAILURE = 51; + public static final byte SSH_MSG_USERAUTH_SUCCESS = 52; + public static final byte SSH_MSG_USERAUTH_BANNER = 53; + + public static final byte SSH_MSG_USERAUTH_INFO_REQUEST = 60; + public static final byte SSH_MSG_USERAUTH_INFO_RESPONSE = 61; + + public static final byte SSH_MSG_USERAUTH_PK_OK = 60; + + public static final byte SSH_MSG_USERAUTH_PASSWD_CHANGEREQ = 60; + + public static final byte SSH_MSG_USERAUTH_GSSAPI_MIC = 66; + + public static final byte SSH_MSG_GLOBAL_REQUEST = 80; + public static final byte SSH_MSG_REQUEST_SUCCESS = 81; + public static final byte SSH_MSG_REQUEST_FAILURE = 82; + public static final byte SSH_MSG_CHANNEL_OPEN = 90; + public static final byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; + public static final byte SSH_MSG_CHANNEL_OPEN_FAILURE = 92; + public static final byte SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; + public static final byte SSH_MSG_CHANNEL_DATA = 94; + public static final byte SSH_MSG_CHANNEL_EXTENDED_DATA = 95; + public static final byte SSH_MSG_CHANNEL_EOF = 96; + public static final byte SSH_MSG_CHANNEL_CLOSE = 97; + public static final byte SSH_MSG_CHANNEL_REQUEST = 98; + public static final byte SSH_MSG_CHANNEL_SUCCESS = 99; + public static final byte SSH_MSG_CHANNEL_FAILURE = 100; + + // + // Disconnect error codes + // + public static final int SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1; + public static final int SSH2_DISCONNECT_PROTOCOL_ERROR = 2; + public static final int SSH2_DISCONNECT_KEY_EXCHANGE_FAILED = 3; + public static final int SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED = 4; + public static final int SSH2_DISCONNECT_RESERVED = 4; + public static final int SSH2_DISCONNECT_MAC_ERROR = 5; + public static final int SSH2_DISCONNECT_COMPRESSION_ERROR = 6; + public static final int SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE = 7; + public static final int SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8; + public static final int SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9; + public static final int SSH2_DISCONNECT_CONNECTION_LOST = 10; + public static final int SSH2_DISCONNECT_BY_APPLICATION = 11; + public static final int SSH2_DISCONNECT_TOO_MANY_CONNECTIONS = 12; + public static final int SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER = 13; + public static final int SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14; + public static final int SSH2_DISCONNECT_ILLEGAL_USER_NAME = 15; + + // + // Open error codes + // + + public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; + public static final int SSH_OPEN_CONNECT_FAILED = 2; + public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; + public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; + + // Some more constants + public static final int SSH_EXTENDED_DATA_STDERR = 1; // see RFC4254 section 5.2 + public static final int SSH_PACKET_HEADER_LEN = 5; // 32-bit length + 8-bit pad length + + private SshConstants() { + throw new UnsupportedOperationException("No instance allowed"); + } + + private static final class LazyAmbiguousOpcodesHolder { + private static final Set<Integer> AMBIGUOUS_OPCODES = + Collections.unmodifiableSet( + new HashSet<>( + LoggingUtils.getAmbiguousMenmonics(SshConstants.class, "SSH_MSG_").values())); + + private LazyAmbiguousOpcodesHolder() { + throw new UnsupportedOperationException("No instance allowed"); + } + } + + /** + * @param cmd The command value + * @return {@code true} if this value is used by several <U>different</U> messages + * @see #getAmbiguousOpcodes() + */ + public static boolean isAmbiguousOpcode(int cmd) { + Collection<Integer> ambiguousOpcodes = getAmbiguousOpcodes(); + return ambiguousOpcodes.contains(cmd); + } + + /** + * @return A {@link Set} of opcodes that are used by several <U>different</U> messages + */ + @SuppressWarnings("synthetic-access") + public static Set<Integer> getAmbiguousOpcodes() { + return LazyAmbiguousOpcodesHolder.AMBIGUOUS_OPCODES; + } + + private static final class LazyMessagesMapHolder { + private static final Map<Integer, String> MESSAGES_MAP = + LoggingUtils.generateMnemonicMap(SshConstants.class, f -> { + String name = f.getName(); + if (!name.startsWith("SSH_MSG_")) { + return false; + } + + try { + return !isAmbiguousOpcode(f.getByte(null)); + } catch (Exception e) { + return false; + } + }); + + private LazyMessagesMapHolder() { + throw new UnsupportedOperationException("No instance allowed"); + } + } + + /** + * Converts a command value to a user-friendly name + * + * @param cmd The command value + * @return The user-friendly name - if not one of the defined {@code SSH_MSG_XXX} + * values then returns the string representation of the command's value + */ + public static String getCommandMessageName(int cmd) { + @SuppressWarnings("synthetic-access") + String name = LazyMessagesMapHolder.MESSAGES_MAP.get(cmd); + if (GenericUtils.isEmpty(name)) { + return Integer.toString(cmd); + } else { + return name; + } + } + + private static final class LazyReasonsMapHolder { + private static final Map<Integer, String> REASONS_MAP = + LoggingUtils.generateMnemonicMap(SshConstants.class, "SSH2_DISCONNECT_"); + + private LazyReasonsMapHolder() { + throw new UnsupportedOperationException("No instance allowed"); + } + } + + /** + * Converts a disconnect reason value to a user-friendly name + * + * @param reason The disconnect reason value + * @return The user-friendly name - if not one of the defined {@code SSH2_DISCONNECT_} + * values then returns the string representation of the reason's value + */ + public static String getDisconnectReasonName(int reason) { + @SuppressWarnings("synthetic-access") + String name = LazyReasonsMapHolder.REASONS_MAP.get(reason); + if (GenericUtils.isEmpty(name)) { + return Integer.toString(reason); + } else { + return name; + } + } + + private static final class LazyOpenCodesMapHolder { + private static final Map<Integer, String> OPEN_CODES_MAP = + LoggingUtils.generateMnemonicMap(SshConstants.class, "SSH_OPEN_"); + + private LazyOpenCodesMapHolder() { + throw new UnsupportedOperationException("No instance allowed"); + } + } + + /** + * Converts an open error value to a user-friendly name + * + * @param code The open error value + * @return The user-friendly name - if not one of the defined {@code SSH_OPEN_} + * values then returns the string representation of the reason's value + */ + public static String getOpenErrorCodeName(int code) { + @SuppressWarnings("synthetic-access") + String name = LazyOpenCodesMapHolder.OPEN_CODES_MAP.get(code); + if (GenericUtils.isEmpty(name)) { + return Integer.toString(code); + } else { + return name; + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/SshException.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/SshException.java b/sshd-common/src/main/java/org/apache/sshd/common/SshException.java new file mode 100644 index 0000000..4280d08 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/SshException.java @@ -0,0 +1,72 @@ +/* + * 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; + +import java.io.IOException; +import java.util.Objects; + +import org.apache.sshd.common.util.GenericUtils; + +/** + * Represents an SSH related exception + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class SshException extends IOException { + + private static final long serialVersionUID = -7349477687125144606L; + + private final int disconnectCode; + + public SshException(String message) { + this(message, null); + } + + public SshException(Throwable cause) { + this(Objects.requireNonNull(cause, "No cause").getMessage(), cause); + } + + public SshException(String message, Throwable cause) { + this(0, message, cause); + } + + public SshException(int disconnectCode) { + this(disconnectCode, SshConstants.getDisconnectReasonName(disconnectCode)); + } + + public SshException(int disconnectCode, String message) { + this(disconnectCode, message, null); + } + + public SshException(int disconnectCode, Throwable cause) { + this(disconnectCode, SshConstants.getDisconnectReasonName(disconnectCode), cause); + } + + public SshException(int disconnectCode, String message, Throwable cause) { + super(GenericUtils.isEmpty(message) ? SshConstants.getDisconnectReasonName(disconnectCode) : message); + this.disconnectCode = disconnectCode; + if (cause != null) { + initCause(cause); + } + } + + public int getDisconnectCode() { + return disconnectCode; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/SyspropsMapWrapper.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/SyspropsMapWrapper.java b/sshd-common/src/main/java/org/apache/sshd/common/SyspropsMapWrapper.java new file mode 100644 index 0000000..4408be2 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/SyspropsMapWrapper.java @@ -0,0 +1,209 @@ +/* + * 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; + +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.MapEntryUtils; + +/** + * A wrapper that exposes a read-only {@link Map} access to the system + * properties. Any attempt to modify it will throw {@link UnsupportedOperationException}. + * The mapper uses the {@link #SYSPROPS_MAPPED_PREFIX} to filter and access' + * only these properties, ignoring all others + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class SyspropsMapWrapper implements Map<String, Object> { + /** + * Prefix of properties used by the mapper to identify SSHD related settings + */ + public static final String SYSPROPS_MAPPED_PREFIX = "org.apache.sshd.config"; + + /** + * The one and only wrapper instance + */ + public static final SyspropsMapWrapper INSTANCE = new SyspropsMapWrapper(); + + /** + * A {@link PropertyResolver} with no parent that exposes the system properties + */ + public static final PropertyResolver SYSPROPS_RESOLVER = new PropertyResolver() { + @Override + public Map<String, Object> getProperties() { + return SyspropsMapWrapper.INSTANCE; + } + + @Override + public PropertyResolver getParentPropertyResolver() { + return null; + } + + @Override + public String toString() { + return "SYSPROPS"; + } + }; + + private SyspropsMapWrapper() { + super(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("sysprops#clear() N/A"); + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(Object value) { + // not the most efficient implementation, but we do not expect it to be called much + Properties props = System.getProperties(); + for (String key : props.stringPropertyNames()) { + if (!isMappedSyspropKey(key)) { + continue; + } + + Object v = props.getProperty(key); + if (Objects.equals(v, value)) { + return true; + } + } + + return false; + } + + @Override + public Set<Entry<String, Object>> entrySet() { + Properties props = System.getProperties(); + // return a copy in order to avoid concurrent modifications + Set<Entry<String, Object>> entries = new TreeSet<>(MapEntryUtils.byKeyEntryComparator()); + for (String key : props.stringPropertyNames()) { + if (!isMappedSyspropKey(key)) { + continue; + } + + Object v = props.getProperty(key); + if (v != null) { + entries.add(new SimpleImmutableEntry<>(getUnmappedSyspropKey(key), v)); + } + } + + return entries; + } + + @Override + public Object get(Object key) { + return (key instanceof String) ? System.getProperty(getMappedSyspropKey(key)) : null; + } + + @Override + public boolean isEmpty() { + return GenericUtils.isEmpty(keySet()); + } + + @Override + public Set<String> keySet() { + return System.getProperties() + .stringPropertyNames().stream() + // filter out any non-SSHD properties + .filter(SyspropsMapWrapper::isMappedSyspropKey) + .map(SyspropsMapWrapper::getUnmappedSyspropKey) + .collect(Collectors.toSet()); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException("sysprops#put(" + key + ")[" + value + "] N/A"); + } + + @Override + public void putAll(Map<? extends String, ?> m) { + throw new UnsupportedOperationException("sysprops#putAll(" + m + ") N/A"); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException("sysprops#remove(" + key + ") N/A"); + } + + @Override + public int size() { + return GenericUtils.size(keySet()); + } + + @Override + public Collection<Object> values() { + Properties props = System.getProperties(); + // return a copy in order to avoid concurrent modifications + return props + .stringPropertyNames().stream() + .filter(SyspropsMapWrapper::isMappedSyspropKey) + .map(props::get) + .collect(Collectors.toList()); + } + + @Override + public String toString() { + return Objects.toString(System.getProperties(), null); + } + + /** + * @param key Key to be tested + * @return {@code true} if key starts with {@link #SYSPROPS_MAPPED_PREFIX} + * and continues with a dot followed by some characters + */ + public static boolean isMappedSyspropKey(String key) { + return (GenericUtils.length(key) > (SYSPROPS_MAPPED_PREFIX.length() + 1)) + && key.startsWith(SYSPROPS_MAPPED_PREFIX) + && (key.charAt(SYSPROPS_MAPPED_PREFIX.length()) == '.'); + } + + /** + * @param key Key to be transformed + * @return The "pure" key name if a mapped one, same as input otherwise + * @see #isMappedSyspropKey(String) + */ + public static String getUnmappedSyspropKey(Object key) { + String s = Objects.toString(key); + return isMappedSyspropKey(s) ? s.substring(SYSPROPS_MAPPED_PREFIX.length() + 1 /* skip dot */) : s; + } + + /** + * @param key The original key + * @return A key prefixed by {@link #SYSPROPS_MAPPED_PREFIX} + * @see #isMappedSyspropKey(String) + */ + public static String getMappedSyspropKey(Object key) { + return SYSPROPS_MAPPED_PREFIX + "." + key; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableUserHolder.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableUserHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableUserHolder.java new file mode 100644 index 0000000..485f2f2 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableUserHolder.java @@ -0,0 +1,27 @@ +/* + * 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.auth; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface MutableUserHolder extends UsernameHolder { + void setUsername(String username); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java new file mode 100644 index 0000000..7ee76ad --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/UsernameHolder.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.auth; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface UsernameHolder { + /** + * @return The attached username - may be {@code null}/empty if holder + * not yet initialized + */ + String getUsername(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java new file mode 100644 index 0000000..c3d9426 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java @@ -0,0 +1,113 @@ +/* + * 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.cipher; + +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * Base class for all Cipher implementations delegating to the JCE provider. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BaseCipher implements Cipher { + + protected javax.crypto.Cipher cipher; + private final int ivsize; + private final int bsize; + private final String algorithm; + private final String transformation; + private String s; + + public BaseCipher(int ivsize, int bsize, String algorithm, String transformation) { + this.ivsize = ivsize; + this.bsize = bsize; + this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm"); + this.transformation = ValidateUtils.checkNotNullAndNotEmpty(transformation, "No transformation"); + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getTransformation() { + return transformation; + } + + @Override + public int getIVSize() { + return ivsize; + } + + @Override + public int getBlockSize() { + return bsize; + } + + @Override + public void init(Mode mode, byte[] key, byte[] iv) throws Exception { + key = resize(key, getBlockSize()); + iv = resize(iv, getIVSize()); + try { + cipher = SecurityUtils.getCipher(getTransformation()); + cipher.init(Mode.Encrypt.equals(mode) ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE, + new SecretKeySpec(key, getAlgorithm()), + new IvParameterSpec(iv)); + } catch (Exception e) { + cipher = null; + throw new SshException("Unable to initialize cipher " + this, e); + } + } + + @Override + public void update(byte[] input, int inputOffset, int inputLen) throws Exception { + cipher.update(input, inputOffset, inputLen, input, inputOffset); + } + + protected static byte[] resize(byte[] data, int size) { + if (data.length > size) { + byte[] tmp = new byte[size]; + System.arraycopy(data, 0, tmp, 0, size); + data = tmp; + } + return data; + } + + @Override + public String toString() { + synchronized (this) { + if (s == null) { + s = getClass().getSimpleName() + + "[" + getAlgorithm() + + "," + getIVSize() + + "," + getBlockSize() + + "," + getTransformation() + + "]"; + } + } + + return s; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java new file mode 100644 index 0000000..2e3fc1e --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java @@ -0,0 +1,54 @@ +/* + * 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.cipher; + +import javax.crypto.spec.SecretKeySpec; + +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BaseRC4Cipher extends BaseCipher { + + public static final int SKIP_SIZE = 1536; + + public BaseRC4Cipher(int ivsize, int bsize) { + super(ivsize, bsize, "ARCFOUR", "RC4"); + } + + @Override + public void init(Mode mode, byte[] key, byte[] iv) throws Exception { + key = resize(key, getBlockSize()); + try { + cipher = SecurityUtils.getCipher(getTransformation()); + cipher.init(Mode.Encrypt.equals(mode) ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE, + new SecretKeySpec(key, getAlgorithm())); + + byte[] foo = new byte[1]; + for (int i = 0; i < SKIP_SIZE; i++) { + cipher.update(foo, 0, 1, foo, 0); + } + } catch (Exception e) { + cipher = null; + throw e; + } + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java new file mode 100644 index 0000000..8609d50 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java @@ -0,0 +1,348 @@ +/* + * 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.cipher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.NamedFactoriesListParseResult; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * Provides easy access to the currently implemented ciphers + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum BuiltinCiphers implements CipherFactory { + none(Constants.NONE, 0, 0, "None", "None") { + @Override + public Cipher create() { + return new CipherNone(); + } + }, + aes128cbc(Constants.AES128_CBC, 16, 16, "AES", "AES/CBC/NoPadding"), + aes128ctr(Constants.AES128_CTR, 16, 16, "AES", "AES/CTR/NoPadding"), + aes192cbc(Constants.AES192_CBC, 16, 24, "AES", "AES/CBC/NoPadding"), + aes192ctr(Constants.AES192_CTR, 16, 24, "AES", "AES/CTR/NoPadding"), + aes256cbc(Constants.AES256_CBC, 16, 32, "AES", "AES/CBC/NoPadding"), + aes256ctr(Constants.AES256_CTR, 16, 32, "AES", "AES/CTR/NoPadding"), + arcfour128(Constants.ARCFOUR128, 8, 16, "ARCFOUR", "RC4") { + @Override + public Cipher create() { + return new BaseRC4Cipher(getIVSize(), getBlockSize()); + } + }, + arcfour256(Constants.ARCFOUR256, 8, 32, "ARCFOUR", "RC4") { + @Override + public Cipher create() { + return new BaseRC4Cipher(getIVSize(), getBlockSize()); + } + }, + blowfishcbc(Constants.BLOWFISH_CBC, 8, 16, "Blowfish", "Blowfish/CBC/NoPadding"), + tripledescbc(Constants.TRIPLE_DES_CBC, 8, 24, "DESede", "DESede/CBC/NoPadding"); + + public static final Set<BuiltinCiphers> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(BuiltinCiphers.class)); + + private static final Map<String, CipherFactory> EXTENSIONS = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + private final String factoryName; + private final int ivsize; + private final int blocksize; + private final int keysize; + private final String algorithm; + private final String transformation; + private final boolean supported; + + BuiltinCiphers(String factoryName, int ivsize, int blocksize, String algorithm, String transformation) { + this.factoryName = factoryName; + this.ivsize = ivsize; + this.blocksize = blocksize; + this.keysize = blocksize * Byte.SIZE; + this.algorithm = algorithm; + this.transformation = transformation; + /* + * 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 = Constants.NONE.equals(factoryName) || Cipher.checkSupported(this.transformation, this.keysize); + } + + @Override + public final String getName() { + return factoryName; + } + + @Override + public final String toString() { + return getName(); + } + + /** + * @return {@code true} if the current JVM configuration supports this + * cipher - e.g., AES-256 requires the <A HREF="http://www.oracle.com/technetwork/java/javase/downloads/"> + * Java Cryptography Extension (JCE)</A> + */ + @Override + public boolean isSupported() { + return supported; + } + + /** + * @return The key size (in bits) for the cipher + */ + public int getKeySize() { + return keysize; + } + + @Override + public int getIVSize() { + return ivsize; + } + + @Override + public int getBlockSize() { + return blocksize; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getTransformation() { + return transformation; + } + + @Override + public Cipher create() { + return new BaseCipher(getIVSize(), getBlockSize(), getAlgorithm(), getTransformation()); + } + + /** + * Registered a {@link NamedFactory} to be available besides the built-in + * ones when parsing configuration + * + * @param extension The factory to register + * @throws IllegalArgumentException if factory instance is {@code null}, + * or overrides a built-in one or overrides another registered factory + * with the same name (case <U>insensitive</U>). + */ + public static void registerExtension(CipherFactory extension) { + String name = Objects.requireNonNull(extension, "No extension provided").getName(); + ValidateUtils.checkTrue(fromFactoryName(name) == null, "Extension overrides built-in: %s", name); + + synchronized (EXTENSIONS) { + ValidateUtils.checkTrue(!EXTENSIONS.containsKey(name), "Extension overrides existing: %s", name); + EXTENSIONS.put(name, extension); + } + } + + /** + * @return A {@link SortedSet} of the currently registered extensions, sorted + * according to the factory name (case <U>insensitive</U>) + */ + public static NavigableSet<CipherFactory> getRegisteredExtensions() { + synchronized (EXTENSIONS) { + return GenericUtils.asSortedSet(NamedResource.BY_NAME_COMPARATOR, EXTENSIONS.values()); + } + } + + /** + * Unregisters specified extension + * + * @param name The factory name - ignored if {@code null}/empty + * @return The registered extension - {@code null} if not found + */ + public static NamedFactory<Cipher> unregisterExtension(String name) { + if (GenericUtils.isEmpty(name)) { + return null; + } + + synchronized (EXTENSIONS) { + return EXTENSIONS.remove(name); + } + } + + /** + * @param s The {@link Enum}'s name - ignored if {@code null}/empty + * @return The matching {@link BuiltinCiphers} whose {@link Enum#name()} matches + * (case <U>insensitive</U>) the provided argument - {@code null} if no match + */ + public static BuiltinCiphers fromString(String s) { + if (GenericUtils.isEmpty(s)) { + return null; + } + + for (BuiltinCiphers c : VALUES) { + if (s.equalsIgnoreCase(c.name())) { + return c; + } + } + + return null; + } + + /** + * @param factory The {@link NamedFactory} for the cipher - ignored if {@code null} + * @return The matching {@link BuiltinCiphers} whose factory name matches + * (case <U>insensitive</U>) the cipher factory name + * @see #fromFactoryName(String) + */ + public static BuiltinCiphers fromFactory(NamedFactory<Cipher> 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 BuiltinCiphers} whose factory name matches + * (case <U>insensitive</U>) the provided name - {@code null} if no match + */ + public static BuiltinCiphers fromFactoryName(String name) { + return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES); + } + + /** + * @param ciphers A comma-separated list of ciphers' names - ignored if {@code null}/empty + * @return A {@link ParseResult} containing the successfully parsed + * factories and the unknown ones. <B>Note:</B> it is up to caller to + * ensure that the lists do not contain duplicates + */ + public static ParseResult parseCiphersList(String ciphers) { + return parseCiphersList(GenericUtils.split(ciphers, ',')); + } + + public static ParseResult parseCiphersList(String... ciphers) { + return parseCiphersList(GenericUtils.isEmpty((Object[]) ciphers) ? Collections.emptyList() : Arrays.asList(ciphers)); + } + + public static ParseResult parseCiphersList(Collection<String> ciphers) { + if (GenericUtils.isEmpty(ciphers)) { + return ParseResult.EMPTY; + } + + List<CipherFactory> factories = new ArrayList<>(ciphers.size()); + List<String> unknown = Collections.emptyList(); + for (String name : ciphers) { + CipherFactory c = resolveFactory(name); + if (c != null) { + factories.add(c); + } else { + // replace the (unmodifiable) empty list with a real one + if (unknown.isEmpty()) { + unknown = new ArrayList<>(); + } + unknown.add(name); + } + } + + return new ParseResult(factories, unknown); + } + + /** + * @param name The factory name + * @return The factory or {@code null} if it is neither a built-in one + * or a registered extension + */ + public static CipherFactory resolveFactory(String name) { + if (GenericUtils.isEmpty(name)) { + return null; + } + + CipherFactory c = fromFactoryName(name); + if (c != null) { + return c; + } + + synchronized (EXTENSIONS) { + return EXTENSIONS.get(name); + } + } + + /** + * Holds the result of {@link BuiltinCiphers#parseCiphersList(String)} + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ + public static class ParseResult extends NamedFactoriesListParseResult<Cipher, CipherFactory> { + public static final ParseResult EMPTY = new ParseResult(Collections.emptyList(), Collections.emptyList()); + + public ParseResult(List<CipherFactory> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + } + + public static final class Constants { + public static final String NONE = "none"; + public static final Pattern NONE_CIPHER_PATTERN = + Pattern.compile("(^|.*,)" + NONE + "($|,.*)"); + + public static final String AES128_CBC = "aes128-cbc"; + public static final String AES128_CTR = "aes128-ctr"; + public static final String AES192_CBC = "aes192-cbc"; + public static final String AES192_CTR = "aes192-ctr"; + public static final String AES256_CBC = "aes256-cbc"; + public static final String AES256_CTR = "aes256-ctr"; + public static final String ARCFOUR128 = "arcfour128"; + public static final String ARCFOUR256 = "arcfour256"; + public static final String BLOWFISH_CBC = "blowfish-cbc"; + public static final String TRIPLE_DES_CBC = "3des-cbc"; + + private Constants() { + throw new UnsupportedOperationException("No instance allowed"); + } + + /** + * @param s A comma-separated list of ciphers - ignored if {@code null}/empty + * @return {@code true} if the {@link #NONE} cipher name appears in it + */ + public static boolean isNoneCipherIncluded(String s) { + if (GenericUtils.isEmpty(s)) { + return false; + } + Matcher m = NONE_CIPHER_PATTERN.matcher(s); + return m.matches(); + } + + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/Cipher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/Cipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/Cipher.java new file mode 100644 index 0000000..868e983 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/Cipher.java @@ -0,0 +1,89 @@ +/* + * 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.cipher; + +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * Wrapper for a cryptographic cipher, used either for encryption + * or decryption. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface Cipher extends CipherInformation { + + enum Mode { + Encrypt, Decrypt + } + + /** + * Initialize the cipher for encryption or decryption with + * the given key and initialization vector + * + * @param mode Encrypt/Decrypt initialization + * @param key Key bytes + * @param iv Initialization vector bytes + * @throws Exception If failed to initialize + */ + void init(Mode mode, byte[] key, byte[] iv) throws Exception; + + /** + * Performs in-place encryption or decryption on the given data. + * + * @param input The input/output bytes + * @throws Exception If failed to execute + * @see #update(byte[], int, int) + */ + default void update(byte[] input) throws Exception { + update(input, 0, NumberUtils.length(input)); + } + + /** + * Performs in-place encryption or decryption on the given data. + * + * @param input The input/output bytes + * @param inputOffset The offset of the data in the data buffer + * @param inputLen The number of bytes to update - starting at the given offset + * @throws Exception If failed to execute + */ + void update(byte[] input, int inputOffset, int inputLen) throws Exception; + + /** + * @param xform The full cipher transformation - e.g., AES/CBC/NoPadding - + * never {@code null}/empty + * @param keyLength The required key length in bits - always positive + * @return {@code true} if the cipher transformation <U>and</U> required + * key length are supported + * @see javax.crypto.Cipher#getMaxAllowedKeyLength(String) + */ + static boolean checkSupported(String xform, int keyLength) { + ValidateUtils.checkNotNullAndNotEmpty(xform, "No transformation"); + if (keyLength <= 0) { + throw new IllegalArgumentException("Bad key length (" + keyLength + ") for cipher=" + xform); + } + + try { + int maxKeyLength = javax.crypto.Cipher.getMaxAllowedKeyLength(xform); + return maxKeyLength >= keyLength; + } catch (Exception e) { + return false; + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherFactory.java new file mode 100644 index 0000000..36909f3 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherFactory.java @@ -0,0 +1,31 @@ +/* + * 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.cipher; + +import org.apache.sshd.common.BuiltinFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +// CHECKSTYLE:OFF +public interface CipherFactory extends BuiltinFactory<Cipher>, CipherInformation { + // 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/cipher/CipherInformation.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java new file mode 100644 index 0000000..f17fd16 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java @@ -0,0 +1,45 @@ +/* + * 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.cipher; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface CipherInformation { + /** + * @return The cipher's algorithm + */ + String getAlgorithm(); + + /** + * @return The actual transformation used - e.g., AES/CBC/NoPadding + */ + String getTransformation(); + + /** + * @return Size of the initialization vector (in bytes) + */ + int getIVSize(); + + /** + * @return The block size (in bytes) for this cipher + */ + int getBlockSize(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java new file mode 100644 index 0000000..15b6e9f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java @@ -0,0 +1,63 @@ +/* + * 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.cipher; + +/** + * Represents a no-op cipher. + * This cipher can not really be used during authentication and should only + * be used after, so that authentication remains secured, but not the remaining + * of the exchanges. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class CipherNone implements Cipher { + public CipherNone() { + super(); + } + + @Override + public String getAlgorithm() { + return "none"; + } + + @Override + public String getTransformation() { + return "none"; + } + + @Override + public int getIVSize() { + return 8; // dummy + } + + @Override + public int getBlockSize() { + return 16; // dummy + } + + @Override + public void init(Mode mode, byte[] bytes, byte[] bytes1) throws Exception { + // ignored - always succeeds + } + + @Override + public void update(byte[] input, int inputOffset, int inputLen) throws Exception { + // ignored - always succeeds + } +}
