Repository: mina-sshd Updated Branches: refs/heads/master d13c67944 -> e767438ae
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java new file mode 100644 index 0000000..5f37bd7 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java @@ -0,0 +1,151 @@ +/* + * 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.util.io.der; + +import java.io.ByteArrayInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.util.Arrays; + +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; + +/** + * A bare minimum DER parser - just enough to be able to decode + * signatures and private keys + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class DERParser extends FilterInputStream { + /** + * Maximum size of data allowed by {@link #readLength()} - it is a bit + * arbitrary since one can encode 32-bit length data, but it is good + * enough for the keys + */ + public static final int MAX_DER_VALUE_LENGTH = 2 * Short.MAX_VALUE; + + private final byte[] lenBytes = new byte[Integer.BYTES]; + + public DERParser(byte... bytes) { + this(bytes, 0, NumberUtils.length(bytes)); + } + + public DERParser(byte[] bytes, int offset, int len) { + this(new ByteArrayInputStream(bytes, offset, len)); + } + + public DERParser(InputStream s) { + super(s); + } + + /** + * Decode the length of the field. Can only support length + * encoding up to 4 octets. In BER/DER encoding, length can + * be encoded in 2 forms: + * <ul> + * <li><p> + * Short form - One octet. Bit 8 has value "0" and bits 7-1 + * give the length. + * </p></li> + * + * <li><p> + * Long form - Two to 127 octets (only 4 is supported here). + * Bit 8 of first octet has value "1" and bits 7-1 give the + * number of additional length octets. Second and following + * octets give the length, base 256, most significant digit + * first. + * </p></li> + * </ul> + * + * @return The length as integer + * @throws IOException If invalid format found + */ + public int readLength() throws IOException { + int i = read(); + if (i == -1) { + throw new StreamCorruptedException("Invalid DER: length missing"); + } + + // A single byte short length + if ((i & ~0x7F) == 0) { + return i; + } + + int num = i & 0x7F; + // TODO We can't handle length longer than 4 bytes + if ((i >= 0xFF) || (num > lenBytes.length)) { + throw new StreamCorruptedException("Invalid DER: length field too big: " + i); + } + + // place the read bytes last so that the 1st ones are zeroes as big endian + Arrays.fill(lenBytes, (byte) 0); + int n = read(lenBytes, 4 - num, num); + if (n < num) { + throw new StreamCorruptedException("Invalid DER: length data too short: expected=" + num + ", actual=" + n); + } + + long len = BufferUtils.getUInt(lenBytes); + if (len < 0x7FL) { // according to standard: "the shortest possible length encoding must be used" + throw new StreamCorruptedException("Invalid DER: length not in shortest form: " + len); + } + + if (len > MAX_DER_VALUE_LENGTH) { + throw new StreamCorruptedException("Invalid DER: data length too big: " + len + " (max=" + MAX_DER_VALUE_LENGTH + ")"); + } + + // we know the cast is safe since it is less than MAX_DER_VALUE_LENGTH which is ~64K + return (int) len; + } + + public ASN1Object readObject() throws IOException { + int tag = read(); + if (tag == -1) { + return null; + } + + int length = readLength(); + byte[] value = new byte[length]; + int n = read(value); + if (n < length) { + throw new StreamCorruptedException("Invalid DER: stream too short, missing value: read " + n + " out of required " + length); + } + + return new ASN1Object((byte) tag, length, value); + } + + public BigInteger readBigInteger() throws IOException { + int type = read(); + if (type != 0x02) { + throw new StreamCorruptedException("Invalid DER: data type is not an INTEGER: 0x" + Integer.toHexString(type)); + } + + int len = readLength(); + byte[] value = new byte[len]; + int n = read(value); + if (n < len) { + throw new StreamCorruptedException("Invalid DER: stream too short, missing value: read " + n + " out of required " + len); + } + + return new BigInteger(value); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java new file mode 100644 index 0000000..870ea4a --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java @@ -0,0 +1,147 @@ +/* + * 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.util.io.der; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +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.buffer.ByteArrayBuffer; + +/** + * A bare-minimum DER encoder - just enough so we can encoder signatures + * and keys data + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class DERWriter extends FilterOutputStream { + private final byte[] lenBytes = new byte[Integer.BYTES]; + + public DERWriter() { + this(ByteArrayBuffer.DEFAULT_SIZE); + } + + public DERWriter(int initialSize) { + this(new ByteArrayOutputStream(initialSize)); + } + + public DERWriter(OutputStream stream) { + super(Objects.requireNonNull(stream, "No output stream")); + } + + public DERWriter startSequence() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + AtomicBoolean dataWritten = new AtomicBoolean(false); + @SuppressWarnings("resource") + DERWriter encloser = this; + return new DERWriter(baos) { + @Override + public void close() throws IOException { + baos.close(); + + if (!dataWritten.getAndSet(true)) { // detect repeated calls and write this only once + encloser.writeObject(new ASN1Object(ASN1Class.UNIVERSAL, ASN1Type.SEQUENCE, false, baos.size(), baos.toByteArray())); + } + } + }; + } + + public void writeBigInteger(BigInteger value) throws IOException { + writeBigInteger(Objects.requireNonNull(value, "No value").toByteArray()); + } + + public void writeBigInteger(byte... bytes) throws IOException { + writeBigInteger(bytes, 0, NumberUtils.length(bytes)); + } + + public void writeBigInteger(byte[] bytes, int off, int len) throws IOException { + // ASN.1 - zero padding if 1st byte is > 0x7F + int padLen = ((len > 0) && ((bytes[off] & 0x80) != 0)) ? 1 : 0; + + write(0x02); // indicate it is an INTEGER + writeLength(len + padLen); + for (int index = 0; index < padLen; index++) { + write(0); + } + + write(bytes, off, len); + } + + + public void writeObject(ASN1Object obj) throws IOException { + Objects.requireNonNull(obj, "No ASN.1 object"); + + ASN1Type type = obj.getObjType(); + byte typeValue = type.getTypeValue(); + ASN1Class clazz = obj.getObjClass(); + byte classValue = clazz.getClassValue(); + byte tagValue = (byte) (((classValue << 6) & 0xC0) | (typeValue & 0x1F)); + writeObject(tagValue, obj.getLength(), obj.getValue()); + } + + public void writeObject(byte tag, int len, byte... data) throws IOException { + write(tag & 0xFF); + writeLength(len); + write(data, 0, len); + } + + public void writeLength(int len) throws IOException { + ValidateUtils.checkTrue(len >= 0, "Invalid length: %d", len); + + // short form - MSBit is zero + if (len <= 127) { + write(len); + return; + } + + BufferUtils.putUInt(len, lenBytes); + + int nonZeroPos = 0; + for (; nonZeroPos < lenBytes.length; nonZeroPos++) { + if (lenBytes[nonZeroPos] != 0) { + break; + } + } + + if (nonZeroPos >= lenBytes.length) { + throw new StreamCorruptedException("All zeroes length representation for len=" + len); + } + + int bytesLen = lenBytes.length - nonZeroPos; + write(0x80 | bytesLen); // indicate number of octets + write(lenBytes, nonZeroPos, bytesLen); + } + + public byte[] toByteArray() throws IOException { + if (this.out instanceof ByteArrayOutputStream) { + return ((ByteArrayOutputStream) this.out).toByteArray(); + } else { + throw new IOException("The underlying stream is not a byte[] stream"); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java index b94ee4c..9ea871d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -38,7 +38,6 @@ import java.security.Signature; import java.security.cert.CertificateFactory; import java.security.spec.InvalidKeySpecException; import java.util.Collection; -import java.util.LinkedList; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -59,6 +58,7 @@ import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser; +import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.random.JceRandomFactory; import org.apache.sshd.common.random.RandomFactory; @@ -407,6 +407,11 @@ public final class SecurityUtils { return new BouncyCastleGeneratorHostKeyProvider(path); } + public static KeyPairResourceParser getBouncycastleKeyPairResourceParser() { + ValidateUtils.checkTrue(isBouncyCastleRegistered(), "BouncyCastle not registered"); + return BouncyCastleKeyPairResourceParser.INSTANCE; + } + /** * @return If {@link #isBouncyCastleRegistered()} then a {@link BouncyCastleRandomFactory} * instance, otherwise a {@link JceRandomFactory} one @@ -517,6 +522,14 @@ public final class SecurityUtils { return isEDDSACurveSupported() ? EdDSASecurityProvider.compareEDDSAPrivateKeys(k1, k2) : false; } + public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + if (!isEDDSACurveSupported()) { + throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); + } + + return EdDSASecurityProvider.recoverEDDSAPublicKey(key); + } + public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException { if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { throw new InvalidKeyException("Unsupported key type: " + keyType); @@ -571,19 +584,20 @@ public final class SecurityUtils { return parser; } - Collection<KeyPairResourceParser> available = new LinkedList<>(); - available.add(OpenSSHKeyPairResourceParser.INSTANCE); - if (isBouncyCastleRegistered()) { - available.add(BouncyCastleKeyPairResourceParser.INSTANCE); - } - - parser = KeyPairResourceParser.aggregate(available); + parser = KeyPairResourceParser.aggregate( + PEMResourceParserUtils.PROXY, + OpenSSHKeyPairResourceParser.INSTANCE); KEYPAIRS_PARSER_HODLER.set(parser); } return parser; } + /** + * @param parser The system-wide {@code KeyPairResourceParser} to use. + * If set to {@code null}, then the default parser will be re-constructed + * on next call to {@link #getKeyPairResourceParser()} + */ public static void setKeyPairResourceParser(KeyPairResourceParser parser) { synchronized (KEYPAIRS_PARSER_HODLER) { KEYPAIRS_PARSER_HODLER.set(parser); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProvider.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProvider.java index 36abac0..e039547 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProvider.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProvider.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.util.security.eddsa; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -95,6 +96,18 @@ public class EdDSASecurityProvider extends Provider { return EdDSANamedCurveTable.CURVE_ED25519_SHA512.equalsIgnoreCase(algorithm); } + public static EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); + if (!(key instanceof EdDSAPrivateKey)) { + throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA); + } + + EdDSAPrivateKey prvKey = (EdDSAPrivateKey) key; + EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getSeed(), prvKey.getParams()); + KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); + return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec)); + } + public static org.apache.sshd.common.signature.Signature getEDDSASignature() { ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); return new SignatureEd25519(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java index 3046eed..b05a4cc 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -98,13 +98,7 @@ public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntr @Override public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException { - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); - } - - EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getSeed(), prvKey.getParams()); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec)); + return EdDSASecurityProvider.recoverEDDSAPublicKey(prvKey); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java index 016be06..8858d32 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java @@ -44,7 +44,7 @@ import org.apache.sshd.server.SshServer; * Loads server identity key files - e.g., {@code /etc/ssh/ssh_host_rsa_key} * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - * @see org.apache.sshd.common.util.security.SecurityUtils#isBouncyCastleRegistered() + * @see org.apache.sshd.common.util.security.SecurityUtils#getKeyPairResourceParser() */ public final class ServerIdentity { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java index 6f41196..896f37d 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java @@ -33,9 +33,7 @@ import org.apache.sshd.common.config.keys.IdentityUtils; import org.apache.sshd.common.keyprovider.KeyIdentityProvider; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.util.test.BaseTestSupport; -import org.junit.Assume; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; @@ -51,8 +49,6 @@ public class ClientIdentityTest extends BaseTestSupport { @Test public void testLoadClientIdentities() throws Exception { - Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered()); - Path resFolder = getClassResourcesFolder(TEST_SUBFOLDER, getClass()); LinkOption[] options = IoUtils.getLinkOptions(false); Collection<BuiltinIdentities> expected = EnumSet.noneOf(BuiltinIdentities.class); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscatorTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscatorTest.java b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscatorTest.java new file mode 100644 index 0000000..352283a --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscatorTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sshd.common.config.keys.loader; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.util.List; +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.util.test.BaseTestSupport; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +public class AESPrivateKeyObfuscatorTest extends BaseTestSupport { + private static final Random RANDOMIZER = new Random(System.currentTimeMillis()); + + private final int keyLength; + + public AESPrivateKeyObfuscatorTest(int keyLength) { + this.keyLength = keyLength; + } + + @Parameters(name = "keyLength={0}") + public static List<Object[]> parameters() { + List<Integer> lengths = AESPrivateKeyObfuscator.getAvailableKeyLengths(); + assertFalse("No lengths available", GenericUtils.isEmpty(lengths)); + return parameterize(lengths); + } + + @Test + public void testAvailableKeyLengthExists() throws GeneralSecurityException { + assertEquals("Not a BYTE size multiple", 0, keyLength % Byte.SIZE); + + byte[] iv = new byte[keyLength / Byte.SIZE]; + synchronized (RANDOMIZER) { + RANDOMIZER.nextBytes(iv); + } + + Key key = new SecretKeySpec(iv, AESPrivateKeyObfuscator.CIPHER_NAME); + Cipher c = SecurityUtils.getCipher(AESPrivateKeyObfuscator.CIPHER_NAME); + c.init(Cipher.DECRYPT_MODE, key); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/common/util/SecurityUtilsTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/SecurityUtilsTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/SecurityUtilsTest.java index 2e44d83..1fa87ad 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/util/SecurityUtilsTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/util/SecurityUtilsTest.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.util; +import java.io.IOException; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyPair; @@ -33,12 +34,14 @@ import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.apache.sshd.common.cipher.BuiltinCiphers; import org.apache.sshd.common.cipher.ECCurves; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; import org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider; import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider; import org.apache.sshd.common.keyprovider.FileKeyPairProvider; @@ -62,14 +65,12 @@ public class SecurityUtilsTest extends BaseTestSupport { } @Test - public void testLoadEncryptedDESPrivateKey() { - Assume.assumeTrue("Bouncycastle not registered", SecurityUtils.isBouncyCastleRegistered()); + public void testLoadEncryptedDESPrivateKey() throws Exception { testLoadEncryptedRSAPrivateKey("DES-EDE3"); } @Test public void testLoadEncryptedAESPrivateKey() { - Assume.assumeTrue("Bouncycastle not registered", SecurityUtils.isBouncyCastleRegistered()); for (BuiltinCiphers c : new BuiltinCiphers[]{ BuiltinCiphers.aes128cbc, BuiltinCiphers.aes192cbc, BuiltinCiphers.aes256cbc }) { @@ -78,33 +79,34 @@ public class SecurityUtilsTest extends BaseTestSupport { continue; } - testLoadEncryptedRSAPrivateKey("AES-" + c.getKeySize()); + try { + testLoadEncryptedRSAPrivateKey("AES-" + c.getKeySize()); + } catch (Exception e) { + fail("Failed (" + e.getClass().getSimpleName() + " to load key for " + c.getName() + ": " + e.getMessage()); + } } } - private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm) { + private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm) throws IOException, GeneralSecurityException { return testLoadRSAPrivateKey(DEFAULT_PASSWORD.replace(' ', '-') + "-RSA-" + algorithm.toUpperCase() + "-key"); } @Test - public void testLoadUnencryptedRSAPrivateKey() { - Assume.assumeTrue("Bouncycastle not registered", SecurityUtils.isBouncyCastleRegistered()); + public void testLoadUnencryptedRSAPrivateKey() throws Exception { testLoadRSAPrivateKey(getClass().getSimpleName() + "-RSA-KeyPair"); } @Test - public void testLoadUnencryptedDSSPrivateKey() { - Assume.assumeTrue("Bouncycastle not registered", SecurityUtils.isBouncyCastleRegistered()); + public void testLoadUnencryptedDSSPrivateKey() throws Exception { testLoadDSSPrivateKey(getClass().getSimpleName() + "-DSA-KeyPair"); } - private KeyPair testLoadDSSPrivateKey(String name) { + private KeyPair testLoadDSSPrivateKey(String name) throws Exception { return testLoadPrivateKey(name, DSAPublicKey.class, DSAPrivateKey.class); } @Test - public void testLoadUnencryptedECPrivateKey() { - Assume.assumeTrue("Bouncycastle not registered", SecurityUtils.isBouncyCastleRegistered()); + public void testLoadUnencryptedECPrivateKey() throws Exception { Assume.assumeTrue("EC not supported", SecurityUtils.hasEcc()); for (ECCurves c : ECCurves.VALUES) { if (!c.isSupported()) { @@ -116,22 +118,33 @@ public class SecurityUtilsTest extends BaseTestSupport { } } - private KeyPair testLoadECPrivateKey(String name) { + private KeyPair testLoadECPrivateKey(String name) throws IOException, GeneralSecurityException { return testLoadPrivateKey(name, ECPublicKey.class, ECPrivateKey.class); } - private KeyPair testLoadRSAPrivateKey(String name) { + private KeyPair testLoadRSAPrivateKey(String name) throws IOException, GeneralSecurityException { return testLoadPrivateKey(name, RSAPublicKey.class, RSAPrivateKey.class); } - private KeyPair testLoadPrivateKey(String name, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) { + private KeyPair testLoadPrivateKey(String name, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) + throws IOException, GeneralSecurityException { Path folder = getClassResourcesFolder(TEST_SUBFOLDER); - KeyPair kpFile = testLoadPrivateKeyFile(folder.resolve(name), pubType, prvType); + Path file = folder.resolve(name); + KeyPair kpFile = testLoadPrivateKeyFile(file, pubType, prvType); + if (SecurityUtils.isBouncyCastleRegistered()) { + KeyPairResourceLoader bcLoader = SecurityUtils.getBouncycastleKeyPairResourceParser(); + Collection<KeyPair> kpList = bcLoader.loadKeyPairs(file, TEST_PASSWORD_PROVIDER); + assertEquals(name + ": Mismatched loaded BouncyCastle keys count", 1, GenericUtils.size(kpList)); + + KeyPair kpBC = kpList.iterator().next(); + assertTrue(name + ": Mismatched BouncyCastle vs. file values", KeyUtils.compareKeyPairs(kpFile, kpBC)); + } + Class<?> clazz = getClass(); Package pkg = clazz.getPackage(); KeyPair kpResource = testLoadPrivateKeyResource(pkg.getName().replace('.', '/') + "/" + name, pubType, prvType); - assertTrue("Mismatched key pairs values", KeyUtils.compareKeyPairs(kpFile, kpResource)); + assertTrue(name + ": Mismatched key file vs. resource values", KeyUtils.compareKeyPairs(kpFile, kpResource)); return kpResource; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1ClassTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1ClassTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1ClassTest.java new file mode 100644 index 0000000..c70d250 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1ClassTest.java @@ -0,0 +1,61 @@ +/* + * 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.util.io.der; + +import java.util.List; + +import org.apache.sshd.util.test.BaseTestSupport; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +public class ASN1ClassTest extends BaseTestSupport { + private final ASN1Class expected; + + public ASN1ClassTest(ASN1Class expected) { + this.expected = expected; + } + + @Parameters(name = "{0}") + public static List<Object[]> parameters() { + return parameterize(ASN1Class.VALUES); + } + + @Test + public void testFromName() { + String name = expected.name(); + for (int index = 1, count = name.length(); index <= count; index++) { + assertSame(name, expected, ASN1Class.fromName(name)); + name = shuffleCase(name); + } + } + + @Test // NOTE: this also tests "fromTypeValue" since "fromDERValue" invokes it + public void testFromDERValue() { + assertSame(expected, ASN1Class.fromDERValue((expected.getClassValue() << 6) & 0xFF)); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1TypeTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1TypeTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1TypeTest.java new file mode 100644 index 0000000..ce24b66 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/ASN1TypeTest.java @@ -0,0 +1,61 @@ +/* + * 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.util.io.der; + +import java.util.List; + +import org.apache.sshd.util.test.BaseTestSupport; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +public class ASN1TypeTest extends BaseTestSupport { + private final ASN1Type expected; + + public ASN1TypeTest(ASN1Type expected) { + this.expected = expected; + } + + @Parameters(name = "{0}") + public static List<Object[]> parameters() { + return parameterize(ASN1Type.VALUES); + } + + @Test + public void testFromName() { + String name = expected.name(); + for (int index = 1, count = name.length(); index <= count; index++) { + assertSame(name, expected, ASN1Type.fromName(name)); + name = shuffleCase(name); + } + } + + @Test + public void testFromTypeValue() { + assertSame(expected, ASN1Type.fromTypeValue(expected.getTypeValue())); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/DERParserTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/DERParserTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/DERParserTest.java new file mode 100644 index 0000000..edc6768 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/common/util/io/der/DERParserTest.java @@ -0,0 +1,58 @@ +/* + * 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.util.io.der; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StreamCorruptedException; + +import org.apache.sshd.util.test.BaseTestSupport; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class DERParserTest extends BaseTestSupport { + public DERParserTest() { + super(); + } + + @Test + public void testReadLengthConstraint() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + try (DERWriter w = new DERWriter(baos)) { + w.writeLength(DERParser.MAX_DER_VALUE_LENGTH + 1); + } + } finally { + baos.close(); + } + + try (DERParser parser = new DERParser(baos.toByteArray())) { + int len = parser.readLength(); + fail("Unexpected success: len=" + len); + } catch (StreamCorruptedException e) { + // expected ignored + } + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java index 7738669..0ff1dd6 100644 --- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java @@ -34,9 +34,7 @@ import org.apache.sshd.common.config.keys.IdentityUtils; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.util.test.BaseTestSupport; -import org.junit.Assume; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; @@ -52,8 +50,6 @@ public class ServerIdentityTest extends BaseTestSupport { @Test public void testLoadServerIdentities() throws Exception { - Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered()); - Path resFolder = getClassResourcesFolder(TEST_SUBFOLDER, getClass()); Collection<Path> paths = new ArrayList<>(BuiltinIdentities.VALUES.size()); LinkOption[] options = IoUtils.getLinkOptions(false);
