diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100755 index d149b460..00000000 --- a/GNUmakefile +++ /dev/null @@ -1,46 +0,0 @@ -Default: all - -TOP=$(realpath .) -include $(TOP)/../DeveloperTools/install/common.mk - -RSRC=rsrc -LIBDIR=$(RSRC)/lib -LIB=$(subst $(SPACE),$(CLN),$(filter %.jar %.zip, $(wildcard $(LIBDIR)/*))) -BUILD=build -SRC=src/main/java -DOCS=docs -CLASSPATH="$(CLASSLIB)$(CLN)$(LIB)$(CLN)$(SRC)" -CWD=$(shell pwd) - -include classes.mk - -CLASS_FILES:=$(foreach class, $(CLASSES), $(BUILD)/$(subst .,/,$(class)).class) -PACKAGES=$(sort $(basename $(CLASSES))) -PACKAGEDIRS=$(subst .,/,$(PACKAGES)) - -all: sshj.jar - -sshj.jar: classes - $(JAR) cvf $@ -C $(BUILD)/ . - -javadocs: - mkdir -p $(DOCS) - $(JAVA_HOME)/bin/javadoc -d $(DOCS) -classpath $(CLASSPATH) $(PACKAGES) - -clean: - rm -rf $(BUILD) - -classes: classdirs $(CLASS_FILES) - -install: all - cp sshj.jar $(TOP)/../jOVAL-Commercial/components/wsmv/winrs/rsrc/lib - cp sshj.jar $(TOP)/../jOVAL-Commercial/components/provider/remote/rsrc/lib - cp sshj.jar $(TOP)/../jOVAL-Commercial/components/sdk/dist/3rd-party - -classdirs: $(foreach pkg, $(PACKAGEDIRS), $(BUILD)/$(pkg)/) - -$(BUILD)/%.class: $(SRC)/%.java - $(JAVAC) $(JAVACFLAGS) -d $(BUILD) -classpath $(CLASSPATH) $< - -$(BUILD)/%/: - mkdir -p $(subst PKG,,$@) diff --git a/classes.mk b/classes.mk deleted file mode 100644 index 999cbe46..00000000 --- a/classes.mk +++ /dev/null @@ -1,211 +0,0 @@ -CLASSES=\ - com.hierynomus.sshj.backport.JavaVersion \ - com.hierynomus.sshj.backport.Jdk7HttpProxySocket \ - com.hierynomus.sshj.backport.Sockets \ - com.hierynomus.sshj.secg.SecgUtils \ - com.hierynomus.sshj.signature.Ed25519PublicKey \ - com.hierynomus.sshj.signature.SignatureEdDSA \ - com.hierynomus.sshj.transport.cipher.BlockCiphers \ - com.hierynomus.sshj.transport.cipher.ExtendedBlockCiphers \ - com.hierynomus.sshj.transport.cipher.StreamCipher \ - com.hierynomus.sshj.transport.cipher.StreamCiphers \ - com.hierynomus.sshj.transport.IdentificationStringParser \ - net.schmizz.concurrent.ErrorDeliveryUtil \ - net.schmizz.concurrent.Event \ - net.schmizz.concurrent.ExceptionChainer \ - net.schmizz.concurrent.Promise \ - net.schmizz.keepalive.Heartbeater \ - net.schmizz.keepalive.KeepAlive \ - net.schmizz.keepalive.KeepAliveProvider \ - net.schmizz.keepalive.KeepAliveRunner \ - net.schmizz.sshj.AbstractService \ - net.schmizz.sshj.AndroidConfig \ - net.schmizz.sshj.common.Base64 \ - net.schmizz.sshj.common.Buffer \ - net.schmizz.sshj.common.ByteArrayUtils \ - net.schmizz.sshj.common.DisconnectReason \ - net.schmizz.sshj.common.ErrorNotifiable \ - net.schmizz.sshj.common.Factory \ - net.schmizz.sshj.common.IOUtils \ - net.schmizz.sshj.common.KeyType \ - net.schmizz.sshj.common.Message \ - net.schmizz.sshj.common.SecurityUtils \ - net.schmizz.sshj.common.SSHException \ - net.schmizz.sshj.common.SSHPacket \ - net.schmizz.sshj.common.SSHPacketHandler \ - net.schmizz.sshj.common.SSHRuntimeException \ - net.schmizz.sshj.common.StreamCopier \ - net.schmizz.sshj.Config \ - net.schmizz.sshj.ConfigImpl \ - net.schmizz.sshj.connection.channel.AbstractChannel \ - net.schmizz.sshj.connection.channel.Channel \ - net.schmizz.sshj.connection.channel.ChannelInputStream \ - net.schmizz.sshj.connection.channel.ChannelOutputStream \ - net.schmizz.sshj.connection.channel.direct.AbstractDirectChannel \ - net.schmizz.sshj.connection.channel.direct.LocalPortForwarder \ - net.schmizz.sshj.connection.channel.direct.PTYMode \ - net.schmizz.sshj.connection.channel.direct.Session \ - net.schmizz.sshj.connection.channel.direct.SessionChannel \ - net.schmizz.sshj.connection.channel.direct.SessionFactory \ - net.schmizz.sshj.connection.channel.direct.Signal \ - net.schmizz.sshj.connection.channel.forwarded.AbstractForwardedChannel \ - net.schmizz.sshj.connection.channel.forwarded.AbstractForwardedChannelOpener \ - net.schmizz.sshj.connection.channel.forwarded.ConnectListener \ - net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener \ - net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder \ - net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener \ - net.schmizz.sshj.connection.channel.forwarded.X11Forwarder \ - net.schmizz.sshj.connection.channel.OpenFailException \ - net.schmizz.sshj.connection.channel.SocketStreamCopyMonitor \ - net.schmizz.sshj.connection.channel.Window \ - net.schmizz.sshj.connection.Connection \ - net.schmizz.sshj.connection.ConnectionException \ - net.schmizz.sshj.connection.ConnectionImpl \ - net.schmizz.sshj.DefaultConfig \ - net.schmizz.sshj.Service \ - net.schmizz.sshj.sftp.FileAttributes \ - net.schmizz.sshj.sftp.FileMode \ - net.schmizz.sshj.sftp.OpenMode \ - net.schmizz.sshj.sftp.PacketReader \ - net.schmizz.sshj.sftp.PacketType \ - net.schmizz.sshj.sftp.PathComponents \ - net.schmizz.sshj.sftp.PathHelper \ - net.schmizz.sshj.sftp.RandomAccessRemoteFile \ - net.schmizz.sshj.sftp.RemoteDirectory \ - net.schmizz.sshj.sftp.RemoteFile \ - net.schmizz.sshj.sftp.RemoteResource \ - net.schmizz.sshj.sftp.RemoteResourceFilter \ - net.schmizz.sshj.sftp.RemoteResourceInfo \ - net.schmizz.sshj.sftp.Request \ - net.schmizz.sshj.sftp.Requester \ - net.schmizz.sshj.sftp.Response \ - net.schmizz.sshj.sftp.SFTPClient \ - net.schmizz.sshj.sftp.SFTPEngine \ - net.schmizz.sshj.sftp.SFTPException \ - net.schmizz.sshj.sftp.SFTPFileTransfer \ - net.schmizz.sshj.sftp.SFTPPacket \ - net.schmizz.sshj.sftp.StatefulSFTPClient \ - net.schmizz.sshj.signature.AbstractSignature \ - net.schmizz.sshj.signature.Signature \ - net.schmizz.sshj.signature.SignatureDSA \ - net.schmizz.sshj.signature.SignatureECDSA \ - net.schmizz.sshj.signature.SignatureRSA \ - net.schmizz.sshj.SocketClient \ - net.schmizz.sshj.SSHClient \ - net.schmizz.sshj.transport.cipher.AES128CBC \ - net.schmizz.sshj.transport.cipher.AES128CTR \ - net.schmizz.sshj.transport.cipher.AES192CBC \ - net.schmizz.sshj.transport.cipher.AES192CTR \ - net.schmizz.sshj.transport.cipher.AES256CBC \ - net.schmizz.sshj.transport.cipher.AES256CTR \ - net.schmizz.sshj.transport.cipher.BaseCipher \ - net.schmizz.sshj.transport.cipher.BlockCipher \ - net.schmizz.sshj.transport.cipher.BlowfishCBC \ - net.schmizz.sshj.transport.cipher.Cipher \ - net.schmizz.sshj.transport.cipher.NoneCipher \ - net.schmizz.sshj.transport.cipher.TripleDESCBC \ - net.schmizz.sshj.transport.compression.Compression \ - net.schmizz.sshj.transport.compression.DelayedZlibCompression \ - net.schmizz.sshj.transport.compression.NoneCompression \ - net.schmizz.sshj.transport.compression.ZlibCompression \ - net.schmizz.sshj.transport.Converter \ - net.schmizz.sshj.transport.Decoder \ - net.schmizz.sshj.transport.digest.BaseDigest \ - net.schmizz.sshj.transport.digest.Digest \ - net.schmizz.sshj.transport.digest.MD5 \ - net.schmizz.sshj.transport.digest.SHA1 \ - net.schmizz.sshj.transport.digest.SHA256 \ - net.schmizz.sshj.transport.digest.SHA384 \ - net.schmizz.sshj.transport.digest.SHA512 \ - net.schmizz.sshj.transport.DisconnectListener \ - net.schmizz.sshj.transport.Encoder \ - net.schmizz.sshj.transport.kex.AbstractDH \ - net.schmizz.sshj.transport.kex.AbstractDHG \ - net.schmizz.sshj.transport.kex.AbstractDHGex \ - net.schmizz.sshj.transport.kex.Curve25519DH \ - net.schmizz.sshj.transport.kex.Curve25519SHA256 \ - net.schmizz.sshj.transport.kex.DH \ - net.schmizz.sshj.transport.kex.DHBase \ - net.schmizz.sshj.transport.kex.DHG1 \ - net.schmizz.sshj.transport.kex.DHG14 \ - net.schmizz.sshj.transport.kex.DHGexSHA1 \ - net.schmizz.sshj.transport.kex.DHGexSHA256 \ - net.schmizz.sshj.transport.kex.DHGroupData \ - net.schmizz.sshj.transport.kex.ECDH \ - net.schmizz.sshj.transport.kex.ECDHNistP \ - net.schmizz.sshj.transport.kex.KeyExchange \ - net.schmizz.sshj.transport.kex.KeyExchangeBase \ - net.schmizz.sshj.transport.KeyExchanger \ - net.schmizz.sshj.transport.mac.BaseMAC \ - net.schmizz.sshj.transport.mac.HMACMD5 \ - net.schmizz.sshj.transport.mac.HMACMD596 \ - net.schmizz.sshj.transport.mac.HMACSHA1 \ - net.schmizz.sshj.transport.mac.HMACSHA196 \ - net.schmizz.sshj.transport.mac.HMACSHA2256 \ - net.schmizz.sshj.transport.mac.HMACSHA2512 \ - net.schmizz.sshj.transport.mac.MAC \ - net.schmizz.sshj.transport.NegotiatedAlgorithms \ - net.schmizz.sshj.transport.Proposal \ - net.schmizz.sshj.transport.random.BouncyCastleRandom \ - net.schmizz.sshj.transport.random.JCERandom \ - net.schmizz.sshj.transport.random.Random \ - net.schmizz.sshj.transport.random.SingletonRandomFactory \ - net.schmizz.sshj.transport.Reader \ - net.schmizz.sshj.transport.Transport \ - net.schmizz.sshj.transport.TransportException \ - net.schmizz.sshj.transport.TransportImpl \ - net.schmizz.sshj.transport.verification.AlgorithmsVerifier \ - net.schmizz.sshj.transport.verification.ConsoleKnownHostsVerifier \ - net.schmizz.sshj.transport.verification.HostKeyVerifier \ - net.schmizz.sshj.transport.verification.OpenSSHKnownHosts \ - net.schmizz.sshj.transport.verification.PromiscuousVerifier \ - net.schmizz.sshj.userauth.AuthParams \ - net.schmizz.sshj.userauth.keyprovider.FileKeyProvider \ - net.schmizz.sshj.userauth.keyprovider.KeyFormat \ - net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper \ - net.schmizz.sshj.userauth.keyprovider.KeyProvider \ - net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil \ - net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile \ - net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile \ - net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile \ - net.schmizz.sshj.userauth.method.AbstractAuthMethod \ - net.schmizz.sshj.userauth.method.AuthGssApiWithMic \ - net.schmizz.sshj.userauth.method.AuthHostbased \ - net.schmizz.sshj.userauth.method.AuthKeyboardInteractive \ - net.schmizz.sshj.userauth.method.AuthMethod \ - net.schmizz.sshj.userauth.method.AuthNone \ - net.schmizz.sshj.userauth.method.AuthPassword \ - net.schmizz.sshj.userauth.method.AuthPublickey \ - net.schmizz.sshj.userauth.method.ChallengeResponseProvider \ - net.schmizz.sshj.userauth.method.KeyedAuthMethod \ - net.schmizz.sshj.userauth.method.PasswordResponseProvider \ - net.schmizz.sshj.userauth.password.AccountResource \ - net.schmizz.sshj.userauth.password.PasswordFinder \ - net.schmizz.sshj.userauth.password.PasswordUpdateProvider \ - net.schmizz.sshj.userauth.password.PasswordUtils \ - net.schmizz.sshj.userauth.password.PrivateKeyFileResource \ - net.schmizz.sshj.userauth.password.PrivateKeyReaderResource \ - net.schmizz.sshj.userauth.password.PrivateKeyStringResource \ - net.schmizz.sshj.userauth.password.Resource \ - net.schmizz.sshj.userauth.UserAuth \ - net.schmizz.sshj.userauth.UserAuthException \ - net.schmizz.sshj.userauth.UserAuthImpl \ - net.schmizz.sshj.xfer.AbstractFileTransfer \ - net.schmizz.sshj.xfer.FilePermission \ - net.schmizz.sshj.xfer.FileSystemFile \ - net.schmizz.sshj.xfer.FileTransfer \ - net.schmizz.sshj.xfer.InMemoryDestFile \ - net.schmizz.sshj.xfer.InMemorySourceFile \ - net.schmizz.sshj.xfer.LocalDestFile \ - net.schmizz.sshj.xfer.LocalFileFilter \ - net.schmizz.sshj.xfer.LocalSourceFile \ - net.schmizz.sshj.xfer.LoggingTransferListener \ - net.schmizz.sshj.xfer.scp.AbstractSCPClient \ - net.schmizz.sshj.xfer.scp.ScpCommandLine \ - net.schmizz.sshj.xfer.scp.SCPDownloadClient \ - net.schmizz.sshj.xfer.scp.SCPEngine \ - net.schmizz.sshj.xfer.scp.SCPException \ - net.schmizz.sshj.xfer.scp.SCPFileTransfer \ - net.schmizz.sshj.xfer.scp.SCPRemoteException \ - net.schmizz.sshj.xfer.scp.SCPUploadClient \ - net.schmizz.sshj.xfer.TransferListener diff --git a/rsrc/lib/JZlib.jar b/rsrc/lib/JZlib.jar deleted file mode 100644 index 91a3529f..00000000 Binary files a/rsrc/lib/JZlib.jar and /dev/null differ diff --git a/rsrc/lib/bcpkix-jdk15on-154.jar b/rsrc/lib/bcpkix-jdk15on-154.jar deleted file mode 100644 index 86f7f0be..00000000 Binary files a/rsrc/lib/bcpkix-jdk15on-154.jar and /dev/null differ diff --git a/rsrc/lib/bcprov-ext-jdk15on-154.jar b/rsrc/lib/bcprov-ext-jdk15on-154.jar deleted file mode 100644 index f31c1c1c..00000000 Binary files a/rsrc/lib/bcprov-ext-jdk15on-154.jar and /dev/null differ diff --git a/rsrc/lib/bcprov-jdk15on-154.jar b/rsrc/lib/bcprov-jdk15on-154.jar deleted file mode 100644 index bd95185a..00000000 Binary files a/rsrc/lib/bcprov-jdk15on-154.jar and /dev/null differ diff --git a/rsrc/lib/cal10n-api-0.7.4.jar b/rsrc/lib/cal10n-api-0.7.4.jar deleted file mode 100644 index b6bf621f..00000000 Binary files a/rsrc/lib/cal10n-api-0.7.4.jar and /dev/null differ diff --git a/rsrc/lib/ecc25519.jar b/rsrc/lib/ecc25519.jar deleted file mode 100644 index 88f97028..00000000 Binary files a/rsrc/lib/ecc25519.jar and /dev/null differ diff --git a/rsrc/lib/ed25519.jar b/rsrc/lib/ed25519.jar deleted file mode 100644 index e0c4a8d8..00000000 Binary files a/rsrc/lib/ed25519.jar and /dev/null differ diff --git a/rsrc/lib/slf4j-api-1.6.2.jar b/rsrc/lib/slf4j-api-1.6.2.jar deleted file mode 100644 index 773b0b86..00000000 Binary files a/rsrc/lib/slf4j-api-1.6.2.jar and /dev/null differ diff --git a/rsrc/lib/slf4j-ext-1.6.2.jar b/rsrc/lib/slf4j-ext-1.6.2.jar deleted file mode 100644 index 3e0d6861..00000000 Binary files a/rsrc/lib/slf4j-ext-1.6.2.jar and /dev/null differ diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java new file mode 100644 index 00000000..736b78da --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java @@ -0,0 +1,309 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed 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 net.schmizz.sshj.userauth.keyprovider; + +import net.schmizz.sshj.common.Base64; +import net.schmizz.sshj.common.IOUtils; +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.transport.cipher.AES128CBC; +import net.schmizz.sshj.transport.cipher.AES192CBC; +import net.schmizz.sshj.transport.cipher.AES256CBC; +import net.schmizz.sshj.transport.cipher.Cipher; +import net.schmizz.sshj.transport.cipher.NoneCipher; +import net.schmizz.sshj.transport.cipher.TripleDESCBC; +import net.schmizz.sshj.transport.digest.Digest; +import net.schmizz.sshj.transport.digest.MD5; +import net.schmizz.sshj.userauth.password.PasswordFinder; +import net.schmizz.sshj.userauth.password.PasswordUtils; +import net.schmizz.sshj.userauth.password.PrivateKeyFileResource; +import net.schmizz.sshj.userauth.password.PrivateKeyReaderResource; +import net.schmizz.sshj.userauth.password.PrivateKeyStringResource; +import net.schmizz.sshj.userauth.password.Resource; + +import java.io.BufferedReader; +import java.io.File; +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; +import javax.xml.bind.DatatypeConverter; + +/** Represents a PKCS5-encoded key file. This is the format typically used by OpenSSH, OpenSSL, Amazon, etc. */ +public class PKCS5KeyFile + implements FileKeyProvider { + + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public FileKeyProvider create() { + return new PKCS5KeyFile(); + } + + @Override + public String getName() { + return "PKCS5"; + } + } + + protected PasswordFinder pwdf; + protected Resource resource; + protected KeyPair kp; + + protected KeyType type; + + protected char[] passphrase; // for blanking out + + @Override + public PrivateKey getPrivate() + throws IOException { + return kp != null ? kp.getPrivate() : (kp = readKeyPair()).getPrivate(); + } + + @Override + public PublicKey getPublic() + throws IOException { + return kp != null ? kp.getPublic() : (kp = readKeyPair()).getPublic(); + } + + @Override + public KeyType getType() + throws IOException { + return type != null ? type : (type = KeyType.fromKey(getPublic())); + } + + @Override + public void init(Reader location) { + assert location != null; + resource = new PrivateKeyReaderResource(location); + } + + @Override + public void init(Reader location, PasswordFinder pwdf) { + init(location); + this.pwdf = pwdf; + } + + @Override + public void init(File location) { + assert location != null; + resource = new PrivateKeyFileResource(location.getAbsoluteFile()); + } + + @Override + public void init(File location, PasswordFinder pwdf) { + init(location); + this.pwdf = pwdf; + } + + @Override + public void init(String privateKey, String publicKey) { + assert privateKey != null; + assert publicKey == null; + resource = new PrivateKeyStringResource(privateKey); + } + + @Override + public void init(String privateKey, String publicKey, PasswordFinder pwdf) { + init(privateKey, publicKey); + this.pwdf = pwdf; + } + + protected KeyPair readKeyPair() + throws IOException { + BufferedReader reader = new BufferedReader(resource.getReader()); + try { + String type = null; + String line = null; + Cipher cipher = new NoneCipher(); + StringBuffer sb = new StringBuffer(); + byte[] iv = new byte[0]; // salt + while ((line = reader.readLine()) != null) { + if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) { + int end = line.length() - 17; + if (end > 11) { + type = line.substring(11, line.length() - 17); + } else { + type = "UNKNOWN"; + } + } else if (line.startsWith("-----END")) { + break; + } else if (type != null) { + if (line.startsWith("Proc-Type: ")) { + if (!"4,ENCRYPTED".equals(line.substring(11))) { + throw new IOException("Unrecognized Proc-Type: " + line.substring(11)); + } + } else if (line.startsWith("DEK-Info: ")) { + int ptr = line.indexOf(","); + if (ptr == -1) { + throw new IOException("Unrecognized DEK-Info: " + line.substring(10)); + } else { + String algorithm = line.substring(10,ptr); + if ("DES-EDE3-CBC".equals(algorithm)) { + cipher = new TripleDESCBC(); + iv = DatatypeConverter.parseHexBinary(line.substring(ptr+1)); + } else if ("AES-128-CBC".equals(algorithm)) { + cipher = new AES128CBC(); + iv = DatatypeConverter.parseHexBinary(line.substring(ptr+1)); + } else if ("AES-192-CBC".equals(algorithm)) { + cipher = new AES192CBC(); + iv = Base64.decode(line.substring(ptr+1)); + } else if ("AES-256-CBC".equals(algorithm)) { + cipher = new AES256CBC(); + iv = Base64.decode(line.substring(ptr+1)); + } else { + throw new IOException("Not a supported algorithm: " + algorithm); + } + } + } else if (line.length() > 0) { + sb.append(line); + } + } + } + + byte[] data = Base64.decode(sb.toString()); + if (pwdf != null) { + boolean decrypted = false; + do { + CharBuffer cb = CharBuffer.wrap(pwdf.reqPassword(resource)); + ByteBuffer bb = IOUtils.UTF8.encode(cb); + byte[] passphrase = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit()); + Arrays.fill(cb.array(), '\u0000'); + Arrays.fill(bb.array(), (byte)0); + byte[] key = new byte[cipher.getBlockSize()]; + iv = Arrays.copyOfRange(iv, 0, cipher.getIVSize()); + Digest md5 = new MD5(); + md5.init(); + int hsize = md5.getBlockSize(); + byte[] hn = new byte[key.length / hsize * hsize + (key.length % hsize == 0 ? 0 : hsize)]; + byte[] tmp = null; + for (int i=0; i + hsize <= hn.length;) { + if (tmp != null) { + md5.update(tmp, 0, tmp.length); + } + md5.update(passphrase, 0, passphrase.length); + md5.update(iv, 0, iv.length > 8 ? 8 : iv.length); + tmp = md5.digest(); + System.arraycopy(tmp, 0, hn, i, tmp.length); + i += tmp.length; + } + Arrays.fill(passphrase, (byte)0); + System.arraycopy(hn, 0, key, 0, key.length); + cipher.init(Cipher.Mode.Decrypt, key, iv); + Arrays.fill(key, (byte)0); + cipher.update(data, 0, data.length); + decrypted = 0x30 == data[0]; + } while (!decrypted && pwdf.shouldRetry(resource)); + } + if (0x30 != data[0]) { + throw new IOException("Failed to decrypt key"); + } + + ASN1Data asn = new ASN1Data(data); + if ("RSA".equals(type)) { + KeyFactory factory = KeyFactory.getInstance("RSA"); + asn.readNext(); + BigInteger modulus = asn.readNext(); + BigInteger pubExp = asn.readNext(); + BigInteger prvExp = asn.readNext(); + PublicKey pubKey = factory.generatePublic(new RSAPublicKeySpec(modulus, pubExp)); + PrivateKey prvKey = factory.generatePrivate(new RSAPrivateKeySpec(modulus, prvExp)); + return new KeyPair(pubKey, prvKey); + } else if ("DSA".equals(type)) { + KeyFactory factory = KeyFactory.getInstance("DSA"); + asn.readNext(); + BigInteger p = asn.readNext(); + BigInteger q = asn.readNext(); + BigInteger g = asn.readNext(); + BigInteger pub = asn.readNext(); + BigInteger prv = asn.readNext(); + PublicKey pubKey = factory.generatePublic(new DSAPublicKeySpec(pub, p, q, g)); + PrivateKey prvKey = factory.generatePrivate(new DSAPrivateKeySpec(prv, p, q, g)); + return new KeyPair(pubKey, prvKey); + } else { + throw new IOException("Unrecognized key type: " + type); + } + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (InvalidKeySpecException e) { + throw new IOException(e); + } finally { + reader.close(); + } + } + + @Override + public String toString() { + return "PKCS5KeyFile{resource=" + resource + "}"; + } + + class ASN1Data { + private byte[] buff; + private int index, length; + + ASN1Data(byte[] buff) throws IOException { + this.buff = buff; + index = 0; + if (buff[index++] != (byte)0x30) { + throw new IOException("Not ASN.1 data"); + } + length = buff[index++] & 0xff; + if ((length & 0x80) != 0) { + int counter = length & 0x7f; + length = 0; + while (counter-- > 0) { + length = (length << 8) + (buff[index++] & 0xff); + } + } + if ((index + length) > buff.length) { + throw new IOException("Length mismatch: " + buff.length + " != " + (index + length)); + } + } + + BigInteger readNext() throws IOException { + if (index >= length) { + throw new EOFException(); + } else if (buff[index++] != 0x02) { + throw new IOException("Not an int code: " + Integer.toHexString(0xff & buff[index])); + } + int length = buff[index++] & 0xff; + if ((length & 0x80) != 0) { + int counter = length & 0x7f; + length = 0; + while (counter-- > 0) { + length = (length << 8) + (buff[index++] & 0xff); + } + } + byte[] sequence = new byte[length]; + System.arraycopy(buff, index, sequence, 0, length); + index += length; + return new BigInteger(sequence); + } + } +} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java index ff47780e..097490d0 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -15,47 +15,29 @@ */ package net.schmizz.sshj.userauth.keyprovider; -import net.schmizz.sshj.common.Base64; import net.schmizz.sshj.common.IOUtils; import net.schmizz.sshj.common.KeyType; -import net.schmizz.sshj.transport.cipher.AES128CBC; -import net.schmizz.sshj.transport.cipher.AES192CBC; -import net.schmizz.sshj.transport.cipher.AES256CBC; -import net.schmizz.sshj.transport.cipher.Cipher; -import net.schmizz.sshj.transport.cipher.NoneCipher; -import net.schmizz.sshj.transport.cipher.TripleDESCBC; -import net.schmizz.sshj.transport.digest.Digest; -import net.schmizz.sshj.transport.digest.MD5; import net.schmizz.sshj.userauth.password.PasswordFinder; import net.schmizz.sshj.userauth.password.PasswordUtils; import net.schmizz.sshj.userauth.password.PrivateKeyFileResource; import net.schmizz.sshj.userauth.password.PrivateKeyReaderResource; import net.schmizz.sshj.userauth.password.PrivateKeyStringResource; import net.schmizz.sshj.userauth.password.Resource; - +import org.bouncycastle.openssl.EncryptionException; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; import java.io.File; -import java.io.EOFException; import java.io.IOException; import java.io.Reader; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.security.KeyFactory; import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPrivateKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.util.Arrays; -import javax.xml.bind.DatatypeConverter; /** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */ public class PKCS8KeyFile @@ -141,173 +123,51 @@ public class PKCS8KeyFile protected KeyPair readKeyPair() throws IOException { - BufferedReader reader = new BufferedReader(resource.getReader()); - try { - String type = null; - String line = null; - Cipher cipher = new NoneCipher(); - StringBuffer sb = new StringBuffer(); - byte[] iv = new byte[0]; // salt - while ((line = reader.readLine()) != null) { - if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) { - int end = line.length() - 17; - if (end > 11) { - type = line.substring(11, line.length() - 17); - } else { - type = "UNKNOWN"; - } - } else if (line.startsWith("-----END")) { - break; - } else if (type != null) { - if (line.startsWith("Proc-Type: ")) { - if (!"4,ENCRYPTED".equals(line.substring(11))) { - throw new IOException("Unrecognized Proc-Type: " + line.substring(11)); - } - } else if (line.startsWith("DEK-Info: ")) { - int ptr = line.indexOf(","); - if (ptr == -1) { - throw new IOException("Unrecognized DEK-Info: " + line.substring(10)); - } else { - String algorithm = line.substring(10,ptr); - if ("DES-EDE3-CBC".equals(algorithm)) { - cipher = new TripleDESCBC(); - iv = DatatypeConverter.parseHexBinary(line.substring(ptr+1)); - } else if ("AES-128-CBC".equals(algorithm)) { - cipher = new AES128CBC(); - iv = DatatypeConverter.parseHexBinary(line.substring(ptr+1)); - } else if ("AES-192-CBC".equals(algorithm)) { - cipher = new AES192CBC(); - iv = Base64.decode(line.substring(ptr+1)); - } else if ("AES-256-CBC".equals(algorithm)) { - cipher = new AES256CBC(); - iv = Base64.decode(line.substring(ptr+1)); - } else { - throw new IOException("Not a supported algorithm: " + algorithm); - } - } - } else if (line.length() > 0) { - sb.append(line); - } - } - } + KeyPair kp = null; - byte[] data = Base64.decode(sb.toString()); - if (pwdf != null) { - boolean decrypted = false; - do { - CharBuffer cb = CharBuffer.wrap(pwdf.reqPassword(resource)); - ByteBuffer bb = IOUtils.UTF8.encode(cb); - byte[] passphrase = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit()); - Arrays.fill(cb.array(), '\u0000'); - Arrays.fill(bb.array(), (byte)0); - byte[] key = new byte[cipher.getBlockSize()]; - iv = Arrays.copyOfRange(iv, 0, cipher.getIVSize()); - Digest md5 = new MD5(); - md5.init(); - int hsize = md5.getBlockSize(); - byte[] hn = new byte[key.length / hsize * hsize + (key.length % hsize == 0 ? 0 : hsize)]; - byte[] tmp = null; - for (int i=0; i + hsize <= hn.length;) { - if (tmp != null) { - md5.update(tmp, 0, tmp.length); - } - md5.update(passphrase, 0, passphrase.length); - md5.update(iv, 0, iv.length > 8 ? 8 : iv.length); - tmp = md5.digest(); - System.arraycopy(tmp, 0, hn, i, tmp.length); - i += tmp.length; + for (PEMParser r = null; ; ) { + // while the PasswordFinder tells us we should retry + try { + r = new PEMParser(resource.getReader()); + final Object o = r.readObject(); + + final JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + pemConverter.setProvider("BC"); + + if (o instanceof PEMEncryptedKeyPair) { + final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o; + JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); + decryptorBuilder.setProvider("BC"); + try { + passphrase = pwdf == null ? null : pwdf.reqPassword(resource); + kp = pemConverter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorBuilder.build(passphrase))); + } finally { + PasswordUtils.blankOut(passphrase); } - Arrays.fill(passphrase, (byte)0); - System.arraycopy(hn, 0, key, 0, key.length); - cipher.init(Cipher.Mode.Decrypt, key, iv); - Arrays.fill(key, (byte)0); - cipher.update(data, 0, data.length); - decrypted = 0x30 == data[0]; - } while (!decrypted && pwdf.shouldRetry(resource)); - } - if (0x30 != data[0]) { - throw new IOException("Failed to decrypt key"); - } + } else if (o instanceof PEMKeyPair) { + kp = pemConverter.getKeyPair((PEMKeyPair) o); + } else { + log.debug("Expected PEMEncryptedKeyPair or PEMKeyPair, got: {}", o); + } - ASN1Data asn = new ASN1Data(data); - if ("RSA".equals(type)) { - KeyFactory factory = KeyFactory.getInstance("RSA"); - asn.readNext(); - BigInteger modulus = asn.readNext(); - BigInteger pubExp = asn.readNext(); - BigInteger prvExp = asn.readNext(); - PublicKey pubKey = factory.generatePublic(new RSAPublicKeySpec(modulus, pubExp)); - PrivateKey prvKey = factory.generatePrivate(new RSAPrivateKeySpec(modulus, prvExp)); - return new KeyPair(pubKey, prvKey); - } else if ("DSA".equals(type)) { - KeyFactory factory = KeyFactory.getInstance("DSA"); - asn.readNext(); - BigInteger p = asn.readNext(); - BigInteger q = asn.readNext(); - BigInteger g = asn.readNext(); - BigInteger pub = asn.readNext(); - BigInteger prv = asn.readNext(); - PublicKey pubKey = factory.generatePublic(new DSAPublicKeySpec(pub, p, q, g)); - PrivateKey prvKey = factory.generatePrivate(new DSAPrivateKeySpec(prv, p, q, g)); - return new KeyPair(pubKey, prvKey); - } else { - throw new IOException("Unrecognized key type: " + type); - } - } catch (NoSuchAlgorithmException e) { - throw new IOException(e); - } catch (InvalidKeySpecException e) { - throw new IOException(e); - } finally { - reader.close(); - } + } catch (EncryptionException e) { + if (pwdf != null && pwdf.shouldRetry(resource)) + continue; + else + throw e; + } finally { + IOUtils.closeQuietly(r); + } + break; + } + + if (kp == null) + throw new IOException("Could not read key pair from: " + resource); + return kp; } @Override public String toString() { return "PKCS8KeyFile{resource=" + resource + "}"; } - - class ASN1Data { - private byte[] buff; - private int index, length; - - ASN1Data(byte[] buff) throws IOException { - this.buff = buff; - index = 0; - if (buff[index++] != (byte)0x30) { - throw new IOException("Not ASN.1 data"); - } - length = buff[index++] & 0xff; - if ((length & 0x80) != 0) { - int counter = length & 0x7f; - length = 0; - while (counter-- > 0) { - length = (length << 8) + (buff[index++] & 0xff); - } - } - if ((index + length) > buff.length) { - throw new IOException("Length mismatch: " + buff.length + " != " + (index + length)); - } - } - - BigInteger readNext() throws IOException { - if (index >= length) { - throw new EOFException(); - } else if (buff[index++] != 0x02) { - throw new IOException("Not an int code: " + Integer.toHexString(0xff & buff[index])); - } - int length = buff[index++] & 0xff; - if ((length & 0x80) != 0) { - int counter = length & 0x7f; - length = 0; - while (counter-- > 0) { - length = (length << 8) + (buff[index++] & 0xff); - } - } - byte[] sequence = new byte[length]; - System.arraycopy(buff, index, sequence, 0, length); - index += length; - return new BigInteger(sequence); - } - } -} +} \ No newline at end of file