Repository: mina-sshd Updated Branches: refs/heads/master 41857c7e8 -> 168adf20a
[SSHD-718] Add support for reading Putty key files Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/168adf20 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/168adf20 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/168adf20 Branch: refs/heads/master Commit: 168adf20ad8ea277130d0fc7f673f7b2ddd61dfb Parents: 41857c7 Author: Lyor Goldstein <lyor.goldst...@gmail.com> Authored: Fri Dec 9 19:17:05 2016 +0200 Committer: Lyor Goldstein <lyor.goldst...@gmail.com> Committed: Fri Dec 9 19:17:05 2016 +0200 ---------------------------------------------------------------------- .../loader/putty/AbstractPuttyKeyDecoder.java | 220 +++++++++++++++++++ .../keys/loader/putty/DSSPuttyKeyDecoder.java | 63 ++++++ .../putty/PuttyKeyPairResourceParser.java | 199 +++++++++++++++++ .../keys/loader/putty/PuttyKeyReader.java | 58 +++++ .../config/keys/loader/putty/PuttyKeyUtils.java | 58 +++++ .../keys/loader/putty/RSAPuttyKeyDecoder.java | 71 ++++++ .../keys/loader/putty/PuttyKeyUtilsTest.java | 126 +++++++++++ .../putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk | 17 ++ .../putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk | 18 ++ ...t-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk | 17 ++ ...t-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk | 18 ++ .../keys/loader/KeyPairResourceParser.java | 14 +- .../loader/pem/DSSPEMResourceKeyPairParser.java | 25 +-- .../loader/pem/RSAPEMResourceKeyPairParser.java | 27 ++- 14 files changed, 903 insertions(+), 28 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java new file mode 100644 index 0000000..5b29618 --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.putty; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Base64; +import java.util.Base64.Decoder; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public abstract class AbstractPuttyKeyDecoder + extends AbstractLoggingBean + implements PuttyKeyPairResourceParser { + public static final String ENCRYPTION_HEADER = "Encryption"; + + private final String keyType; + protected AbstractPuttyKeyDecoder(String keyType) { + this.keyType = keyType; + } + + @Override + public String getKeyType() { + return keyType; + } + + @Override + public boolean canExtractKeyPairs(String resourceKey, List<String> lines) + throws IOException, GeneralSecurityException { + if (!PuttyKeyPairResourceParser.super.canExtractKeyPairs(resourceKey, lines)) { + return false; + } + + for (String l : lines) { + l = GenericUtils.trimToEmpty(l); + if (!l.startsWith(KEY_FILE_HEADER_PREFIX)) { + continue; + } + + int pos = l.indexOf(':'); + if ((pos <= 0) || (pos >= (l.length() - 1))) { + return false; + } + + String typeValue = l.substring(pos + 1).trim(); + return Objects.equals(getKeyType(), typeValue); + } + + return false; + } + + @Override + public Collection<KeyPair> loadKeyPairs( + String resourceKey, FilePasswordProvider passwordProvider, List<String> lines) + throws IOException, GeneralSecurityException { + List<String> pubLines = Collections.emptyList(); + List<String> prvLines = Collections.emptyList(); + String prvEncryption = null; + for (int index = 0, numLines = lines.size(); index < numLines; index++) { + String l = lines.get(index); + l = GenericUtils.trimToEmpty(l); + int pos = l.indexOf(':'); + if ((pos <= 0) || (pos >= (l.length() - 1))) { + continue; + } + + String hdrName = l.substring(0, pos).trim(); + String hdrValue = l.substring(pos + 1).trim(); + switch (hdrName) { + case ENCRYPTION_HEADER: + if (prvEncryption != null) { + throw new StreamCorruptedException("Duplicate " + hdrName + " in" + resourceKey); + } + prvEncryption = hdrValue; + break; + case PUBLIC_LINES_HEADER: + pubLines = extractDataLines(resourceKey, lines, index + 1, hdrName, hdrValue, pubLines); + index += pubLines.size(); + break; + case PRIVATE_LINES_HEADER: + prvLines = extractDataLines(resourceKey, lines, index + 1, hdrName, hdrValue, prvLines); + index += prvLines.size(); + break; + default: // ignored + } + } + + return loadKeyPairs(resourceKey, pubLines, prvLines, prvEncryption, passwordProvider); + } + + public static List<String> extractDataLines( + String resourceKey, List<String> lines, int startIndex, String hdrName, String hdrValue, List<String> curLines) + throws IOException { + if (GenericUtils.size(curLines) > 0) { + throw new StreamCorruptedException("Duplicate " + hdrName + " in " + resourceKey); + } + + int numLines; + try { + numLines = Integer.parseInt(hdrValue); + } catch (NumberFormatException e) { + throw new StreamCorruptedException("Bad " + hdrName + " value (" + hdrValue + ") in " + resourceKey); + } + + int endIndex = startIndex + numLines; + int totalLines = lines.size(); + if (endIndex > totalLines) { + throw new StreamCorruptedException("Excessive " + hdrName + " value (" + hdrValue + ") in " + resourceKey); + } + + return lines.subList(startIndex, endIndex); + } + + public Collection<KeyPair> loadKeyPairs( + String resourceKey, List<String> pubLines, List<String> prvLines, String prvEncryption, FilePasswordProvider passwordProvider) + throws IOException, GeneralSecurityException { + return loadKeyPairs(resourceKey, + KeyPairResourceParser.joinDataLines(pubLines), KeyPairResourceParser.joinDataLines(prvLines), + prvEncryption, passwordProvider); + } + + public Collection<KeyPair> loadKeyPairs( + String resourceKey, String pubData, String prvData, String prvEncryption, FilePasswordProvider passwordProvider) + throws IOException, GeneralSecurityException { + Decoder b64Decoder = Base64.getDecoder(); + byte[] pubBytes = b64Decoder.decode(pubData); + byte[] prvBytes = b64Decoder.decode(prvData); + String password = null; + if ((GenericUtils.length(prvEncryption) > 0) + && (!NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption))) { + password = passwordProvider.getPassword(resourceKey); + } + + if (GenericUtils.isEmpty(prvEncryption) + || NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption) + || GenericUtils.isEmpty(password)) { + return loadKeyPairs(resourceKey, pubBytes, prvBytes); + } + + // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc" + int pos = prvEncryption.indexOf('-'); + if (pos <= 0) { + throw new StreamCorruptedException("Missing private key encryption mode in " + prvEncryption); + } + + String mode = prvEncryption.substring(pos + 1).toUpperCase(); + String algName = null; + int numBits = 0; + for (int index = 0; index < pos; index++) { + char ch = prvEncryption.charAt(index); + if ((ch >= '0') && (ch <= '9')) { + algName = prvEncryption.substring(0, index).toUpperCase(); + numBits = Integer.parseInt(prvEncryption.substring(index, pos)); + break; + } + } + + if (GenericUtils.isEmpty(algName) || (numBits <= 0)) { + throw new StreamCorruptedException("Missing private key encryption algorithm details in " + prvEncryption); + } + + prvBytes = PuttyKeyPairResourceParser.decodePrivateKeyBytes(prvBytes, algName, numBits, mode, password); + return loadKeyPairs(resourceKey, pubBytes, prvBytes); + } + + public Collection<KeyPair> loadKeyPairs(String resourceKey, byte[] pubData, byte[] prvData) + throws IOException, GeneralSecurityException { + ValidateUtils.checkNotNullAndNotEmpty(pubData, "No public key data in %s", resourceKey); + ValidateUtils.checkNotNullAndNotEmpty(prvData, "No private key data in %s", resourceKey); + try (InputStream pubStream = new ByteArrayInputStream(pubData); + InputStream prvStream = new ByteArrayInputStream(prvData)) { + return loadKeyPairs(resourceKey, pubStream, prvStream); + } + } + + public Collection<KeyPair> loadKeyPairs(String resourceKey, InputStream pubData, InputStream prvData) + throws IOException, GeneralSecurityException { + try (PuttyKeyReader pubReader = + new PuttyKeyReader(ValidateUtils.checkNotNull(pubData, "No public key data in %s", resourceKey)); + PuttyKeyReader prvReader = + new PuttyKeyReader(ValidateUtils.checkNotNull(prvData, "No private key data in %s", resourceKey))) { + return loadKeyPairs(resourceKey, pubReader, prvReader); + } + } + + public abstract Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + throws IOException, GeneralSecurityException; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java new file mode 100644 index 0000000..c03a9d5 --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.putty; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.util.Collection; +import java.util.Collections; + +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder { + public static final DSSPuttyKeyDecoder INSTANCE = new DSSPuttyKeyDecoder(); + + public DSSPuttyKeyDecoder() { + super(KeyPairProvider.SSH_DSS); + } + + @Override + public Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + throws IOException, GeneralSecurityException { + pubReader.skip(); // skip version + + BigInteger p = pubReader.readInt(); + BigInteger q = pubReader.readInt(); + BigInteger g = pubReader.readInt(); + BigInteger y = pubReader.readInt(); + BigInteger x = prvReader.readInt(); + KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM); + PublicKey pubKey = kf.generatePublic(new DSAPublicKeySpec(y, p, q, g)); + PrivateKey prvKey = kf.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)); + return Collections.singletonList(new KeyPair(pubKey, prvKey)); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java new file mode 100644 index 0000000..774624b --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java @@ -0,0 +1,199 @@ +/* + * 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.putty; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; +import org.apache.sshd.common.digest.BuiltinDigests; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.SecurityUtils; + +//CHECKSTYLE:OFF +/** + * Loads a {@link KeyPair} from PuTTY's ".ppk" file. + * <P>Note(s):</P> + * <UL> + * <P><LI> + * The file appears to be a text file but it doesn't have a fixed encoding like UTF-8. + * We use UTF-8 as the default encoding - since the important part is all ASCII, + * this shouldn't really hurt the interpretation of the key. + * </LI></P> + * + * <P><LI> + * Based on code from <A HREF="https://github.com/kohsuke/trilead-putty-extension">Kohsuke's Trilead Putty Extension</A> + * </LI></P> + * + * <P><LI> + * Encrypted keys requires AES-256-CBC support, which is available only if the + * <A HREF="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html"> + * Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files</A> are installed + * </LI></P> + * </UL> + * + * <P>Sample PuTTY file format</P> + * <PRE> + * PuTTY-User-Key-File-2: ssh-rsa + * Encryption: none + * Comment: rsa-key-20080514 + * Public-Lines: 4 + * AAAAB3NzaC1yc2EAAAABJQAAAIEAiPVUpONjGeVrwgRPOqy3Ym6kF/f8bltnmjA2 + * BMdAtaOpiD8A2ooqtLS5zWYuc0xkW0ogoKvORN+RF4JI+uNUlkxWxnzJM9JLpnvA + * HrMoVFaQ0cgDMIHtE1Ob1cGAhlNInPCRnGNJpBNcJ/OJye3yt7WqHP4SPCCLb6nL + * nmBUrLM= + * Private-Lines: 8 + * AAAAgGtYgJzpktzyFjBIkSAmgeVdozVhgKmF6WsDMUID9HKwtU8cn83h6h7ug8qA + * hUWcvVxO201/vViTjWVz9ALph3uMnpJiuQaaNYIGztGJBRsBwmQW9738pUXcsUXZ + * 79KJP01oHn6Wkrgk26DIOsz04QOBI6C8RumBO4+F1WdfueM9AAAAQQDmA4hcK8Bx + * nVtEpcF310mKD3nsbJqARdw5NV9kCxPnEsmy7Sy1L4Ob/nTIrynbc3MA9HQVJkUz + * 7V0va5Pjm/T7AAAAQQCYbnG0UEekwk0LG1Hkxh1OrKMxCw2KWMN8ac3L0LVBg/Tk + * 8EnB2oT45GGeJaw7KzdoOMFZz0iXLsVLNUjNn2mpAAAAQQCN6SEfWqiNzyc/w5n/ + * lFVDHExfVUJp0wXv+kzZzylnw4fs00lC3k4PZDSsb+jYCMesnfJjhDgkUA0XPyo8 + * Emdk + * Private-MAC: 50c45751d18d74c00fca395deb7b7695e3ed6f77 + * </PRE> + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +//CHECKSTYLE:ON +public interface PuttyKeyPairResourceParser extends KeyPairResourceParser { + String KEY_FILE_HEADER_PREFIX = "PuTTY-User-Key-File"; + String PUBLIC_LINES_HEADER = "Public-Lines"; + String PRIVATE_LINES_HEADER = "Private-Lines"; + String PPK_FILE_SUFFIX = ".ppk"; + + List<String> KNOWN_HEADERS = + Collections.unmodifiableList( + Arrays.asList( + KEY_FILE_HEADER_PREFIX, + PUBLIC_LINES_HEADER, + PRIVATE_LINES_HEADER)); + + /** + * Value (case insensitive) used to denote that private key is not encrypted + */ + String NO_PRIVATE_KEY_ENCRYPTION_VALUE = "none"; + + /** + * @return Type of key being parsed by the resource parser + */ + String getKeyType(); + + @Override + default boolean canExtractKeyPairs(String resourceKey, List<String> lines) + throws IOException, GeneralSecurityException { + if (GenericUtils.isEmpty(lines)) { + return false; + } + + for (String l : lines) { + l = GenericUtils.trimToEmpty(l); + for (String hdr : KNOWN_HEADERS) { + if (l.startsWith(hdr)) { + return true; + } + } + } + + return false; + } + + static byte[] decodePrivateKeyBytes(byte[] prvBytes, String algName, int numBits, String algMode, String password) + throws GeneralSecurityException { + Objects.requireNonNull(prvBytes, "No encrypted key bytes"); + ValidateUtils.checkNotNullAndNotEmpty(algName, "No encryption algorithm", GenericUtils.EMPTY_OBJECT_ARRAY); + ValidateUtils.checkTrue(numBits > 0, "Invalid encryption key size: %d", numBits); + ValidateUtils.checkNotNullAndNotEmpty(algMode, "No encryption mode", GenericUtils.EMPTY_OBJECT_ARRAY); + ValidateUtils.checkNotNullAndNotEmpty(password, "No encryption password", GenericUtils.EMPTY_OBJECT_ARRAY); + + if (!"AES".equalsIgnoreCase(algName)) { + throw new NoSuchAlgorithmException("decodePrivateKeyBytes(" + algName + "-" + numBits + "-" + algMode + ") N/A"); + } + + return decodePrivateKeyBytes(prvBytes, algName, algMode, numBits, new byte[16], toEncryptionKey(password)); + } + + static byte[] decodePrivateKeyBytes( + byte[] encBytes, String cipherName, String cipherMode, int numBits, byte[] initVector, byte[] keyValue) + throws GeneralSecurityException { + String xform = cipherName + "/" + cipherMode + "/NoPadding"; + int maxAllowedBits = Cipher.getMaxAllowedKeyLength(xform); + // see http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml + if (numBits > maxAllowedBits) { + throw new InvalidKeySpecException("decodePrivateKeyBytes(" + xform + ")" + + " required key length (" + numBits + ") exceeds max. available: " + maxAllowedBits); + } + + SecretKeySpec skeySpec = new SecretKeySpec(keyValue, cipherName); + IvParameterSpec ivspec = new IvParameterSpec(initVector); + Cipher cipher = SecurityUtils.getCipher(xform); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec); + + return cipher.doFinal(encBytes); + } + + /** + * Converts a pass-phrase into a key, by following the convention that PuTTY uses. + * Used to decrypt the private key when it's encrypted. + * @param passphrase the Password to be used as seed for the key - ignored + * if {@code null}/empty + * @return The encryption key bytes - {@code null} if no pass-phrase + * @throws GeneralSecurityException If cannot retrieve SHA-1 digest + * @see <A HREF="http://security.stackexchange.com/questions/71341/how-does-putty-derive-the-encryption-key-in-its-ppk-format"> + * How does Putty derive the encryption key in its .ppk format ?</A> + */ + static byte[] toEncryptionKey(String passphrase) throws GeneralSecurityException { + if (GenericUtils.isEmpty(passphrase)) { + return null; + } + + MessageDigest hash = SecurityUtils.getMessageDigest(BuiltinDigests.sha1.getAlgorithm()); + byte[] stateValue = {0, 0, 0, 0}; + byte[] passBytes = passphrase.getBytes(StandardCharsets.UTF_8); + byte[] keyValue = new byte[32]; + for (int i = 0, remLen = keyValue.length; i < 2; i++) { + hash.reset(); // just making sure + + stateValue[3] = (byte) i; + hash.update(stateValue); + hash.update(passBytes); + + byte[] digest = hash.digest(); + System.arraycopy(digest, 0, keyValue, i * 20, Math.min(20, remLen)); + remLen -= 20; + } + + return keyValue; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java new file mode 100644 index 0000000..ae00ef2 --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.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.config.keys.loader.putty; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +/** + * Helper class for {@code Putty} key files decoders + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class PuttyKeyReader implements Closeable { + private final DataInputStream di; + + public PuttyKeyReader(InputStream s) { + di = new DataInputStream(s); + } + + public void skip() throws IOException { + di.skipBytes(di.readInt()); + } + + private byte[] read() throws IOException { + int len = di.readInt(); + byte[] r = new byte[len]; + di.readFully(r); + return r; + } + + public BigInteger readInt() throws IOException { + return new BigInteger(read()); + } + + @Override + public void close() throws IOException { + di.close(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java new file mode 100644 index 0000000..61ad233 --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.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.config.keys.loader.putty; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public final class PuttyKeyUtils { + public static final List<PuttyKeyPairResourceParser> DEFAULT_PARSERS = + Collections.unmodifiableList( + Arrays.asList( + RSAPuttyKeyDecoder.INSTANCE, + DSSPuttyKeyDecoder.INSTANCE)); + public static final SortedMap<String, PuttyKeyPairResourceParser> BY_KEY_TYPE = + Collections.unmodifiableSortedMap( + new TreeMap<String, PuttyKeyPairResourceParser>(String.CASE_INSENSITIVE_ORDER) { + // Not serializing it + private static final long serialVersionUID = 1L; + + { + for (PuttyKeyPairResourceParser p : DEFAULT_PARSERS) { + put(p.getKeyType(), p); + } + } + }); + + public static final KeyPairResourceParser DEFAULT_INSTANCE = + KeyPairResourceParser.aggregate(DEFAULT_PARSERS); + + private PuttyKeyUtils() { + throw new UnsupportedOperationException("No instance"); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java new file mode 100644 index 0000000..b4f9687 --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java @@ -0,0 +1,71 @@ +/* + * 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.putty; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Collection; +import java.util.Collections; + +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder { + public static final RSAPuttyKeyDecoder INSTANCE = new RSAPuttyKeyDecoder(); + + public RSAPuttyKeyDecoder() { + super(KeyPairProvider.SSH_RSA); + } + + @Override + public Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + throws IOException, GeneralSecurityException { + pubReader.skip(); // skip version + + KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM); + BigInteger publicExp = pubReader.readInt(); + BigInteger modulus = pubReader.readInt(); + PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, publicExp)); + + BigInteger privateExp = prvReader.readInt(); + BigInteger primeP = prvReader.readInt(); + BigInteger primeQ = prvReader.readInt(); + BigInteger crtCoef = prvReader.readInt(); + BigInteger primeExponentP = privateExp.mod(primeP.subtract(BigInteger.ONE)); + BigInteger primeExponentQ = privateExp.mod(primeQ.subtract(BigInteger.ONE)); + RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec( + modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef); + PrivateKey prvKey = kf.generatePrivate(prvSpec); + return Collections.singletonList(new KeyPair(pubKey, prvKey)); + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java new file mode 100644 index 0000000..aea094f --- /dev/null +++ b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.putty; + +import java.io.IOException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collection; +import java.util.List; + +import org.apache.sshd.common.cipher.BuiltinCiphers; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.util.test.BaseTestSupport; +import org.junit.Assume; +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:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +public class PuttyKeyUtilsTest extends BaseTestSupport { + public static final String PASSWORD = "super secret passphrase"; + + private final String keyType; + private final String regularFile; + private final String encryptedFile; + private final PuttyKeyPairResourceParser parser; + + public PuttyKeyUtilsTest(String keyType) { + this.keyType = keyType; + this.parser = PuttyKeyUtils.BY_KEY_TYPE.get(keyType); + this.regularFile = getClass().getSimpleName() + + "-" + keyType + "-" + KeyPair.class.getSimpleName() + + PuttyKeyPairResourceParser.PPK_FILE_SUFFIX; + this.encryptedFile = PASSWORD.replace(' ', '-') + "-AES-256-CBC" + + "-" + keyType + "-" + KeyPair.class.getSimpleName() + + PuttyKeyPairResourceParser.PPK_FILE_SUFFIX; + } + + @Parameters(name = "{0}") + public static List<Object[]> parameters() { + return parameterize(PuttyKeyUtils.BY_KEY_TYPE.keySet()); + } + + @Test + public void testCanDecodePuttyKeyFile() throws IOException, GeneralSecurityException { + for (String resource : new String[]{regularFile, encryptedFile}) { + URL url = getClass().getResource(resource); + assertNotNull("Missing test resource: " + resource, url); + + List<String> lines = IoUtils.readAllLines(url); + assertTrue(resource + " - can extract key pair", parser.canExtractKeyPairs(resource, lines)); + + for (PuttyKeyPairResourceParser other : PuttyKeyUtils.BY_KEY_TYPE.values()) { + if (parser == other) { + continue; + } + + assertFalse(other.getKeyType() + "/" + resource + " - unexpected extraction capability", + other.canExtractKeyPairs(resource, lines)); + } + } + } + + @Test + public void testDecodePuttyKeyFile() throws IOException, GeneralSecurityException { + URL url = getClass().getResource(regularFile); + assertNotNull("Missing test resource: " + regularFile, url); + + Collection<KeyPair> keys = parser.loadKeyPairs(url, null); + assertEquals("Mismatched loaded keys count from " + regularFile, 1, GenericUtils.size(keys)); + assertLoadedKeyPair(regularFile, keys.iterator().next()); + } + + @Test + public void testDecodeEncryptedPuttyKeyFile() throws IOException, GeneralSecurityException { + Assume.assumeTrue(BuiltinCiphers.aes256cbc.getTransformation() + " N/A", BuiltinCiphers.aes256cbc.isSupported()); + URL url = getClass().getResource(encryptedFile); + assertNotNull("Missing test resource: " + encryptedFile, url); + + Collection<KeyPair> keys = parser.loadKeyPairs(url, r -> PASSWORD); + assertEquals("Mismatched loaded keys count from " + encryptedFile, 1, GenericUtils.size(keys)); + + assertLoadedKeyPair(encryptedFile, keys.iterator().next()); + } + + private void assertLoadedKeyPair(String prefix, KeyPair kp) { + assertNotNull(prefix + ": no key pair loaded", kp); + + PublicKey pubKey = kp.getPublic(); + assertNotNull(prefix + ": no public key loaded", pubKey); + assertEquals(prefix + ": mismatched public key type", keyType, KeyUtils.getKeyType(pubKey)); + + PrivateKey prvKey = kp.getPrivate(); + assertNotNull(prefix + ": no private key loaded", prvKey); + assertEquals(prefix + ": mismatched private key type", keyType, KeyUtils.getKeyType(prvKey)); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk new file mode 100644 index 0000000..59a8fac --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk @@ -0,0 +1,17 @@ +PuTTY-User-Key-File-2: ssh-dss +Encryption: none +Comment: dsa-key-20130709 +Public-Lines: 10 +AAAAB3NzaC1kc3MAAACBAMg/IxsG5BxnF5gM7IKqqR0rftxZC+n5GlbO+J4H+iIb +/KR8NBehkxG3CrBZMF96M2K1sEGYLob+3k4r71oWaPul8n5rt9kpd+JSq4iD2ygO +yg6Kd1/YDBHoxneizy6I/bGsLwhAAKWcRNrXmYVKGzhrhvZWN12AJDq2mGdj3szL +AAAAFQD7a2MltdUSF7FU3//SpW4WGjZbeQAAAIBf0nNsfKQL/TEMo7IpTrEMg5V0 +RnSigCX0+yUERS42GW/ZeCZBJw7oL2XZbuBtu63vMjDgVpnb92BdrcPgjJ7EFW6D +lcyeuywStmg1ygXmDR2AQCxv0eX2CQgrdUczmRa155SDVUTvTQlO1IyKx0vwKAh1 +H7E3yJUfkTAJstbGYQAAAIEAtv+cdRfNevYFkp55jVqazc8zRLvfb64jzgc5oSJV +c64kFs4yx+abYpGX9WxNxDlG6g2WiY8voDBB0YnUJsn0kVRjBKX9OceROxrfT4K4 +dVbQZsdt+SLaXWL4lGJFrFZL3LZqvySvq6xfhJfakQDDivW4hUOhFPXPHrE5/Ia3 +T7A= +Private-Lines: 1 +AAAAFQCE6flGnmVCAbzo9YsbdJWBnxMnBA== +Private-MAC: 6ace0a5e5bf23649c1375e91dcd71d1def6c6aa1 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk new file mode 100644 index 0000000..da2868e --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk @@ -0,0 +1,18 @@ +PuTTY-User-Key-File-2: ssh-rsa +Encryption: none +Comment: rsa-key-20130709 +Public-Lines: 4 +AAAAB3NzaC1yc2EAAAABJQAAAIBLSj7+SllQdWjkD8EN4sA/fUv/jhc+ssGmCYx3 +uoiPMxjKH3xUPWu4zxJzhdlgFy4gchzjU51fDS4Oel6xGoWbGKGe4ZLQdE/t8N8l +jAfOm/5lGp5tFhHs9UHoSm/h3RsErWNjKPjTGIlID35IcOXVhfhp9fX0RU6y/ZBI +PhM20w== +Private-Lines: 8 +AAAAgBx89T2fl2qNSkh0qemUEWQhkmCyTfwMSUW+bIBUawXATpGrDHLmztBOWgIy +pUb0A52S9iyAgLwugCEnYhl/qCxvoARH7ZyTdYAL4KjJDySxVuqeo/ZhLscYcMAz +MOyn8g5cR4dRgEwJ1/pRuK8r4+Z96zJG4NlxlHsUjHuj7t1dAAAAQQCTrj48XKIX +M3dxWLSsSXbUCOpmAOTviuz9LD0H1ik7a6ebr0P6GTl9z7iscBgzdjBIHMFcdvar +ophUJ5iRanCvAAAAQQCCg1VU1H5FHMipRvvw8b/zRqDsx6GTZs03ffhyKrTl1Dcd +0oKy5/U3kQwdXPOSlVZeyX8nUVE2o7DOh7INsX0dAAAAQHkPjxivrN0SQuVAx+tK +uRJ8vGL7sBOZ9gdTS25T3BVEkhRt37aDcshrodzDCzd515cwhmbLSsOsgyxcTwcX +7SA= +Private-MAC: 2416438f1a7ebdd33d519f6102d843b5f2c565d4 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk new file mode 100644 index 0000000..9da31f1 --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk @@ -0,0 +1,17 @@ +PuTTY-User-Key-File-2: ssh-dss +Encryption: aes256-cbc +Comment: dsa-key-20130909 +Public-Lines: 10 +AAAAB3NzaC1kc3MAAACBALVdprWfZ7+FiITCILnDkeMZ2ntkV2WjW5RcyiQvJvBO +jCNiVtK87xATEOfBb20YvNZ/CibBjGS1TL5TBqRV5XleucPHMJZ5rXdJ2FH5oZnL +kna3Et+L1/O/GQMmp2vfSFrO3n3+mI1Jozx3FoQO8jr1zIerJ5Mc4LKqsIQB9hvR +AAAAFQD+z1y1/4ll4ax3rri8mkYgGDhqIQAAAIBVU4VJ7V7GoEQJ5WBMbpDEcLIZ +KUgSHsJMQzWnLOi/DcsPjVMDX6FWGPLtrjd7fgInlPMCC/SPAhXdaXMvHZSkvBHV +DfNjpsDgsxBnK1FKqRGtD49rETFGDl92EOsyBhv+9ymdOPX6R0hCqS+ulZheQPXI +iHXdIvQK2Ev5Dy3xNgAAAIA8qhumHZcKss+Puuw+mY5J5Qt7Omv16MuDsYiVqrBq +1V2C9gutx3Tu+n5QYi0xPlkkP/knMtkUZS+Wt3Dr8zPcEzNBc/Tm2EdYp11jZNx4 +4PM4ing+aCU5oGcg/7RS5CrY5Fn/rvgHqK22XiC8/U55iti44bWKvI6HCejExeZX +iA== +Private-Lines: 1 +64IdcIX48CNGjhcxsVN0vSNpT7S72e3FVdQ3t4ENAvI= +Private-MAC: 8e62b44b5c080965e361936520be4feaa50285b1 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk new file mode 100644 index 0000000..2a11d04 --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk @@ -0,0 +1,18 @@ +PuTTY-User-Key-File-2: ssh-rsa +Encryption: aes256-cbc +Comment: rsa-key-20130909 +Public-Lines: 4 +AAAAB3NzaC1yc2EAAAABJQAAAIEAhY5eZJrX0a3+rtEZCq0nu2zvAHp16nk93jhi +p7c9tTDlGm9QEAgqzmuilEeUQ4BssxAvhCFEo/7Qbg4M7PwcA5cFkjXE4gj0YDJM +ay7l2mb5aIoS/hACgNz54p/w/UgfQC1Vygt6QtvXXAW8Lh/YCN4Zw4ViROUhoYuy +3K5SBYs= +Private-Lines: 8 +mqcGPnrv9d1tYkJZSGaCy5REslPZ2xh8m7qAbN+bD1m7iQ77pLxlKyzs82rbRaC9 +KSnKwsbFl7o92NT+9yYKJ7ehXyWyrUXkn9KcPk7MzNVwMuWVDXwvHodGLCyVCLYq +PNipvg2USHvnCjnnvtMysBRNJiHTMOaf/gSZLyaEuznYo3FEClMPzggY9b2nrxnV +O1ttk1FJatkRflwFjn3A/R/GpowmBnkDyCkVlTvR+uBAg8iIy1Vzj5zIV9zmzfgx +DxPot+Y81y+Xe3ohVh2s1FVvLw+KQbYbCQam5j0V/dTQ+oVWjCJBlibD3aVTGK0M +Jswz8wPwXFo5N0yX/6ZTrshbvTzoO1bg0+HUu581ZSAeqttk9C1RLmWFS8YDm0Hn +GhDXrjuAvKJ3cjeVJsumgVw45NYGARuzV24TlHUtU+eze8Y/0NsPJXoCfVoYjTjb +fjlMh9rYbRdyNHXwYTzwbw== +Private-MAC: f4c50b3da0b73c34e8989411fc48c884c09e20a0 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java index 6c1f508..e3336ad 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java @@ -68,7 +68,8 @@ public interface KeyPairResourceParser extends KeyPairResourceLoader { * @throws GeneralSecurityException If failed to extract information regarding * the possibility to extract the key pairs */ - boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws IOException, GeneralSecurityException; + boolean canExtractKeyPairs(String resourceKey, List<String> lines) + throws IOException, GeneralSecurityException; /** * Converts the lines assumed to contain BASE-64 encoded data into @@ -77,14 +78,19 @@ public interface KeyPairResourceParser extends KeyPairResourceLoader { * @param lines The data lines - empty lines and spaces are automatically * deleted <U>before</U> BASE-64 decoding takes place. * @return The decoded data bytes + * @see #joinDataLines(Collection) */ static byte[] extractDataBytes(Collection<String> lines) { + String data = joinDataLines(lines); + Base64.Decoder decoder = Base64.getDecoder(); + return decoder.decode(data); + } + + static String joinDataLines(Collection<String> lines) { String data = GenericUtils.join(lines, ' '); data = data.replaceAll("\\s", ""); data = data.trim(); - - Base64.Decoder decoder = Base64.getDecoder(); - return decoder.decode(data); + return data; } static boolean containsMarkerLine(List<String> lines, String marker) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java index 1e43f40..0c357af 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java @@ -26,9 +26,10 @@ import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -69,11 +70,7 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse public Collection<KeyPair> extractKeyPairs( String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream) throws IOException, GeneralSecurityException { - DSAPrivateKeySpec keySpec = decodeDSSPrivateKeySpec(stream, false); - KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM); - DSAPrivateKey prvKey = (DSAPrivateKey) kf.generatePrivate(keySpec); - DSAPublicKey pubKey = KeyUtils.recoverDSAPublicKey(prvKey); - KeyPair kp = new KeyPair(pubKey, prvKey); + KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false); return Collections.singletonList(kp); } @@ -89,13 +86,16 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse * x INTEGER * } * </code></pre> + * @param kf The {@link KeyFactory} To use to generate the keys * @param s The {@link InputStream} containing the encoded bytes * @param okToClose <code>true</code> if the method may close the input * stream regardless of success or failure - * @return The recovered {@link DSAPrivateKeySpec} + * @return The recovered {@link KeyPair} * @throws IOException If failed to read or decode the bytes + * @throws GeneralSecurityException If failed to generate the keys */ - public static final DSAPrivateKeySpec decodeDSSPrivateKeySpec(InputStream s, boolean okToClose) throws IOException { + public static KeyPair decodeDSSKeyPair(KeyFactory kf, InputStream s, boolean okToClose) + throws IOException, GeneralSecurityException { ASN1Object sequence; try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) { sequence = parser.readObject(); @@ -116,12 +116,11 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse BigInteger p = parser.readObject().asInteger(); BigInteger q = parser.readObject().asInteger(); BigInteger g = parser.readObject().asInteger(); - // don't need it, but have to read it to get to x - @SuppressWarnings("unused") BigInteger y = parser.readObject().asInteger(); BigInteger x = parser.readObject().asInteger(); - - return new DSAPrivateKeySpec(x, p, q, g); + PublicKey pubKey = kf.generatePublic(new DSAPublicKeySpec(y, p, q, g)); + PrivateKey prvKey = kf.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)); + return new KeyPair(pubKey, prvKey); } } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java index 0aea567..d760aaf 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java @@ -26,9 +26,11 @@ import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -69,11 +71,7 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse public Collection<KeyPair> extractKeyPairs( String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream) throws IOException, GeneralSecurityException { - RSAPrivateCrtKeySpec prvSpec = decodeRSAPrivateKeySpec(stream, false); - KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM); - RSAPrivateKey prvKey = (RSAPrivateKey) kf.generatePrivate(prvSpec); - RSAPublicKey pubKey = KeyUtils.recoverRSAPublicKey(prvSpec.getPrimeP(), prvSpec.getPrimeQ(), prvSpec.getPublicExponent()); - KeyPair kp = new KeyPair(pubKey, prvKey); + KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false); return Collections.singletonList(kp); } @@ -93,13 +91,16 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse * otherPrimeInfos OtherPrimeInfos OPTIONAL * } * </code></pre> + * @param kf The {@link KeyFactory} To use to generate the keys * @param s The {@link InputStream} containing the encoded bytes * @param okToClose <code>true</code> if the method may close the input * stream regardless of success or failure - * @return The recovered {@link RSAPrivateCrtKeySpec} + * @return The recovered {@link KeyPair} * @throws IOException If failed to read or decode the bytes + * @throws GeneralSecurityException If failed to generate the keys */ - public static RSAPrivateCrtKeySpec decodeRSAPrivateKeySpec(InputStream s, boolean okToClose) throws IOException { + public static KeyPair decodeRSAKeyPair(KeyFactory kf, InputStream s, boolean okToClose) + throws IOException, GeneralSecurityException { ASN1Object sequence; try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) { sequence = parser.readObject(); @@ -124,14 +125,18 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse BigInteger modulus = parser.readObject().asInteger(); BigInteger publicExp = parser.readObject().asInteger(); + PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, publicExp)); + BigInteger privateExp = parser.readObject().asInteger(); BigInteger primeP = parser.readObject().asInteger(); BigInteger primeQ = parser.readObject().asInteger(); BigInteger primeExponentP = parser.readObject().asInteger(); BigInteger primeExponentQ = parser.readObject().asInteger(); BigInteger crtCoef = parser.readObject().asInteger(); - - return new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef); + RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec( + modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef); + PrivateKey prvKey = kf.generatePrivate(prvSpec); + return new KeyPair(pubKey, prvKey); } } }