http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/mac/BaseMac.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/BaseMac.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/BaseMac.java new file mode 100644 index 0000000..e1681d4 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/BaseMac.java @@ -0,0 +1,111 @@ +/* + * 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.mac; + +import javax.crypto.spec.SecretKeySpec; + +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * Base class for <code>Mac</code> implementations based on the JCE provider. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BaseMac implements Mac { + + private final String algorithm; + private final int defbsize; + private final int bsize; + private final byte[] tmp; + private javax.crypto.Mac mac; + private String s; + + public BaseMac(String algorithm, int bsize, int defbsize) { + this.algorithm = algorithm; + this.bsize = bsize; + this.defbsize = defbsize; + this.tmp = new byte[defbsize]; + } + + @Override + public final String getAlgorithm() { + return algorithm; + } + + @Override + public final int getBlockSize() { + return bsize; + } + + @Override + public final int getDefaultBlockSize() { + return defbsize; + } + + @Override + public void init(byte[] key) throws Exception { + if (key.length > defbsize) { + byte[] tmp = new byte[defbsize]; + System.arraycopy(key, 0, tmp, 0, defbsize); + key = tmp; + } + + SecretKeySpec skey = new SecretKeySpec(key, algorithm); + mac = SecurityUtils.getMac(algorithm); + mac.init(skey); + } + + @Override + public void updateUInt(long i) { + tmp[0] = (byte) (i >>> 24); + tmp[1] = (byte) (i >>> 16); + tmp[2] = (byte) (i >>> 8); + tmp[3] = (byte) i; + update(tmp, 0, 4); + } + + @Override + public void update(byte buf[], int offset, int len) { + mac.update(buf, offset, len); + } + + @Override + public void doFinal(byte[] buf, int offset) throws Exception { + int blockSize = getBlockSize(); + int defaultSize = getDefaultBlockSize(); + if (blockSize != defaultSize) { + mac.doFinal(tmp, 0); + System.arraycopy(tmp, 0, buf, offset, blockSize); + } else { + mac.doFinal(buf, offset); + } + } + + @Override + public String toString() { + synchronized (this) { + if (s == null) { + s = getClass().getSimpleName() + "[" + getAlgorithm() + "] - " + + " block=" + getBlockSize() + "/" + getDefaultBlockSize() + " bytes"; + } + } + + return s; + } +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/mac/BuiltinMacs.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/BuiltinMacs.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/BuiltinMacs.java new file mode 100644 index 0000000..5a4528d --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/BuiltinMacs.java @@ -0,0 +1,273 @@ +/* + * 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.mac; + +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.TreeMap; + +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 macs + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum BuiltinMacs implements MacFactory { + hmacmd5(Constants.HMAC_MD5, "HmacMD5", 16, 16), + hmacmd596(Constants.HMAC_MD5_96, "HmacMD5", 12, 16), + hmacsha1(Constants.HMAC_SHA1, "HmacSHA1", 20, 20), + hmacsha196(Constants.HMAC_SHA1_96, "HmacSHA1", 12, 20), + /** See <A HREF="https://tools.ietf.org/html/rfc6668">RFC 6668</A> */ + hmacsha256(Constants.HMAC_SHA2_256, "HmacSHA256", 32, 32), + /** See <A HREF="https://tools.ietf.org/html/rfc6668">RFC 6668</A> */ + hmacsha512(Constants.HMAC_SHA2_512, "HmacSHA512", 64, 64); + + public static final Set<BuiltinMacs> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(BuiltinMacs.class)); + + private static final Map<String, MacFactory> EXTENSIONS = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + private final String factoryName; + private final String algorithm; + private final int defbsize; + private final int bsize; + + BuiltinMacs(String factoryName, String algorithm, int bsize, int defbsize) { + this.factoryName = factoryName; + this.algorithm = algorithm; + this.bsize = bsize; + this.defbsize = defbsize; + } + + @Override + public Mac create() { + return new BaseMac(getAlgorithm(), getBlockSize(), getDefaultBlockSize()); + } + + @Override + public final String getName() { + return factoryName; + } + + @Override + public final String getAlgorithm() { + return algorithm; + } + + @Override + public final int getBlockSize() { + return bsize; + } + + @Override + public final int getDefaultBlockSize() { + return defbsize; + } + + @Override + public final boolean isSupported() { + return true; + } + + @Override + public final String toString() { + return getName(); + } + + /** + * 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(MacFactory 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 NavigableSet} of the currently registered extensions, sorted + * according to the factory name (case <U>insensitive</U>) + */ + public static NavigableSet<MacFactory> 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 MacFactory 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 org.apache.sshd.common.mac.BuiltinMacs} whose {@link Enum#name()} matches + * (case <U>insensitive</U>) the provided argument - {@code null} if no match + */ + public static BuiltinMacs fromString(String s) { + if (GenericUtils.isEmpty(s)) { + return null; + } + + for (BuiltinMacs c : VALUES) { + if (s.equalsIgnoreCase(c.name())) { + return c; + } + } + + return null; + } + + /** + * @param factory The {@link org.apache.sshd.common.NamedFactory} for the MAC - ignored if {@code null} + * @return The matching {@link org.apache.sshd.common.mac.BuiltinMacs} whose factory name matches + * (case <U>insensitive</U>) the digest factory name + * @see #fromFactoryName(String) + */ + public static BuiltinMacs fromFactory(NamedFactory<Mac> 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 BuiltinMacs} whose factory name matches + * (case <U>insensitive</U>) the provided name - {@code null} if no match + */ + public static BuiltinMacs fromFactoryName(String name) { + return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES); + } + + /** + * @param macs A comma-separated list of MACs' 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 parseMacsList(String macs) { + return parseMacsList(GenericUtils.split(macs, ',')); + } + + public static ParseResult parseMacsList(String... macs) { + return parseMacsList(GenericUtils.isEmpty((Object[]) macs) ? Collections.emptyList() : Arrays.asList(macs)); + } + + public static ParseResult parseMacsList(Collection<String> macs) { + if (GenericUtils.isEmpty(macs)) { + return ParseResult.EMPTY; + } + + List<MacFactory> factories = new ArrayList<>(macs.size()); + List<String> unknown = Collections.emptyList(); + for (String name : macs) { + MacFactory m = resolveFactory(name); + if (m != null) { + factories.add(m); + } 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 MacFactory resolveFactory(String name) { + if (GenericUtils.isEmpty(name)) { + return null; + } + + MacFactory m = fromFactoryName(name); + if (m != null) { + return m; + } + + synchronized (EXTENSIONS) { + return EXTENSIONS.get(name); + } + } + + public static final class ParseResult extends NamedFactoriesListParseResult<Mac, MacFactory> { + public static final ParseResult EMPTY = new ParseResult(Collections.emptyList(), Collections.emptyList()); + + public ParseResult(List<MacFactory> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + } + + public static final class Constants { + public static final String HMAC_MD5 = "hmac-md5"; + public static final String HMAC_MD5_96 = "hmac-md5-96"; + public static final String HMAC_SHA1 = "hmac-sha1"; + public static final String HMAC_SHA1_96 = "hmac-sha1-96"; + public static final String HMAC_SHA2_256 = "hmac-sha2-256"; + public static final String HMAC_SHA2_512 = "hmac-sha2-512"; + + 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/mac/Mac.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/Mac.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/Mac.java new file mode 100644 index 0000000..4b80447 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/Mac.java @@ -0,0 +1,52 @@ +/* + * 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.mac; + +import org.apache.sshd.common.util.NumberUtils; + +/** + * Message Authentication Code for use in SSH. + * It usually wraps a javax.crypto.Mac class. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface Mac extends MacInformation { + void init(byte[] key) throws Exception; + + default void update(byte[] buf) { + update(buf, 0, NumberUtils.length(buf)); + } + + void update(byte[] buf, int start, int len); + + void updateUInt(long foo); + + default byte[] doFinal() throws Exception { + int blockSize = getBlockSize(); + byte[] buf = new byte[blockSize]; + doFinal(buf); + return buf; + } + + default void doFinal(byte[] buf) throws Exception { + doFinal(buf, 0); + } + + void doFinal(byte[] buf, int offset) throws Exception; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/mac/MacFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/MacFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacFactory.java new file mode 100644 index 0000000..4463600 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacFactory.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.mac; + +import org.apache.sshd.common.BuiltinFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +// CHECKSTYLE:OFF +public interface MacFactory extends MacInformation, BuiltinFactory<Mac> { + // 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/mac/MacInformation.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java new file mode 100644 index 0000000..583165f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java @@ -0,0 +1,41 @@ +/* + * 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.mac; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface MacInformation { + /** + * @return MAC algorithm name + */ + String getAlgorithm(); + + /** + * @return MAC output block size in bytes - may be less than the default + * - e.g., MD5-96 + */ + int getBlockSize(); + + /** + * @return The "natural" MAC block size in bytes + */ + int getDefaultBlockSize(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/mac/package.html ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/package.html b/sshd-common/src/main/java/org/apache/sshd/common/mac/package.html new file mode 100644 index 0000000..52a90ca --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/package.html @@ -0,0 +1,25 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> + +<a href="{@docRoot}/org/apache/sshd/common/mac/Mac.html"><code>Mac</code></a> implementations. + +</body> +</html> http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandom.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandom.java b/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandom.java new file mode 100644 index 0000000..2a1f825 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandom.java @@ -0,0 +1,34 @@ +/* + * 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.random; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractRandom implements Random { + protected AbstractRandom() { + super(); + } + + @Override + public String toString() { + return getName(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandomFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandomFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandomFactory.java new file mode 100644 index 0000000..c1d7893 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/AbstractRandomFactory.java @@ -0,0 +1,43 @@ +/* + * 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.random; + +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractRandomFactory implements RandomFactory { + private final String name; + + protected AbstractRandomFactory(String name) { + this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No name"); + } + + @Override + public final String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandom.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandom.java b/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandom.java new file mode 100644 index 0000000..ba050e6 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandom.java @@ -0,0 +1,60 @@ +/* + * 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.random; + +import java.security.SecureRandom; + +/** + * A <code>Random</code> implementation using the built-in {@link SecureRandom} PRNG. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class JceRandom extends AbstractRandom { + public static final String NAME = "JCE"; + + private byte[] tmp = new byte[16]; + private final SecureRandom random = new SecureRandom(); + + public JceRandom() { + super(); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public synchronized void fill(byte[] foo, int start, int len) { + if ((start == 0) && (len == foo.length)) { + random.nextBytes(foo); + } else { + if (len > tmp.length) { + tmp = new byte[len]; + } + random.nextBytes(tmp); + System.arraycopy(tmp, 0, foo, start, len); + } + } + + @Override + public synchronized int random(int n) { + return random.nextInt(n); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandomFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandomFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandomFactory.java new file mode 100644 index 0000000..450a85e --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/JceRandomFactory.java @@ -0,0 +1,42 @@ +/* + * 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.random; + +/** + * Named factory for the JCE <code>Random</code> + */ +public class JceRandomFactory extends AbstractRandomFactory { + public static final String NAME = "default"; + public static final JceRandomFactory INSTANCE = new JceRandomFactory(); + + public JceRandomFactory() { + super(NAME); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public Random create() { + return new JceRandom(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/Random.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/Random.java b/sshd-common/src/main/java/org/apache/sshd/common/random/Random.java new file mode 100644 index 0000000..6e597ef --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/Random.java @@ -0,0 +1,56 @@ +/* + * 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.random; + +import org.apache.sshd.common.NamedResource; + +/** + * A pseudo random number generator. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface Random extends NamedResource { + /** + * Fill the buffer with random values + * + * @param bytes The bytes to fill + * @see #fill(byte[], int, int) + */ + default void fill(byte[] bytes) { + fill(bytes, 0, bytes.length); + } + + /** + * Fill part of bytes with random values. + * + * @param bytes byte array to be filled. + * @param start index to start filling at. + * @param len length of segment to fill. + */ + void fill(byte[] bytes, int start, int len); + + /** + * Returns a pseudo-random uniformly distributed {@code int} + * in the half-open range [0, n). + * + * @param n The range upper limit + * @return The randomly selected value in the range + */ + int random(int n); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/RandomFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/RandomFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/random/RandomFactory.java new file mode 100644 index 0000000..dded06f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/RandomFactory.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.random; + +import org.apache.sshd.common.BuiltinFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +// CHECKSTYLE:OFF +public interface RandomFactory extends BuiltinFactory<Random> { + // 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/random/SingletonRandomFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java new file mode 100644 index 0000000..ce24112 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java @@ -0,0 +1,70 @@ +/* + * 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.random; + +import java.util.Objects; + +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.OptionalFeature; + +/** + * A random factory wrapper that uses a single random instance. + * The underlying random instance has to be thread safe. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class SingletonRandomFactory extends AbstractRandom implements RandomFactory { + + private final NamedFactory<Random> factory; + private final Random random; + + public SingletonRandomFactory(NamedFactory<Random> factory) { + this.factory = Objects.requireNonNull(factory, "No factory"); + this.random = Objects.requireNonNull(factory.create(), "No random instance created"); + } + + @Override + public boolean isSupported() { + if (factory instanceof OptionalFeature) { + return ((OptionalFeature) factory).isSupported(); + } else { + return true; + } + } + + @Override + public void fill(byte[] bytes, int start, int len) { + random.fill(bytes, start, len); + } + + @Override + public int random(int max) { + return random.random(max); + } + + @Override + public String getName() { + return factory.getName(); + } + + @Override + public Random create() { + return this; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/random/package.html ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/random/package.html b/sshd-common/src/main/java/org/apache/sshd/common/random/package.html new file mode 100644 index 0000000..0e94d7f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/random/package.html @@ -0,0 +1,25 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> + +<a href="{@docRoot}/org/apache/sshd/common/random/Random.html"><code>Random</code></a> implementations. + +</body> +</html> http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java new file mode 100644 index 0000000..ef06d15 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java @@ -0,0 +1,149 @@ +/* + * 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.signature; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Objects; + +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * Useful base class for {@link Signature} implementation + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractSignature implements Signature { + private java.security.Signature signatureInstance; + private final String algorithm; + + protected AbstractSignature(String algorithm) { + this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No signature algorithm specified"); + } + + @Override + public final String getAlgorithm() { + return algorithm; + } + + /** + * Initializes the internal signature instance + * + * @param algo The signature's algorithm + * @param forSigning If {@code true} then it is being initialized for signing, + * otherwise for verifying a signature + * @return The {@link java.security.Signature} instance + * @throws GeneralSecurityException if failed to initialize + */ + protected java.security.Signature doInitSignature(String algo, boolean forSigning) throws GeneralSecurityException { + return SecurityUtils.getSignature(algo); + } + + /** + * @return The current {@link java.security.Signature} instance + * - {@code null} if not initialized + * @see #doInitSignature(String, boolean) + */ + protected java.security.Signature getSignature() { + return signatureInstance; + } + + @Override + public byte[] sign() throws Exception { + java.security.Signature signature = Objects.requireNonNull(getSignature(), "Signature not initialized"); + return signature.sign(); + } + + @Override + public void initVerifier(PublicKey key) throws Exception { + String algo = getAlgorithm(); + signatureInstance = Objects.requireNonNull(doInitSignature(algo, false), "No signature instance create"); + signatureInstance.initVerify(Objects.requireNonNull(key, "No public key provided")); + } + + @Override + public void initSigner(PrivateKey key) throws Exception { + String algo = getAlgorithm(); + signatureInstance = Objects.requireNonNull(doInitSignature(algo, true), "No signature instance create"); + signatureInstance.initSign(Objects.requireNonNull(key, "No private key provided")); + } + + @Override + public void update(byte[] hash, int off, int len) throws Exception { + java.security.Signature signature = Objects.requireNonNull(getSignature(), "Signature not initialized"); + signature.update(hash, off, len); + } + + /** + * Makes an attempt to detect if the signature is encoded or pure data + * + * @param sig The original signature + * @return A {@link SimpleImmutableEntry} where first value is the key type and second + * value is the data - {@code null} if not encoded + */ + protected SimpleImmutableEntry<String, byte[]> extractEncodedSignature(byte[] sig) { + final int dataLen = NumberUtils.length(sig); + // if it is encoded then we must have at least 2 UINT32 values + if (dataLen < (2 * Integer.BYTES)) { + return null; + } + + long keyTypeLen = BufferUtils.getUInt(sig, 0, dataLen); + // after the key type we MUST have data bytes + if (keyTypeLen >= (dataLen - Integer.BYTES)) { + return null; + } + + int keyTypeStartPos = Integer.BYTES; + int keyTypeEndPos = keyTypeStartPos + (int) keyTypeLen; + int remainLen = dataLen - keyTypeEndPos; + // must have UINT32 with the data bytes length + if (remainLen < Integer.BYTES) { + return null; + } + + long dataBytesLen = BufferUtils.getUInt(sig, keyTypeEndPos, remainLen); + // make sure reported number of bytes does not exceed available + if (dataBytesLen > (remainLen - Integer.BYTES)) { + return null; + } + + String keyType = new String(sig, keyTypeStartPos, (int) keyTypeLen, StandardCharsets.UTF_8); + byte[] data = new byte[(int) dataBytesLen]; + System.arraycopy(sig, keyTypeEndPos + Integer.BYTES, data, 0, (int) dataBytesLen); + return new SimpleImmutableEntry<>(keyType, data); + } + + protected boolean doVerify(byte[] data) throws SignatureException { + java.security.Signature signature = Objects.requireNonNull(getSignature(), "Signature not initialized"); + return signature.verify(data); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getAlgorithm() + "]"; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java new file mode 100644 index 0000000..7d4e1e2 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java @@ -0,0 +1,305 @@ +/* + * 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.signature; + +import java.security.spec.ECParameterSpec; +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.TreeMap; + +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.cipher.ECCurves; +import org.apache.sshd.common.config.NamedFactoriesListParseResult; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * Provides easy access to the currently implemented signatures + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum BuiltinSignatures implements SignatureFactory { + dsa(KeyPairProvider.SSH_DSS) { + @Override + public Signature create() { + return new SignatureDSA(); + } + }, + rsa(KeyPairProvider.SSH_RSA) { + @Override + public Signature create() { + return new SignatureRSA(); + } + }, + nistp256(KeyPairProvider.ECDSA_SHA2_NISTP256) { + @Override + public Signature create() { + return new SignatureECDSA.SignatureECDSA256(); + } + + @Override + public boolean isSupported() { + return SecurityUtils.isECCSupported(); + } + }, + nistp384(KeyPairProvider.ECDSA_SHA2_NISTP384) { + @Override + public Signature create() { + return new SignatureECDSA.SignatureECDSA384(); + } + + @Override + public boolean isSupported() { + return SecurityUtils.isECCSupported(); + } + }, + nistp521(KeyPairProvider.ECDSA_SHA2_NISTP521) { + @Override + public Signature create() { + return new SignatureECDSA.SignatureECDSA521(); + } + + @Override + public boolean isSupported() { + return SecurityUtils.isECCSupported(); + } + }, + ed25519(KeyPairProvider.SSH_ED25519) { + @Override + public Signature create() { + return SecurityUtils.getEDDSASigner(); + } + + @Override + public boolean isSupported() { + return SecurityUtils.isEDDSACurveSupported(); + } + }; + + public static final Set<BuiltinSignatures> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(BuiltinSignatures.class)); + + private static final Map<String, SignatureFactory> EXTENSIONS = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + private final String factoryName; + + BuiltinSignatures(String facName) { + factoryName = facName; + } + + public static Signature getByCurveSize(ECParameterSpec params) { + int curveSize = ECCurves.getCurveSize(params); + if (curveSize <= 256) { + return nistp256.create(); + } else if (curveSize <= 384) { + return nistp384.create(); + } else { + return nistp521.create(); + } + } + + @Override + public final String getName() { + return factoryName; + } + + @Override + public final String toString() { + return getName(); + } + + @Override + public boolean isSupported() { + return true; + } + + /** + * 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(SignatureFactory 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 NavigableSet} of the currently registered extensions, sorted + * according to the factory name (case <U>insensitive</U>) + */ + public static NavigableSet<SignatureFactory> 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 SignatureFactory 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 org.apache.sshd.common.signature.BuiltinSignatures} whose {@link Enum#name()} matches + * (case <U>insensitive</U>) the provided argument - {@code null} if no match + */ + public static BuiltinSignatures fromString(String s) { + if (GenericUtils.isEmpty(s)) { + return null; + } + + for (BuiltinSignatures c : VALUES) { + if (s.equalsIgnoreCase(c.name())) { + return c; + } + } + + return null; + } + + /** + * @param factory The {@link org.apache.sshd.common.NamedFactory} for the signature - ignored if {@code null} + * @return The matching {@link org.apache.sshd.common.signature.BuiltinSignatures} whose factory name matches + * (case <U>insensitive</U>) the digest factory name + * @see #fromFactoryName(String) + */ + public static BuiltinSignatures fromFactory(NamedFactory<Signature> 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 BuiltinSignatures} whose factory name matches + * (case <U>insensitive</U>) the provided name - {@code null} if no match + */ + public static BuiltinSignatures fromFactoryName(String name) { + return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES); + } + + /** + * @param sigs A comma-separated list of signatures' names - ignored + * if {@code null}/empty + * @return A {@link ParseResult} of all the {@link NamedFactory} whose + * name appears in the string and represent a built-in signature. Any + * unknown name is <U>ignored</U>. The order of the returned result + * is the same as the original order - bar the unknown signatures. + * <B>Note:</B> it is up to caller to ensure that the list does not + * contain duplicates + */ + public static ParseResult parseSignatureList(String sigs) { + return parseSignatureList(GenericUtils.split(sigs, ',')); + } + + public static ParseResult parseSignatureList(String... sigs) { + return parseSignatureList(GenericUtils.isEmpty((Object[]) sigs) ? Collections.emptyList() : Arrays.asList(sigs)); + } + + public static ParseResult parseSignatureList(Collection<String> sigs) { + if (GenericUtils.isEmpty(sigs)) { + return ParseResult.EMPTY; + } + + List<SignatureFactory> factories = new ArrayList<>(sigs.size()); + List<String> unknown = Collections.emptyList(); + for (String name : sigs) { + SignatureFactory s = resolveFactory(name); + if (s != null) { + factories.add(s); + } 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 SignatureFactory resolveFactory(String name) { + if (GenericUtils.isEmpty(name)) { + return null; + } + + SignatureFactory s = fromFactoryName(name); + if (s != null) { + return s; + } + + synchronized (EXTENSIONS) { + return EXTENSIONS.get(name); + } + } + + /** + * Holds the result of the {@link BuiltinSignatures#parseSignatureList(String)} + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ + public static final class ParseResult extends NamedFactoriesListParseResult<Signature, SignatureFactory> { + public static final ParseResult EMPTY = new ParseResult(Collections.emptyList(), Collections.emptyList()); + + public ParseResult(List<SignatureFactory> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java new file mode 100644 index 0000000..fd88a1d --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java @@ -0,0 +1,88 @@ +/* + * 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.signature; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import org.apache.sshd.common.util.NumberUtils; + +/** + * Signature interface for SSH used to sign or verify packets + * Usually wraps a javax.crypto.Signature object + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface Signature { + /** + * @return The signature algorithm name + */ + String getAlgorithm(); + + /** + * @param key The {@link PublicKey} to be used for verifying signatures + * @throws Exception If failed to initialize + */ + void initVerifier(PublicKey key) throws Exception; + + /** + * @param key The {@link PrivateKey} to be used for signing + * @throws Exception If failed to initialize + */ + void initSigner(PrivateKey key) throws Exception; + + /** + * Update the computed signature with the given data + * + * @param hash The hash data buffer + * @throws Exception If failed to update + * @see #update(byte[], int, int) + */ + default void update(byte[] hash) throws Exception { + update(hash, 0, NumberUtils.length(hash)); + } + + /** + * Update the computed signature with the given data + * + * @param hash The hash data buffer + * @param off Offset of hash data in buffer + * @param len Length of hash data + * @throws Exception If failed to update + */ + void update(byte[] hash, int off, int len) throws Exception; + + /** + * Verify against the given signature + * + * @param sig The signed data + * @return {@code true} if signature is valid + * @throws Exception If failed to extract signed data for validation + */ + boolean verify(byte[] sig) throws Exception; + + /** + * Compute the signature + * + * @return The signature value + * @throws Exception If failed to calculate the signature + */ + byte[] sign() throws Exception; + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java new file mode 100644 index 0000000..1f552bd --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.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.signature; + +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.security.SignatureException; +import java.util.Map; + +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.io.der.DERParser; +import org.apache.sshd.common.util.io.der.DERWriter; + + +/** + * DSA <code>Signature</code> + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-6.6">RFC4253 section 6.6</A> + */ +public class SignatureDSA extends AbstractSignature { + public static final String DEFAULT_ALGORITHM = "SHA1withDSA"; + + public static final int DSA_SIGNATURE_LENGTH = 40; + // result must be 40 bytes, but length of r and s may not exceed 20 bytes + public static final int MAX_SIGNATURE_VALUE_LENGTH = DSA_SIGNATURE_LENGTH / 2; + + public SignatureDSA() { + this(DEFAULT_ALGORITHM); + } + + protected SignatureDSA(String algorithm) { + super(algorithm); + } + + @Override + public byte[] sign() throws Exception { + byte[] sig = super.sign(); + + try (DERParser parser = new DERParser(sig)) { + int type = parser.read(); + if (type != 0x30) { + throw new StreamCorruptedException("Invalid signature format - not a DER SEQUENCE: 0x" + Integer.toHexString(type)); + } + + // length of remaining encoding of the 2 integers + int remainLen = parser.readLength(); + /* + * There are supposed to be 2 INTEGERs, each encoded with: + * + * - one byte representing the fact that it is an INTEGER + * - one byte of the integer encoding length + * - at least one byte of integer data (zero length is not an option) + */ + if (remainLen < (2 * 3)) { + throw new StreamCorruptedException("Invalid signature format - not enough encoded data length: " + remainLen); + } + + BigInteger r = parser.readBigInteger(); + BigInteger s = parser.readBigInteger(); + + byte[] result = new byte[DSA_SIGNATURE_LENGTH]; + putBigInteger(r, result, 0); + putBigInteger(s, result, MAX_SIGNATURE_VALUE_LENGTH); + return result; + } + } + + public static void putBigInteger(BigInteger value, byte[] result, int offset) { + byte[] data = value.toByteArray(); + boolean maxExceeded = data.length > MAX_SIGNATURE_VALUE_LENGTH; + int dstOffset = maxExceeded ? 0 : (MAX_SIGNATURE_VALUE_LENGTH - data.length); + System.arraycopy(data, maxExceeded ? 1 : 0, + result, offset + dstOffset, + Math.min(MAX_SIGNATURE_VALUE_LENGTH, data.length)); + } + + @Override + public boolean verify(byte[] sig) throws Exception { + int sigLen = NumberUtils.length(sig); + byte[] data = sig; + + if (sigLen != DSA_SIGNATURE_LENGTH) { + // probably some encoded data + Map.Entry<String, byte[]> encoding = extractEncodedSignature(sig); + if (encoding != null) { + String keyType = encoding.getKey(); + ValidateUtils.checkTrue(KeyPairProvider.SSH_DSS.equals(keyType), "Mismatched key type: %s", keyType); + data = encoding.getValue(); + sigLen = NumberUtils.length(data); + } + } + + if (sigLen != DSA_SIGNATURE_LENGTH) { + throw new SignatureException("Bad signature length (" + sigLen + " instead of " + DSA_SIGNATURE_LENGTH + ")" + + " for " + BufferUtils.toHex(':', data)); + } + + byte[] rEncoding; + try (DERWriter w = new DERWriter(MAX_SIGNATURE_VALUE_LENGTH + 4)) { // in case length > 0x7F + w.writeBigInteger(data, 0, MAX_SIGNATURE_VALUE_LENGTH); + rEncoding = w.toByteArray(); + } + + byte[] sEncoding; + try (DERWriter w = new DERWriter(MAX_SIGNATURE_VALUE_LENGTH + 4)) { // in case length > 0x7F + w.writeBigInteger(data, MAX_SIGNATURE_VALUE_LENGTH, MAX_SIGNATURE_VALUE_LENGTH); + sEncoding = w.toByteArray(); + } + + int length = rEncoding.length + sEncoding.length; + byte[] encoded; + try (DERWriter w = new DERWriter(1 + length + 4)) { // in case length > 0x7F + w.write(0x30); // SEQUENCE + w.writeLength(length); + w.write(rEncoding); + w.write(sEncoding); + encoded = w.toByteArray(); + } + + return doVerify(encoded); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java new file mode 100644 index 0000000..56964d3 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java @@ -0,0 +1,144 @@ +/* + * 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.signature; + +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.util.Map; + +import org.apache.sshd.common.cipher.ECCurves; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; +import org.apache.sshd.common.util.io.der.DERParser; +import org.apache.sshd.common.util.io.der.DERWriter; + +/** + * Signature algorithm for EC keys using ECDSA. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="http://tools.ietf.org/html/rfc3278#section-8.2">RFC3278 section 8.2</A> + */ +public class SignatureECDSA extends AbstractSignature { + public static class SignatureECDSA256 extends SignatureECDSA { + public static final String DEFAULT_ALGORITHM = "SHA256withECDSA"; + + public SignatureECDSA256() { + super(DEFAULT_ALGORITHM); + } + } + + public static class SignatureECDSA384 extends SignatureECDSA { + public static final String DEFAULT_ALGORITHM = "SHA384withECDSA"; + + public SignatureECDSA384() { + super(DEFAULT_ALGORITHM); + } + } + + public static class SignatureECDSA521 extends SignatureECDSA { + public static final String DEFAULT_ALGORITHM = "SHA512withECDSA"; + + public SignatureECDSA521() { + super(DEFAULT_ALGORITHM); + } + } + + protected SignatureECDSA(String algo) { + super(algo); + } + + @Override + public byte[] sign() throws Exception { + byte[] sig = super.sign(); + + try (DERParser parser = new DERParser(sig)) { + int type = parser.read(); + if (type != 0x30) { + throw new StreamCorruptedException("Invalid signature format - not a DER SEQUENCE: 0x" + Integer.toHexString(type)); + } + + // length of remaining encoding of the 2 integers + int remainLen = parser.readLength(); + /* + * There are supposed to be 2 INTEGERs, each encoded with: + * + * - one byte representing the fact that it is an INTEGER + * - one byte of the integer encoding length + * - at least one byte of integer data (zero length is not an option) + */ + if (remainLen < (2 * 3)) { + throw new StreamCorruptedException("Invalid signature format - not enough encoded data length: " + remainLen); + } + + BigInteger r = parser.readBigInteger(); + BigInteger s = parser.readBigInteger(); + // Write the <r,s> to its own types writer. + Buffer rsBuf = new ByteArrayBuffer(); + rsBuf.putMPInt(r); + rsBuf.putMPInt(s); + + return rsBuf.getCompactData(); + } + } + + @Override + public boolean verify(byte[] sig) throws Exception { + byte[] data = sig; + Map.Entry<String, byte[]> encoding = extractEncodedSignature(data); + if (encoding != null) { + String keyType = encoding.getKey(); + ECCurves curve = ECCurves.fromKeyType(keyType); + ValidateUtils.checkNotNull(curve, "Unknown curve type: %s", keyType); + data = encoding.getValue(); + } + + Buffer rsBuf = new ByteArrayBuffer(data); + byte[] rArray = rsBuf.getMPIntAsBytes(); + byte[] rEncoding; + try (DERWriter w = new DERWriter(rArray.length + 4)) { // in case length > 0x7F + w.writeBigInteger(rArray); + rEncoding = w.toByteArray(); + } + + byte[] sArray = rsBuf.getMPIntAsBytes(); + byte[] sEncoding; + try (DERWriter w = new DERWriter(sArray.length + 4)) { // in case length > 0x7F + w.writeBigInteger(sArray); + sEncoding = w.toByteArray(); + } + + int remaining = rsBuf.available(); + if (remaining != 0) { + throw new StreamCorruptedException("Signature had padding - remaining=" + remaining); + } + + int length = rEncoding.length + sEncoding.length; + byte[] encoded; + try (DERWriter w = new DERWriter(1 + length + 4)) { // in case length > 0x7F + w.write(0x30); // SEQUENCE + w.writeLength(length); + w.write(rEncoding); + w.write(sEncoding); + encoded = w.toByteArray(); + } + + return doVerify(encoded); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactoriesManager.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactoriesManager.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactoriesManager.java new file mode 100644 index 0000000..c9d876a --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactoriesManager.java @@ -0,0 +1,94 @@ +/* + * 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.signature; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * Manage the list of named factories for <code>Signature</code>. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SignatureFactoriesManager { + /** + * @return The list of named <code>Signature</code> factories + */ + List<NamedFactory<Signature>> getSignatureFactories(); + + default String getSignatureFactoriesNameList() { + return NamedResource.getNames(getSignatureFactories()); + } + + default List<String> getSignatureFactoriesNames() { + return NamedResource.getNameList(getSignatureFactories()); + } + + void setSignatureFactories(List<NamedFactory<Signature>> factories); + + default void setSignatureFactoriesNameList(String names) { + setSignatureFactoriesNames(GenericUtils.split(names, ',')); + } + + default void setSignatureFactoriesNames(String... names) { + setSignatureFactoriesNames(GenericUtils.isEmpty((Object[]) names) ? Collections.emptyList() : Arrays.asList(names)); + } + + default void setSignatureFactoriesNames(Collection<String> names) { + BuiltinSignatures.ParseResult result = BuiltinSignatures.parseSignatureList(names); + @SuppressWarnings({ "rawtypes", "unchecked" }) + List<NamedFactory<Signature>> factories = + (List) ValidateUtils.checkNotNullAndNotEmpty(result.getParsedFactories(), "No supported signature factories: %s", names); + Collection<String> unsupported = result.getUnsupportedFactories(); + ValidateUtils.checkTrue(GenericUtils.isEmpty(unsupported), "Unsupported signature factories found: %s", unsupported); + setSignatureFactories(factories); + } + + /** + * Attempts to use the primary manager's signature factories if not {@code null}/empty, + * otherwise uses the secondary ones (regardless of whether there are any...) + * + * @param primary The primary {@link SignatureFactoriesManager} + * @param secondary The secondary {@link SignatureFactoriesManager} + * @return The resolved signature factories - may be {@code null}/empty + * @see #getSignatureFactories(SignatureFactoriesManager) + */ + static List<NamedFactory<Signature>> resolveSignatureFactories( + SignatureFactoriesManager primary, SignatureFactoriesManager secondary) { + List<NamedFactory<Signature>> factories = getSignatureFactories(primary); + return GenericUtils.isEmpty(factories) ? getSignatureFactories(secondary) : factories; + } + + /** + * @param manager The {@link SignatureFactoriesManager} instance - ignored if {@code null} + * @return The associated list of named <code>Signature</code> factories or {@code null} if + * no manager instance + */ + static List<NamedFactory<Signature>> getSignatureFactories(SignatureFactoriesManager manager) { + return (manager == null) ? null : manager.getSignatureFactories(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java new file mode 100644 index 0000000..0881714 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.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.signature; + +import org.apache.sshd.common.BuiltinFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +// CHECKSTYLE:OFF +public interface SignatureFactory extends BuiltinFactory<Signature> { + // 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/signature/SignatureRSA.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java new file mode 100644 index 0000000..ad1d37e --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.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.signature; + +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.interfaces.RSAKey; +import java.util.Map; + +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * RSA <code>Signature</code> + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-6.6">RFC4253 section 6.6</A> + */ +public class SignatureRSA extends AbstractSignature { + public static final String DEFAULT_ALGORITHM = "SHA1withRSA"; + + private int verifierSignatureSize = -1; + + public SignatureRSA() { + super(DEFAULT_ALGORITHM); + } + + protected SignatureRSA(String algorithm) { + super(algorithm); + } + + /** + * @return The expected number of bytes in the signature - non-positive + * if not initialized or not intended to be used for verification + */ + protected int getVerifierSignatureSize() { + return verifierSignatureSize; + } + + @Override + public void initVerifier(PublicKey key) throws Exception { + super.initVerifier(key); + RSAKey rsaKey = ValidateUtils.checkInstanceOf(key, RSAKey.class, "Not an RSA key"); + verifierSignatureSize = getVerifierSignatureSize(rsaKey); + } + + public static int getVerifierSignatureSize(RSAKey key) { + BigInteger modulus = key.getModulus(); + return (modulus.bitLength() + Byte.SIZE - 1) / Byte.SIZE; + } + + @Override + public boolean verify(byte[] sig) throws Exception { + byte[] data = sig; + Map.Entry<String, byte[]> encoding = extractEncodedSignature(data); + if (encoding != null) { + String keyType = encoding.getKey(); + ValidateUtils.checkTrue(KeyPairProvider.SSH_RSA.equals(keyType), "Mismatched key type: %s", keyType); + data = encoding.getValue(); + } + + int expectedSize = getVerifierSignatureSize(); + ValidateUtils.checkTrue(expectedSize > 0, "Signature verification size has not been initialized"); + // Pad with zero if value is trimmed + if (data.length < expectedSize) { + byte[] pad = new byte[expectedSize]; + System.arraycopy(data, 0, pad, pad.length - data.length, data.length); + data = pad; + } + + return doVerify(data); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/signature/package.html ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/package.html b/sshd-common/src/main/java/org/apache/sshd/common/signature/package.html new file mode 100644 index 0000000..d1b16e4 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/package.html @@ -0,0 +1,25 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> + +<a href="{@docRoot}/org/apache/sshd/common/signature/Signature.html"><code>Signature</code></a> implementations. + +</body> +</html>
