mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-07 15:50:57 +03:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb1629f250 | ||
|
|
8856aaea61 | ||
|
|
1f6615b57a | ||
|
|
e5084ed8db | ||
|
|
3729119e23 | ||
|
|
aed3decf1d | ||
|
|
303c03061c | ||
|
|
5e3a08a637 | ||
|
|
d0800058e8 | ||
|
|
ad9c2d5411 | ||
|
|
ed65176b68 | ||
|
|
28f3280a84 | ||
|
|
d69f722908 | ||
|
|
1d7cb8c2c6 | ||
|
|
6ad6242ed1 | ||
|
|
3310530d42 | ||
|
|
3685f9dc36 | ||
|
|
f8cad120a6 | ||
|
|
56dd4e4af4 | ||
|
|
9f8cf1f298 | ||
|
|
a51270791d | ||
|
|
d43fc4551e | ||
|
|
93bf6c0089 | ||
|
|
7b535a8db3 | ||
|
|
9d4f8fc46a | ||
|
|
2b21ec6032 | ||
|
|
8e15a8bd7d | ||
|
|
531eb97767 | ||
|
|
e36fd0fb3d | ||
|
|
382321deca | ||
|
|
7b75fb3d53 | ||
|
|
4d84d3f67c | ||
|
|
8eb7d1a2ad | ||
|
|
a03fa9ac63 | ||
|
|
bcb15e6ccd | ||
|
|
d85b22fe8d |
20
README.adoc
20
README.adoc
@@ -1,7 +1,7 @@
|
||||
= sshj - SSHv2 library for Java
|
||||
Jeroen van Erp
|
||||
:sshj_groupid: com.hierynomus
|
||||
:sshj_version: 0.19.0
|
||||
:sshj_version: 0.21.1
|
||||
:source-highlighter: pygments
|
||||
|
||||
image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"]
|
||||
@@ -37,7 +37,7 @@ If you're building your project using Maven, you can add the following dependenc
|
||||
If your project is built using another build tool that uses the Maven Central repository, translate this dependency into the format used by your build tool.
|
||||
|
||||
== Building SSHJ
|
||||
. Clone the Overthere repository.
|
||||
. Clone the SSHJ repository.
|
||||
. Ensure you have Java6 installed with the http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html[Unlimited strength Java Cryptography Extensions (JCE)].
|
||||
. Run the command `./gradlew clean build`.
|
||||
|
||||
@@ -75,7 +75,7 @@ key exchange::
|
||||
`diffie-hellman-group16-sha256`, `diffie-hellman-group16-sha384@ssh.com`, `diffie-hellman-group16-sha512@ssh.com`, `diffie-hellman-group18-sha512@ssh.com`
|
||||
|
||||
signatures::
|
||||
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ssh-ed25519`
|
||||
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519`
|
||||
|
||||
mac::
|
||||
`hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`
|
||||
@@ -84,7 +84,7 @@ compression::
|
||||
`zlib` and `zlib@openssh.com` (delayed zlib)
|
||||
|
||||
private key files::
|
||||
`pkcs5`, `pkcs8`, `openssh-key-v1`
|
||||
`pkcs5`, `pkcs8`, `openssh-key-v1`, `ssh-rsa-cert-v01@openssh.com`, `ssh-dsa-cert-v01@openssh.com`
|
||||
|
||||
If you need something that is not included, it shouldn't be too hard to add (do contribute it!)
|
||||
|
||||
@@ -104,6 +104,18 @@ Google Group: http://groups.google.com/group/sshj-users
|
||||
Fork away!
|
||||
|
||||
== Release history
|
||||
SSHJ 0.22.0 (2017-08-24)::
|
||||
* Fixed https://github.com/hierynomus/sshj/pulls/341[#341]: Fixed path walking during recursive copy
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/338[#338]: Added ConsolePasswordFinder to read password from stdin
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/336[#336]: Added support for ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 signatures
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/331[#331]: Added support for wildcards in known_hosts file
|
||||
SSHJ 0.21.1 (2017-04-25)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/322[#322]: Fix regression from 40f956b (invalid length parameter on outputstream)
|
||||
SSHJ 0.21.0 (2017-04-14)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/319[#319]: Added support for `ssh-rsa-cert-v01@openssh.com` and `ssh-dsa-cert-v01@openssh.com` certificate key files
|
||||
* Upgraded Gradle to 3.4.1
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/305[#305]: Added support for custom string encoding
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/312[#312]: Upgraded BouncyCastle to 1.56
|
||||
SSHJ 0.20.0 (2017-02-09)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/294[#294]: Reference ED25519 by constant instead of name
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/293[#293], https://github.com/hierynomus/sshj/pulls/295[#295] and https://github.com/hierynomus/sshj/pulls/301[#301]: Fixed OSGi packaging
|
||||
|
||||
14
build.gradle
14
build.gradle
@@ -24,7 +24,7 @@ targetCompatibility = 1.6
|
||||
|
||||
configurations.compile.transitive = false
|
||||
|
||||
def bouncycastleVersion = "1.51"
|
||||
def bouncycastleVersion = "1.56"
|
||||
|
||||
dependencies {
|
||||
compile "org.slf4j:slf4j-api:1.7.7"
|
||||
@@ -36,7 +36,7 @@ dependencies {
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
testCompile "org.mockito:mockito-core:2.8.47"
|
||||
testCompile "org.apache.sshd:sshd-core:1.2.0"
|
||||
testRuntime "ch.qos.logback:logback-classic:1.1.2"
|
||||
testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17'
|
||||
@@ -68,10 +68,12 @@ if (JavaVersion.current().isJava8Compatible()) {
|
||||
}
|
||||
}
|
||||
|
||||
task writeSshjVersionProperties << {
|
||||
project.file("${project.buildDir}/resources/main").mkdirs()
|
||||
project.file("${project.buildDir}/resources/main/sshj.properties").withWriter { w ->
|
||||
w.append("sshj.version=${version}")
|
||||
task writeSshjVersionProperties {
|
||||
doLast {
|
||||
project.file("${project.buildDir}/resources/main").mkdirs()
|
||||
project.file("${project.buildDir}/resources/main/sshj.properties").withWriter { w ->
|
||||
w.append("sshj.version=${version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
|
||||
|
||||
@@ -18,7 +18,8 @@ package com.hierynomus.sshj.backport;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.*;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
|
||||
public class Jdk7HttpProxySocket extends Socket {
|
||||
|
||||
@@ -48,7 +49,7 @@ public class Jdk7HttpProxySocket extends Socket {
|
||||
}
|
||||
InetSocketAddress isa = (InetSocketAddress) endpoint;
|
||||
String httpConnect = "CONNECT " + isa.getHostName() + ":" + isa.getPort() + " HTTP/1.0\n\n";
|
||||
getOutputStream().write(httpConnect.getBytes(Charset.forName("UTF-8")));
|
||||
getOutputStream().write(httpConnect.getBytes(IOUtils.UTF8));
|
||||
checkAndFlushProxyResponse();
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ public class Jdk7HttpProxySocket extends Socket {
|
||||
throw new SocketException("Empty response from proxy");
|
||||
}
|
||||
|
||||
String proxyResponse = new String(tmpBuffer, 0, len, "UTF-8");
|
||||
String proxyResponse = new String(tmpBuffer, 0, len, IOUtils.UTF8);
|
||||
|
||||
// Expecting HTTP/1.x 200 OK
|
||||
if (proxyResponse.contains("200")) {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 com.hierynomus.sshj.transport.verification;
|
||||
|
||||
import net.schmizz.sshj.common.Base64;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class KnownHostMatchers {
|
||||
|
||||
public static HostMatcher createMatcher(String hostEntry) throws SSHException {
|
||||
if (hostEntry.contains(",")) {
|
||||
return new AnyHostMatcher(hostEntry);
|
||||
}
|
||||
if (hostEntry.startsWith("!")) {
|
||||
return new NegateHostMatcher(hostEntry);
|
||||
}
|
||||
if (hostEntry.startsWith("|1|")) {
|
||||
return new HashedHostMatcher(hostEntry);
|
||||
}
|
||||
if (hostEntry.contains("*") || hostEntry.contains("?")) {
|
||||
return new WildcardHostMatcher(hostEntry);
|
||||
}
|
||||
|
||||
return new EquiHostMatcher(hostEntry);
|
||||
}
|
||||
|
||||
public interface HostMatcher {
|
||||
boolean match(String hostname) throws IOException;
|
||||
}
|
||||
|
||||
private static class EquiHostMatcher implements HostMatcher {
|
||||
private String host;
|
||||
|
||||
public EquiHostMatcher(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) {
|
||||
return host.equals(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HashedHostMatcher implements HostMatcher {
|
||||
private final MAC sha1 = new HMACSHA1();
|
||||
private final String hash;
|
||||
private final String salt;
|
||||
private byte[] saltyBytes;
|
||||
|
||||
HashedHostMatcher(String hash) throws SSHException {
|
||||
this.hash = hash;
|
||||
final String[] hostParts = hash.split("\\|");
|
||||
if (hostParts.length != 4) {
|
||||
throw new SSHException("Unrecognized format for hashed hostname");
|
||||
}
|
||||
salt = hostParts[2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
return hash.equals(hashHost(hostname));
|
||||
}
|
||||
|
||||
private String hashHost(String host) throws IOException {
|
||||
sha1.init(getSaltyBytes());
|
||||
return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
|
||||
}
|
||||
|
||||
private byte[] getSaltyBytes() throws IOException {
|
||||
if (saltyBytes == null) {
|
||||
saltyBytes = Base64.decode(salt);
|
||||
}
|
||||
return saltyBytes;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class AnyHostMatcher implements HostMatcher {
|
||||
private final List<HostMatcher> matchers;
|
||||
|
||||
AnyHostMatcher(String hostEntry) throws SSHException {
|
||||
matchers = new ArrayList<HostMatcher>();
|
||||
for (String subEntry : hostEntry.split(",")) {
|
||||
matchers.add(KnownHostMatchers.createMatcher(subEntry));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
for (HostMatcher matcher : matchers) {
|
||||
if (matcher.match(hostname)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NegateHostMatcher implements HostMatcher {
|
||||
private final HostMatcher matcher;
|
||||
|
||||
NegateHostMatcher(String hostEntry) throws SSHException {
|
||||
this.matcher = createMatcher(hostEntry.substring(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
return !matcher.match(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WildcardHostMatcher implements HostMatcher {
|
||||
private final Pattern pattern;
|
||||
|
||||
public WildcardHostMatcher(String hostEntry) {
|
||||
this.pattern = Pattern.compile(hostEntry.replace(".", "\\.").replace("*", ".*").replace("?", "."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
return pattern.matcher(hostname).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WildcardHostMatcher[" + pattern + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.hierynomus.sshj.userauth.certificate;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Certificate wrapper for public keys, created to help implement
|
||||
* protocol described here:
|
||||
*
|
||||
* https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
|
||||
*
|
||||
* Consumed primarily by net.shmizz.sshj.common.KeyType
|
||||
*
|
||||
* @param <T> inner public key type
|
||||
*/
|
||||
public class Certificate<T extends PublicKey> implements PublicKey {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final T publicKey;
|
||||
private final byte[] nonce;
|
||||
private final BigInteger serial;
|
||||
private final long type;
|
||||
private final String id;
|
||||
private final List<String> validPrincipals;
|
||||
private final Date validAfter;
|
||||
private final Date validBefore;
|
||||
private final Map<String, String> critOptions;
|
||||
private final Map<String, String> extensions;
|
||||
private final byte[] signatureKey;
|
||||
private final byte[] signature;
|
||||
|
||||
Certificate(Builder<T> builder) {
|
||||
this.publicKey = builder.getPublicKey();
|
||||
this.nonce = builder.getNonce();
|
||||
this.serial = builder.getSerial();
|
||||
this.type = builder.getType();
|
||||
this.id = builder.getId();
|
||||
this.validPrincipals = builder.getValidPrincipals();
|
||||
this.validAfter = builder.getValidAfter();
|
||||
this.validBefore = builder.getValidBefore();
|
||||
this.critOptions = builder.getCritOptions();
|
||||
this.extensions = builder.getExtensions();
|
||||
this.signatureKey = builder.getSignatureKey();
|
||||
this.signature = builder.getSignature();
|
||||
}
|
||||
|
||||
public static <P extends PublicKey> Builder<P> getBuilder() {
|
||||
return new Builder<P>();
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public BigInteger getSerial() {
|
||||
return serial;
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<String> getValidPrincipals() {
|
||||
return validPrincipals;
|
||||
}
|
||||
|
||||
public Date getValidAfter() {
|
||||
return validAfter;
|
||||
}
|
||||
|
||||
public Date getValidBefore() {
|
||||
return validBefore;
|
||||
}
|
||||
|
||||
public Map<String, String> getCritOptions() {
|
||||
return critOptions;
|
||||
}
|
||||
|
||||
public Map<String, String> getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
public byte[] getSignatureKey() {
|
||||
return signatureKey;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public T getKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return publicKey.getEncoded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return publicKey.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return publicKey.getFormat();
|
||||
}
|
||||
|
||||
public static class Builder<T extends PublicKey> {
|
||||
private T publicKey;
|
||||
private byte[] nonce;
|
||||
private BigInteger serial;
|
||||
private long type;
|
||||
private String id;
|
||||
private List<String> validPrincipals;
|
||||
private Date validAfter;
|
||||
private Date validBefore;
|
||||
private Map<String, String> critOptions;
|
||||
private Map<String, String> extensions;
|
||||
private byte[] signatureKey;
|
||||
private byte[] signature;
|
||||
|
||||
public Certificate<T> build() {
|
||||
return new Certificate<T>(this);
|
||||
}
|
||||
|
||||
public T getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public Builder<T> publicKey(T publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public Builder<T> nonce(byte[] nonce) {
|
||||
this.nonce = nonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigInteger getSerial() {
|
||||
return serial;
|
||||
}
|
||||
|
||||
public Builder<T> serial(BigInteger serial) {
|
||||
this.serial = serial;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Builder<T> type(long type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Builder<T> id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getValidPrincipals() {
|
||||
return validPrincipals;
|
||||
}
|
||||
|
||||
public Builder<T> validPrincipals(List<String> validPrincipals) {
|
||||
this.validPrincipals = validPrincipals;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getValidAfter() {
|
||||
return validAfter;
|
||||
}
|
||||
|
||||
public Builder<T> validAfter(Date validAfter) {
|
||||
this.validAfter = validAfter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getValidBefore() {
|
||||
return validBefore;
|
||||
}
|
||||
|
||||
public Builder<T> validBefore(Date validBefore) {
|
||||
this.validBefore = validBefore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getCritOptions() {
|
||||
return critOptions;
|
||||
}
|
||||
|
||||
public Builder<T> critOptions(Map<String, String> critOptions) {
|
||||
this.critOptions = critOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
public Builder<T> extensions(Map<String, String> extensions) {
|
||||
this.extensions = extensions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getSignatureKey() {
|
||||
return signatureKey;
|
||||
}
|
||||
|
||||
public Builder<T> signatureKey(byte[] signatureKey) {
|
||||
this.signatureKey = signatureKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public Builder<T> signature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,22 @@
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.hierynomus.sshj.signature.SignatureEdDSA;
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
import com.hierynomus.sshj.transport.cipher.StreamCiphers;
|
||||
import com.hierynomus.sshj.transport.kex.DHGroups;
|
||||
import com.hierynomus.sshj.transport.kex.ExtendedDHGroups;
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
@@ -28,13 +38,26 @@ import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.signature.SignatureDSA;
|
||||
import net.schmizz.sshj.signature.SignatureECDSA;
|
||||
import net.schmizz.sshj.signature.SignatureRSA;
|
||||
import net.schmizz.sshj.transport.cipher.*;
|
||||
import net.schmizz.sshj.transport.cipher.AES128CBC;
|
||||
import net.schmizz.sshj.transport.cipher.AES128CTR;
|
||||
import net.schmizz.sshj.transport.cipher.AES192CBC;
|
||||
import net.schmizz.sshj.transport.cipher.AES192CTR;
|
||||
import net.schmizz.sshj.transport.cipher.AES256CBC;
|
||||
import net.schmizz.sshj.transport.cipher.AES256CTR;
|
||||
import net.schmizz.sshj.transport.cipher.BlowfishCBC;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.cipher.TripleDESCBC;
|
||||
import net.schmizz.sshj.transport.compression.NoneCompression;
|
||||
import net.schmizz.sshj.transport.kex.Curve25519SHA256;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
||||
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
||||
import net.schmizz.sshj.transport.mac.*;
|
||||
import net.schmizz.sshj.transport.mac.HMACMD5;
|
||||
import net.schmizz.sshj.transport.mac.HMACMD596;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA196;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA2256;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA2512;
|
||||
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
|
||||
import net.schmizz.sshj.transport.random.JCERandom;
|
||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||
@@ -42,10 +65,6 @@ import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
|
||||
@@ -210,7 +229,9 @@ public class DefaultConfig
|
||||
|
||||
protected void initSignatureFactories() {
|
||||
setSignatureFactories(
|
||||
new SignatureECDSA.Factory(),
|
||||
new SignatureECDSA.Factory256(),
|
||||
new SignatureECDSA.Factory384(),
|
||||
new SignatureECDSA.Factory521(),
|
||||
new SignatureRSA.Factory(),
|
||||
new SignatureDSA.Factory(),
|
||||
new SignatureEdDSA.Factory()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
@@ -61,6 +62,7 @@ import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.*;
|
||||
@@ -128,6 +130,9 @@ public class SSHClient
|
||||
|
||||
private final List<LocalPortForwarder> forwarders = new ArrayList<LocalPortForwarder>();
|
||||
|
||||
/** character set of the remote machine */
|
||||
protected Charset remoteCharset = IOUtils.UTF8;
|
||||
|
||||
/** Default constructor. Initializes this object using {@link DefaultConfig}. */
|
||||
public SSHClient() {
|
||||
this(new DefaultConfig());
|
||||
@@ -440,6 +445,15 @@ public class SSHClient
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character set used to communicate with the remote machine for certain strings (like paths).
|
||||
*
|
||||
* @return remote character set
|
||||
*/
|
||||
public Charset getRemoteCharset() {
|
||||
return remoteCharset;
|
||||
}
|
||||
|
||||
/** @return a {@link RemotePortForwarder} that allows requesting remote forwarding over this connection. */
|
||||
public RemotePortForwarder getRemotePortForwarder() {
|
||||
synchronized (conn) {
|
||||
@@ -708,12 +722,22 @@ public class SSHClient
|
||||
doKex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the character set used to communicate with the remote machine for certain strings (like paths)
|
||||
*
|
||||
* @param remoteCharset
|
||||
* remote character set or {@code null} for default
|
||||
*/
|
||||
public void setRemoteCharset(Charset remoteCharset) {
|
||||
this.remoteCharset = remoteCharset != null ? remoteCharset : IOUtils.UTF8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session startSession()
|
||||
throws ConnectionException, TransportException {
|
||||
checkConnected();
|
||||
checkAuthenticated();
|
||||
final SessionChannel sess = new SessionChannel(conn);
|
||||
final SessionChannel sess = new SessionChannel(conn, remoteCharset);
|
||||
sess.open();
|
||||
return sess;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
@@ -57,6 +57,11 @@ public class Buffer<T extends Buffer<T>> {
|
||||
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
|
||||
public static final int MAX_SIZE = (1 << 30);
|
||||
|
||||
/** Maximum size of a uint64 */
|
||||
private static final BigInteger MAX_UINT64_VALUE = BigInteger.ONE
|
||||
.shiftLeft(64)
|
||||
.subtract(BigInteger.ONE);
|
||||
|
||||
protected static int getNextPowerOf2(int i) {
|
||||
int j = 1;
|
||||
while (j < i) {
|
||||
@@ -343,10 +348,29 @@ public class Buffer<T extends Buffer<T>> {
|
||||
return uint64;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BigInteger readUInt64AsBigInteger()
|
||||
throws BufferException {
|
||||
byte[] magnitude = new byte[8];
|
||||
readRawBytes(magnitude);
|
||||
return new BigInteger(1, magnitude);
|
||||
}
|
||||
|
||||
public T putUInt64(long uint64) {
|
||||
if (uint64 < 0)
|
||||
throw new IllegalArgumentException("Invalid value: " + uint64);
|
||||
return putUInt64Unchecked(uint64);
|
||||
}
|
||||
|
||||
public T putUInt64(BigInteger uint64) {
|
||||
if (uint64.compareTo(MAX_UINT64_VALUE) > 0 ||
|
||||
uint64.compareTo(BigInteger.ZERO) < 0) {
|
||||
throw new IllegalArgumentException("Invalid value: " + uint64);
|
||||
}
|
||||
return putUInt64Unchecked(uint64.longValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T putUInt64Unchecked(long uint64) {
|
||||
data[wpos++] = (byte) (uint64 >> 56);
|
||||
data[wpos++] = (byte) (uint64 >> 48);
|
||||
data[wpos++] = (byte) (uint64 >> 40);
|
||||
@@ -361,24 +385,31 @@ public class Buffer<T extends Buffer<T>> {
|
||||
/**
|
||||
* Reads an SSH string
|
||||
*
|
||||
* @param cs the charset to use for decoding
|
||||
*
|
||||
* @return the string as a Java {@code String}
|
||||
*/
|
||||
public String readString()
|
||||
public String readString(Charset cs)
|
||||
throws BufferException {
|
||||
int len = readUInt32AsInt();
|
||||
if (len < 0 || len > 32768)
|
||||
throw new BufferException("Bad item length: " + len);
|
||||
ensureAvailable(len);
|
||||
String s;
|
||||
try {
|
||||
s = new String(data, rpos, len, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
String s = new String(data, rpos, len, cs);
|
||||
rpos += len;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an SSH string using {@code UTF8}
|
||||
*
|
||||
* @return the string as a Java {@code String}
|
||||
*/
|
||||
public String readString()
|
||||
throws BufferException {
|
||||
return readString(IOUtils.UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an SSH string
|
||||
*
|
||||
@@ -397,8 +428,12 @@ public class Buffer<T extends Buffer<T>> {
|
||||
return putBytes(str, offset, len);
|
||||
}
|
||||
|
||||
public T putString(String string, Charset cs) {
|
||||
return putString(string.getBytes(cs));
|
||||
}
|
||||
|
||||
public T putString(String string) {
|
||||
return putString(string.getBytes(IOUtils.UTF8));
|
||||
return putString(string, IOUtils.UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.common;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.hierynomus.sshj.secg.SecgUtils;
|
||||
|
||||
public class ECDSAVariationsAdapter {
|
||||
|
||||
private final static String BASE_ALGORITHM_NAME = "ecdsa-sha2-nistp";
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ECDSAVariationsAdapter.class);
|
||||
|
||||
public final static Map<String, String> SUPPORTED_CURVES = new HashMap<String, String>();
|
||||
public final static Map<String, String> NIST_CURVES_NAMES = new HashMap<String, String>();
|
||||
|
||||
static {
|
||||
NIST_CURVES_NAMES.put("256", "p-256");
|
||||
NIST_CURVES_NAMES.put("384", "p-384");
|
||||
NIST_CURVES_NAMES.put("521", "p-521");
|
||||
|
||||
SUPPORTED_CURVES.put("256", "nistp256");
|
||||
SUPPORTED_CURVES.put("384", "nistp384");
|
||||
SUPPORTED_CURVES.put("521", "nistp521");
|
||||
}
|
||||
|
||||
public static PublicKey readPubKeyFromBuffer(Buffer<?> buf, String variation) throws GeneralSecurityException {
|
||||
String algorithm = BASE_ALGORITHM_NAME + variation;
|
||||
if (!SecurityUtils.isBouncyCastleRegistered()) {
|
||||
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + algorithm);
|
||||
}
|
||||
try {
|
||||
// final String algo = buf.readString(); it has been already read
|
||||
final String curveName = buf.readString();
|
||||
final int keyLen = buf.readUInt32AsInt();
|
||||
final byte x04 = buf.readByte(); // it must be 0x04, but don't think
|
||||
// we need that check
|
||||
final byte[] x = new byte[(keyLen - 1) / 2];
|
||||
final byte[] y = new byte[(keyLen - 1) / 2];
|
||||
buf.readRawBytes(x);
|
||||
buf.readRawBytes(y);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
|
||||
algorithm, curveName, keyLen, x04, Arrays.toString(x), Arrays.toString(y)));
|
||||
}
|
||||
|
||||
if (!SUPPORTED_CURVES.values().contains(curveName)) {
|
||||
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
|
||||
}
|
||||
|
||||
BigInteger bigX = new BigInteger(1, x);
|
||||
BigInteger bigY = new BigInteger(1, y);
|
||||
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName(NIST_CURVES_NAMES.get(variation));
|
||||
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY);
|
||||
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(), ecParams.getG(), ecParams.getN());
|
||||
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
|
||||
return keyFactory.generatePublic(publicSpec);
|
||||
} catch (Exception ex) {
|
||||
throw new GeneralSecurityException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
||||
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
|
||||
|
||||
buf.putString("nistp" + Integer.toString(fieldSizeFromKey(ecdsa)))
|
||||
.putBytes(encoded);
|
||||
}
|
||||
|
||||
public static int fieldSizeFromKey(ECPublicKey ecPublicKey) {
|
||||
return ecPublicKey.getParams().getCurve().getField().getFieldSize();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,34 +15,41 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import com.hierynomus.sshj.secg.SecgUtils;
|
||||
import com.hierynomus.sshj.signature.Ed25519PublicKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.*;
|
||||
import java.security.interfaces.DSAPrivateKey;
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.hierynomus.sshj.signature.Ed25519PublicKey;
|
||||
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||
|
||||
/** Type of key e.g. rsa, dsa */
|
||||
public enum KeyType {
|
||||
|
||||
|
||||
/** SSH identifier for RSA keys */
|
||||
RSA("ssh-rsa") {
|
||||
@Override
|
||||
@@ -60,18 +67,16 @@ public enum KeyType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final RSAPublicKey rsaKey = (RSAPublicKey) pk;
|
||||
buf.putString(sType)
|
||||
.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
buf.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/** SSH identifier for DSA keys */
|
||||
@@ -93,13 +98,12 @@ public enum KeyType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
|
||||
buf.putString(sType)
|
||||
.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
buf.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,70 +113,66 @@ public enum KeyType {
|
||||
|
||||
},
|
||||
|
||||
/** SSH identifier for ECDSA keys */
|
||||
ECDSA("ecdsa-sha2-nistp256") {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
/** SSH identifier for ECDSA-256 keys */
|
||||
ECDSA256("ecdsa-sha2-nistp256") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
if (!SecurityUtils.isBouncyCastleRegistered()) {
|
||||
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + sType);
|
||||
}
|
||||
try {
|
||||
// final String algo = buf.readString(); it has been already read
|
||||
final String curveName = buf.readString();
|
||||
final int keyLen = buf.readUInt32AsInt();
|
||||
final byte x04 = buf.readByte(); // it must be 0x04, but don't think we need that check
|
||||
final byte[] x = new byte[(keyLen - 1) / 2];
|
||||
final byte[] y = new byte[(keyLen - 1) / 2];
|
||||
buf.readRawBytes(x);
|
||||
buf.readRawBytes(y);
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
|
||||
sType,
|
||||
curveName,
|
||||
keyLen,
|
||||
x04,
|
||||
Arrays.toString(x),
|
||||
Arrays.toString(y))
|
||||
);
|
||||
}
|
||||
|
||||
if (!NISTP_CURVE.equals(curveName)) {
|
||||
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
|
||||
}
|
||||
|
||||
BigInteger bigX = new BigInteger(1, x);
|
||||
BigInteger bigY = new BigInteger(1, y);
|
||||
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName("p-256");
|
||||
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY);
|
||||
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(),
|
||||
ecParams.getG(), ecParams.getN());
|
||||
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
|
||||
return keyFactory.generatePublic(publicSpec);
|
||||
} catch (Exception ex) {
|
||||
throw new GeneralSecurityException(ex);
|
||||
}
|
||||
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "256");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
||||
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
|
||||
|
||||
buf.putString(sType)
|
||||
.putString(NISTP_CURVE)
|
||||
.putBytes(encoded);
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return ("ECDSA".equals(key.getAlgorithm()));
|
||||
return ("ECDSA".equals(key.getAlgorithm()) && ECDSAVariationsAdapter.fieldSizeFromKey((ECPublicKey) key) == 256);
|
||||
}
|
||||
},
|
||||
|
||||
/** SSH identifier for ECDSA-384 keys */
|
||||
ECDSA384("ecdsa-sha2-nistp384") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "384");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return ("ECDSA".equals(key.getAlgorithm()) && ECDSAVariationsAdapter.fieldSizeFromKey((ECPublicKey) key) == 384);
|
||||
}
|
||||
},
|
||||
|
||||
/** SSH identifier for ECDSA-521 keys */
|
||||
ECDSA521("ecdsa-sha2-nistp521") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "521");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return ("ECDSA".equals(key.getAlgorithm()) && ECDSAVariationsAdapter.fieldSizeFromKey((ECPublicKey) key) == 521);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -202,9 +202,9 @@ public enum KeyType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
EdDSAPublicKey key = (EdDSAPublicKey) pk;
|
||||
buf.putString(sType).putBytes(key.getAbyte());
|
||||
buf.putBytes(key.getAbyte());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,6 +213,44 @@ public enum KeyType {
|
||||
}
|
||||
},
|
||||
|
||||
/** Signed rsa certificate */
|
||||
RSA_CERT("ssh-rsa-cert-v01@openssh.com") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return CertUtils.readPubKey(buf, RSA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
CertUtils.writePubKeyContentsIntoBuffer(pk, RSA, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return CertUtils.isCertificateOfType(key, RSA);
|
||||
}
|
||||
},
|
||||
|
||||
/** Signed dsa certificate */
|
||||
DSA_CERT("ssh-dss-cert-v01@openssh.com") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return CertUtils.readPubKey(buf, DSA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
CertUtils.writePubKeyContentsIntoBuffer(pk, DSA, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return CertUtils.isCertificateOfType(key, DSA);
|
||||
}
|
||||
},
|
||||
|
||||
/** Unrecognized */
|
||||
UNKNOWN("unknown") {
|
||||
@Override
|
||||
@@ -226,15 +264,17 @@ public enum KeyType {
|
||||
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static final String NISTP_CURVE = "nistp256";
|
||||
|
||||
protected final String sType;
|
||||
|
||||
private KeyType(String type) {
|
||||
@@ -244,7 +284,11 @@ public enum KeyType {
|
||||
public abstract PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
public abstract void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf);
|
||||
protected abstract void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf);
|
||||
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
writePubKeyContentsIntoBuffer(pk, buf.putString(sType));
|
||||
}
|
||||
|
||||
protected abstract boolean isMyType(Key key);
|
||||
|
||||
@@ -266,4 +310,129 @@ public enum KeyType {
|
||||
public String toString() {
|
||||
return sType;
|
||||
}
|
||||
|
||||
static class CertUtils {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends PublicKey> Certificate<T> readPubKey(Buffer<?> buf, KeyType innerKeyType) throws GeneralSecurityException {
|
||||
Certificate.Builder<T> builder = Certificate.getBuilder();
|
||||
|
||||
try {
|
||||
builder.nonce(buf.readBytes());
|
||||
builder.publicKey((T) innerKeyType.readPubKeyFromBuffer(buf));
|
||||
builder.serial(buf.readUInt64AsBigInteger());
|
||||
builder.type(buf.readUInt32());
|
||||
builder.id(buf.readString());
|
||||
builder.validPrincipals(unpackList(buf.readBytes()));
|
||||
builder.validAfter(dateFromEpoch(buf.readUInt64()));
|
||||
builder.validBefore(dateFromEpoch(buf.readUInt64()));
|
||||
builder.critOptions(unpackMap(buf.readBytes()));
|
||||
builder.extensions(unpackMap(buf.readBytes()));
|
||||
buf.readString(); // reserved
|
||||
builder.signatureKey(buf.readBytes());
|
||||
builder.signature(buf.readBytes());
|
||||
} catch (Buffer.BufferException be) {
|
||||
throw new GeneralSecurityException(be);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static void writePubKeyContentsIntoBuffer(PublicKey publicKey, KeyType innerKeyType, Buffer<?> buf) {
|
||||
Certificate<PublicKey> certificate = toCertificate(publicKey);
|
||||
buf.putBytes(certificate.getNonce());
|
||||
innerKeyType.writePubKeyContentsIntoBuffer(certificate.getKey(), buf);
|
||||
buf.putUInt64(certificate.getSerial())
|
||||
.putUInt32(certificate.getType())
|
||||
.putString(certificate.getId())
|
||||
.putBytes(packList(certificate.getValidPrincipals()))
|
||||
.putUInt64(epochFromDate(certificate.getValidAfter()))
|
||||
.putUInt64(epochFromDate(certificate.getValidBefore()))
|
||||
.putBytes(packMap(certificate.getCritOptions()))
|
||||
.putBytes(packMap(certificate.getExtensions()))
|
||||
.putString("") // reserved
|
||||
.putBytes(certificate.getSignatureKey())
|
||||
.putBytes(certificate.getSignature());
|
||||
}
|
||||
|
||||
static boolean isCertificateOfType(Key key, KeyType innerKeyType) {
|
||||
if (!(key instanceof Certificate)) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Key innerKey = ((Certificate<PublicKey>) key).getKey();
|
||||
return innerKeyType.isMyType(innerKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Certificate<PublicKey> toCertificate(PublicKey key) {
|
||||
if (!(key instanceof Certificate)) {
|
||||
throw new UnsupportedOperationException("Can't convert non-certificate key " +
|
||||
key.getAlgorithm() + " to certificate");
|
||||
}
|
||||
return ((Certificate<PublicKey>) key);
|
||||
}
|
||||
|
||||
private static Date dateFromEpoch(long seconds) {
|
||||
return new Date(seconds * 1000);
|
||||
}
|
||||
|
||||
private static long epochFromDate(Date date) {
|
||||
return date.getTime() / 1000;
|
||||
}
|
||||
|
||||
private static String unpackString(byte[] packedString) throws BufferException {
|
||||
if (packedString.length == 0) {
|
||||
return "";
|
||||
}
|
||||
return new Buffer.PlainBuffer(packedString).readString();
|
||||
}
|
||||
|
||||
private static List<String> unpackList(byte[] packedString) throws BufferException {
|
||||
List<String> list = new ArrayList<String>();
|
||||
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
|
||||
while (buf.available() > 0) {
|
||||
list.add(buf.readString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static Map<String, String> unpackMap(byte[] packedString) throws BufferException {
|
||||
Map<String, String> map = new LinkedHashMap<String, String>();
|
||||
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
|
||||
while (buf.available() > 0) {
|
||||
String name = buf.readString();
|
||||
String data = unpackString(buf.readStringAsBytes());
|
||||
map.put(name, data);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static byte[] packString(String data) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return "".getBytes();
|
||||
}
|
||||
return new Buffer.PlainBuffer().putString(data).getCompactData();
|
||||
}
|
||||
|
||||
private static byte[] packList(Iterable<String> strings) {
|
||||
Buffer<?> buf = new Buffer.PlainBuffer();
|
||||
for (String string : strings) {
|
||||
buf.putString(string);
|
||||
}
|
||||
return buf.getCompactData();
|
||||
}
|
||||
|
||||
private static byte[] packMap(Map<String, String> map) {
|
||||
Buffer<?> buf = new Buffer.PlainBuffer();
|
||||
List<String> keys = new ArrayList<String>(map.keySet());
|
||||
Collections.sort(keys);
|
||||
for (String key : keys) {
|
||||
buf.putString(key);
|
||||
String value = map.get(key);
|
||||
buf.putString(packString(value));
|
||||
}
|
||||
return buf.getCompactData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.slf4j.Logger;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -51,6 +52,8 @@ public abstract class AbstractChannel
|
||||
private final int id;
|
||||
/** Remote recipient ID */
|
||||
private int recipient;
|
||||
/** Remote character set */
|
||||
private final Charset remoteCharset;
|
||||
|
||||
private boolean eof = false;
|
||||
|
||||
@@ -78,12 +81,16 @@ public abstract class AbstractChannel
|
||||
private volatile boolean autoExpand = false;
|
||||
|
||||
protected AbstractChannel(Connection conn, String type) {
|
||||
this(conn, type, null);
|
||||
}
|
||||
protected AbstractChannel(Connection conn, String type, Charset remoteCharset) {
|
||||
this.conn = conn;
|
||||
this.loggerFactory = conn.getTransport().getConfig().getLoggerFactory();
|
||||
this.type = type;
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
this.trans = conn.getTransport();
|
||||
|
||||
this.remoteCharset = remoteCharset != null ? remoteCharset : IOUtils.UTF8;
|
||||
id = conn.nextID();
|
||||
|
||||
lwin = new Window.Local(conn.getWindowSize(), conn.getMaxPacketSize(), loggerFactory);
|
||||
@@ -135,6 +142,11 @@ public abstract class AbstractChannel
|
||||
return recipient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Charset getRemoteCharset() {
|
||||
return remoteCharset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteMaxPacketSize() {
|
||||
return rwin.getMaxPacketSize();
|
||||
|
||||
@@ -24,6 +24,7 @@ import net.schmizz.sshj.transport.TransportException;
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -123,6 +124,9 @@ public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
*/
|
||||
int getRecipient();
|
||||
|
||||
/** @return the character set used to communicate with the remote machine for certain strings (like paths). */
|
||||
Charset getRemoteCharset();
|
||||
|
||||
/**
|
||||
* @return the maximum packet size as specified by the remote end.
|
||||
*/
|
||||
|
||||
@@ -138,7 +138,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
int length = len;
|
||||
int offset = off;
|
||||
while (length > 0) {
|
||||
final int n = buffer.write(data, offset, len);
|
||||
final int n = buffer.write(data, offset, length);
|
||||
offset += n;
|
||||
length -= n;
|
||||
}
|
||||
@@ -149,8 +149,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
private void checkClose()
|
||||
throws SSHException {
|
||||
private void checkClose() throws SSHException {
|
||||
if (closed) {
|
||||
if (error != null)
|
||||
throw error;
|
||||
@@ -160,8 +159,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close()
|
||||
throws IOException {
|
||||
public synchronized void close() throws IOException {
|
||||
if (!closed) {
|
||||
try {
|
||||
buffer.flush(false);
|
||||
|
||||
@@ -77,8 +77,7 @@ public abstract class Window {
|
||||
super(initialWinSize, maxPacketSize, loggerFactory);
|
||||
}
|
||||
|
||||
public long awaitExpansion(long was)
|
||||
throws ConnectionException {
|
||||
public long awaitExpansion(long was) throws ConnectionException {
|
||||
synchronized (lock) {
|
||||
while (size <= was) {
|
||||
log.debug("Waiting, need size to grow from {} bytes", was);
|
||||
|
||||
@@ -25,6 +25,7 @@ import net.schmizz.sshj.connection.channel.Channel;
|
||||
import net.schmizz.sshj.connection.channel.OpenFailException;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Base class for direct channels whose open is initated by the client. */
|
||||
@@ -41,6 +42,15 @@ public abstract class AbstractDirectChannel
|
||||
conn.attach(this);
|
||||
}
|
||||
|
||||
protected AbstractDirectChannel(Connection conn, String type, Charset remoteCharset) {
|
||||
super(conn, type, remoteCharset);
|
||||
|
||||
/*
|
||||
* We expect to receive channel open confirmation/rejection and want to be able to next this packet.
|
||||
*/
|
||||
conn.attach(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open()
|
||||
throws ConnectionException, TransportException {
|
||||
|
||||
@@ -22,6 +22,7 @@ import net.schmizz.sshj.connection.channel.ChannelInputStream;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -47,6 +48,10 @@ public class SessionChannel
|
||||
super(conn, "session");
|
||||
}
|
||||
|
||||
public SessionChannel(Connection conn, Charset remoteCharset) {
|
||||
super(conn, "session", remoteCharset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allocateDefaultPTY()
|
||||
throws ConnectionException, TransportException {
|
||||
@@ -93,7 +98,7 @@ public class SessionChannel
|
||||
throws ConnectionException, TransportException {
|
||||
checkReuse();
|
||||
log.debug("Will request to exec `{}`", command);
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command))
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command, getRemoteCharset()))
|
||||
.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||
usedUp = true;
|
||||
return this;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RemoteDirectory
|
||||
case NAME:
|
||||
final int count = res.readUInt32AsInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final String name = res.readString();
|
||||
final String name = res.readString(requester.sub.getRemoteCharset());
|
||||
res.readString(); // long name - IGNORED - shdve never been in the protocol
|
||||
final FileAttributes attrs = res.readFileAttributes();
|
||||
final PathComponents comps = requester.getPathHelper().getComponents(path, name);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.concurrent.Promise;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
@@ -25,6 +26,7 @@ import org.slf4j.Logger;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -137,7 +139,7 @@ public class SFTPEngine
|
||||
public RemoteFile open(String path, Set<OpenMode> modes, FileAttributes fa)
|
||||
throws IOException {
|
||||
final byte[] handle = doRequest(
|
||||
newRequest(PacketType.OPEN).putString(path).putUInt32(OpenMode.toMask(modes)).putFileAttributes(fa)
|
||||
newRequest(PacketType.OPEN).putString(path, sub.getRemoteCharset()).putUInt32(OpenMode.toMask(modes)).putFileAttributes(fa)
|
||||
).ensurePacketTypeIs(PacketType.HANDLE).readBytes();
|
||||
return new RemoteFile(this, path, handle);
|
||||
}
|
||||
@@ -155,7 +157,7 @@ public class SFTPEngine
|
||||
public RemoteDirectory openDir(String path)
|
||||
throws IOException {
|
||||
final byte[] handle = doRequest(
|
||||
newRequest(PacketType.OPENDIR).putString(path)
|
||||
newRequest(PacketType.OPENDIR).putString(path, sub.getRemoteCharset())
|
||||
).ensurePacketTypeIs(PacketType.HANDLE).readBytes();
|
||||
return new RemoteDirectory(this, path, handle);
|
||||
}
|
||||
@@ -163,7 +165,7 @@ public class SFTPEngine
|
||||
public void setAttributes(String path, FileAttributes attrs)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.SETSTAT).putString(path).putFileAttributes(attrs)
|
||||
newRequest(PacketType.SETSTAT).putString(path, sub.getRemoteCharset()).putFileAttributes(attrs)
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
@@ -173,13 +175,13 @@ public class SFTPEngine
|
||||
throw new SFTPException("READLINK is not supported in SFTPv" + operativeVersion);
|
||||
return readSingleName(
|
||||
doRequest(
|
||||
newRequest(PacketType.READLINK).putString(path)
|
||||
));
|
||||
newRequest(PacketType.READLINK).putString(path, sub.getRemoteCharset())
|
||||
), sub.getRemoteCharset());
|
||||
}
|
||||
|
||||
public void makeDir(String path, FileAttributes attrs)
|
||||
throws IOException {
|
||||
doRequest(newRequest(PacketType.MKDIR).putString(path).putFileAttributes(attrs)).ensureStatusPacketIsOK();
|
||||
doRequest(newRequest(PacketType.MKDIR).putString(path, sub.getRemoteCharset()).putFileAttributes(attrs)).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void makeDir(String path)
|
||||
@@ -192,21 +194,21 @@ public class SFTPEngine
|
||||
if (operativeVersion < 3)
|
||||
throw new SFTPException("SYMLINK is not supported in SFTPv" + operativeVersion);
|
||||
doRequest(
|
||||
newRequest(PacketType.SYMLINK).putString(linkpath).putString(targetpath)
|
||||
newRequest(PacketType.SYMLINK).putString(linkpath, sub.getRemoteCharset()).putString(targetpath, sub.getRemoteCharset())
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void remove(String filename)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.REMOVE).putString(filename)
|
||||
newRequest(PacketType.REMOVE).putString(filename, sub.getRemoteCharset())
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void removeDir(String path)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.RMDIR).putString(path)
|
||||
newRequest(PacketType.RMDIR).putString(path, sub.getRemoteCharset())
|
||||
).ensureStatusIs(Response.StatusCode.OK);
|
||||
}
|
||||
|
||||
@@ -225,7 +227,7 @@ public class SFTPEngine
|
||||
if (operativeVersion < 1)
|
||||
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
|
||||
doRequest(
|
||||
newRequest(PacketType.RENAME).putString(oldPath).putString(newPath)
|
||||
newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset())
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
@@ -233,8 +235,8 @@ public class SFTPEngine
|
||||
throws IOException {
|
||||
return readSingleName(
|
||||
doRequest(
|
||||
newRequest(PacketType.REALPATH).putString(path)
|
||||
));
|
||||
newRequest(PacketType.REALPATH).putString(path, sub.getRemoteCharset())
|
||||
), sub.getRemoteCharset());
|
||||
}
|
||||
|
||||
public void setTimeoutMs(int timeoutMs) {
|
||||
@@ -258,20 +260,32 @@ public class SFTPEngine
|
||||
|
||||
protected FileAttributes stat(PacketType pt, String path)
|
||||
throws IOException {
|
||||
return doRequest(newRequest(pt).putString(path))
|
||||
return doRequest(newRequest(pt).putString(path, sub.getRemoteCharset()))
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
protected static String readSingleName(Response res)
|
||||
private static byte[] readSingleNameAsBytes(Response res)
|
||||
throws IOException {
|
||||
res.ensurePacketTypeIs(PacketType.NAME);
|
||||
if (res.readUInt32AsInt() == 1)
|
||||
return res.readString();
|
||||
return res.readStringAsBytes();
|
||||
else
|
||||
throw new SFTPException("Unexpected data in " + res.getType() + " packet");
|
||||
}
|
||||
|
||||
/** Using UTF-8 */
|
||||
protected static String readSingleName(Response res)
|
||||
throws IOException {
|
||||
return readSingleName(res, IOUtils.UTF8);
|
||||
}
|
||||
|
||||
/** Using any character set */
|
||||
protected static String readSingleName(Response res, Charset charset)
|
||||
throws IOException {
|
||||
return new String(readSingleNameAsBytes(res), charset);
|
||||
}
|
||||
|
||||
protected synchronized void transmit(SFTPPacket<Request> payload)
|
||||
throws IOException {
|
||||
final int len = payload.available();
|
||||
|
||||
@@ -15,35 +15,73 @@
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SignatureException;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1OutputStream;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SignatureException;
|
||||
|
||||
/** ECDSA {@link Signature} */
|
||||
public class SignatureECDSA
|
||||
extends AbstractSignature {
|
||||
public class SignatureECDSA extends AbstractSignature {
|
||||
|
||||
/** A named factory for ECDSA signature */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
/** A named factory for ECDSA-256 signature */
|
||||
public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureECDSA();
|
||||
return new SignatureECDSA("SHA256withECDSA", KeyType.ECDSA256.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.ECDSA.toString();
|
||||
return KeyType.ECDSA256.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SignatureECDSA() {
|
||||
super("SHA256withECDSA");
|
||||
/** A named factory for ECDSA-384 signature */
|
||||
public static class Factory384 implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureECDSA("SHA384withECDSA", KeyType.ECDSA384.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.ECDSA384.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A named factory for ECDSA-521 signature */
|
||||
public static class Factory521 implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureECDSA("SHA512withECDSA", KeyType.ECDSA521.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.ECDSA521.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String keyTypeName;
|
||||
|
||||
public SignatureECDSA(String algorithm, String keyTypeName) {
|
||||
super(algorithm);
|
||||
this.keyTypeName = keyTypeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,8 +113,8 @@ public class SignatureECDSA
|
||||
try {
|
||||
Buffer sigbuf = new Buffer.PlainBuffer(sig);
|
||||
final String algo = new String(sigbuf.readBytes());
|
||||
if (!"ecdsa-sha2-nistp256".equals(algo)) {
|
||||
throw new SSHRuntimeException(String.format("Signature :: ecdsa-sha2-nistp256 expected, got %s", algo));
|
||||
if (!keyTypeName.equals(algo)) {
|
||||
throw new SSHRuntimeException(String.format("Signature :: " + keyTypeName + " expected, got %s", algo));
|
||||
}
|
||||
final int rsLen = sigbuf.readUInt32AsInt();
|
||||
if (sigbuf.available() != rsLen) {
|
||||
@@ -88,10 +126,23 @@ public class SignatureECDSA
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
return signature.verify(asnEncode(r, s));
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] asnEncode(byte[] r, byte[] s) throws IOException {
|
||||
int rLen = r.length;
|
||||
int sLen = s.length;
|
||||
|
||||
/* We can't have the high bit set, so add an extra zero at the beginning if so. */
|
||||
/*
|
||||
* We can't have the high bit set, so add an extra zero at the beginning
|
||||
* if so.
|
||||
*/
|
||||
if ((r[0] & 0x80) != 0) {
|
||||
rLen++;
|
||||
}
|
||||
@@ -101,37 +152,17 @@ public class SignatureECDSA
|
||||
|
||||
/* Calculate total output length */
|
||||
int length = 6 + rLen + sLen;
|
||||
byte[] asn1 = new byte[length];
|
||||
|
||||
/* ASN.1 SEQUENCE tag */
|
||||
asn1[0] = (byte) 0x30;
|
||||
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||
vector.add(new ASN1Integer(r));
|
||||
vector.add(new ASN1Integer(s));
|
||||
|
||||
/* Size of SEQUENCE */
|
||||
asn1[1] = (byte) (4 + rLen + sLen);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
|
||||
ASN1OutputStream asnOS = new ASN1OutputStream(baos);
|
||||
|
||||
/* ASN.1 INTEGER tag */
|
||||
asn1[2] = (byte) 0x02;
|
||||
asnOS.writeObject(new DERSequence(vector));
|
||||
asnOS.flush();
|
||||
|
||||
/* "r" INTEGER length */
|
||||
asn1[3] = (byte) rLen;
|
||||
|
||||
/* Copy in the "r" INTEGER */
|
||||
System.arraycopy(r, 0, asn1, 4, rLen);
|
||||
|
||||
/* ASN.1 INTEGER tag */
|
||||
asn1[rLen + 4] = (byte) 0x02;
|
||||
|
||||
/* "s" INTEGER length */
|
||||
asn1[rLen + 5] = (byte) sLen;
|
||||
|
||||
/* Copy in the "s" INTEGER */
|
||||
System.arraycopy(s, 0, asn1, (6 + rLen), sLen);
|
||||
|
||||
|
||||
try {
|
||||
return signature.verify(asn1);
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +196,11 @@ public final class TransportImpl
|
||||
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
|
||||
while ((serverID = readIdentification(buf)).isEmpty()) {
|
||||
int b = connInfo.in.read();
|
||||
if (b == -1)
|
||||
if (b == -1) {
|
||||
log.error("Received end of connection, but no identification received. ");
|
||||
throw new TransportException("Server closed connection during identification exchange");
|
||||
|
||||
}
|
||||
buf.putByte((byte) b);
|
||||
}
|
||||
}
|
||||
@@ -587,7 +590,7 @@ public final class TransportImpl
|
||||
try {
|
||||
if (!close.isSet()) {
|
||||
|
||||
log.error("Dying because - {}", ex);
|
||||
log.error("Dying because - {}", ex.getMessage(), ex);
|
||||
|
||||
final SSHException causeOfDeath = SSHException.chainer.chain(ex);
|
||||
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.compression;
|
||||
|
||||
import com.jcraft.jzlib.Deflater;
|
||||
import com.jcraft.jzlib.GZIPException;
|
||||
import com.jcraft.jzlib.Inflater;
|
||||
import com.jcraft.jzlib.JZlib;
|
||||
import com.jcraft.jzlib.ZStream;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
@@ -45,20 +47,24 @@ public class ZlibCompression
|
||||
|
||||
private final byte[] tempBuf = new byte[BUF_SIZE];
|
||||
|
||||
private ZStream stream;
|
||||
private Deflater deflater;
|
||||
private Inflater inflater;
|
||||
|
||||
@Override
|
||||
public void init(Mode mode) {
|
||||
stream = new ZStream();
|
||||
switch (mode) {
|
||||
case DEFLATE:
|
||||
stream.deflateInit(JZlib.Z_DEFAULT_COMPRESSION);
|
||||
break;
|
||||
case INFLATE:
|
||||
stream.inflateInit();
|
||||
break;
|
||||
default:
|
||||
assert false;
|
||||
try {
|
||||
switch (mode) {
|
||||
case DEFLATE:
|
||||
deflater = new Deflater(JZlib.Z_DEFAULT_COMPRESSION);
|
||||
break;
|
||||
case INFLATE:
|
||||
inflater = new Inflater();
|
||||
break;
|
||||
default:
|
||||
assert false;
|
||||
}
|
||||
} catch (GZIPException gze) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,44 +75,43 @@ public class ZlibCompression
|
||||
|
||||
@Override
|
||||
public void compress(Buffer buffer) {
|
||||
stream.next_in = buffer.array();
|
||||
stream.next_in_index = buffer.rpos();
|
||||
stream.avail_in = buffer.available();
|
||||
deflater.setNextIn(buffer.array());
|
||||
deflater.setNextInIndex(buffer.rpos());
|
||||
deflater.setAvailIn(buffer.available());
|
||||
buffer.wpos(buffer.rpos());
|
||||
do {
|
||||
stream.next_out = tempBuf;
|
||||
stream.next_out_index = 0;
|
||||
stream.avail_out = BUF_SIZE;
|
||||
final int status = stream.deflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
deflater.setNextOut(tempBuf);
|
||||
deflater.setNextOutIndex(0);
|
||||
deflater.setAvailOut(BUF_SIZE);
|
||||
final int status = deflater.deflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
if (status == JZlib.Z_OK) {
|
||||
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - stream.avail_out);
|
||||
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - deflater.getAvailOut());
|
||||
} else {
|
||||
throw new SSHRuntimeException("compress: deflate returned " + status);
|
||||
}
|
||||
} while (stream.avail_out == 0);
|
||||
} while (deflater.getAvailOut() == 0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void uncompress(Buffer from, Buffer to)
|
||||
throws TransportException {
|
||||
stream.next_in = from.array();
|
||||
stream.next_in_index = from.rpos();
|
||||
stream.avail_in = from.available();
|
||||
inflater.setNextIn(from.array());
|
||||
inflater.setNextInIndex(from.rpos());
|
||||
inflater.setAvailIn(from.available());
|
||||
while (true) {
|
||||
stream.next_out = tempBuf;
|
||||
stream.next_out_index = 0;
|
||||
stream.avail_out = BUF_SIZE;
|
||||
final int status = stream.inflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
inflater.setNextOut(tempBuf);
|
||||
inflater.setNextOutIndex(0);
|
||||
inflater.setAvailOut(BUF_SIZE);
|
||||
final int status = inflater.inflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
switch (status) {
|
||||
case JZlib.Z_OK:
|
||||
to.putRawBytes(tempBuf, 0, BUF_SIZE - stream.avail_out);
|
||||
to.putRawBytes(tempBuf, 0, BUF_SIZE - inflater.getAvailOut());
|
||||
break;
|
||||
case JZlib.Z_BUF_ERROR:
|
||||
return;
|
||||
default:
|
||||
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned "
|
||||
+ status);
|
||||
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ConsoleKnownHostsVerifier
|
||||
}
|
||||
if (response.equalsIgnoreCase(YES)) {
|
||||
try {
|
||||
entries().add(new SimpleEntry(null, hostname, KeyType.fromKey(key), key));
|
||||
entries().add(new HostEntry(null, hostname, KeyType.fromKey(key), key));
|
||||
write();
|
||||
console.printf("Warning: Permanently added '%s' (%s) to the list of known hosts.\n", hostname, type);
|
||||
} catch (IOException e) {
|
||||
@@ -60,7 +60,7 @@ public class ConsoleKnownHostsVerifier
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
|
||||
protected boolean hostKeyChangedAction(KnownHostEntry entry, String hostname, PublicKey key) {
|
||||
final KeyType type = KeyType.fromKey(key);
|
||||
final String fp = SecurityUtils.getFingerprint(key);
|
||||
final String path = getFile().getAbsolutePath();
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.verification;
|
||||
|
||||
import com.hierynomus.sshj.transport.verification.KnownHostMatchers;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
@@ -26,7 +25,6 @@ import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -40,7 +38,7 @@ public class OpenSSHKnownHosts
|
||||
protected final Logger log;
|
||||
|
||||
protected final File khFile;
|
||||
protected final List<HostEntry> entries = new ArrayList<HostEntry>();
|
||||
protected final List<KnownHostEntry> entries = new ArrayList<KnownHostEntry>();
|
||||
|
||||
public OpenSSHKnownHosts(File khFile)
|
||||
throws IOException {
|
||||
@@ -59,7 +57,7 @@ public class OpenSSHKnownHosts
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
try {
|
||||
HostEntry entry = entryFactory.parseEntry(line);
|
||||
KnownHostEntry entry = entryFactory.parseEntry(line);
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
@@ -83,12 +81,13 @@ public class OpenSSHKnownHosts
|
||||
public boolean verify(final String hostname, final int port, final PublicKey key) {
|
||||
final KeyType type = KeyType.fromKey(key);
|
||||
|
||||
if (type == KeyType.UNKNOWN)
|
||||
if (type == KeyType.UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
|
||||
|
||||
for (HostEntry e : entries) {
|
||||
for (KnownHostEntry e : entries) {
|
||||
try {
|
||||
if (e.appliesTo(type, adjustedHostname))
|
||||
return e.verify(key) || hostKeyChangedAction(e, adjustedHostname, key);
|
||||
@@ -105,12 +104,12 @@ public class OpenSSHKnownHosts
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
|
||||
protected boolean hostKeyChangedAction(KnownHostEntry entry, String hostname, PublicKey key) {
|
||||
log.warn("Host key for `{}` has changed!", hostname);
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<HostEntry> entries() {
|
||||
public List<KnownHostEntry> entries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
@@ -120,7 +119,7 @@ public class OpenSSHKnownHosts
|
||||
throws IOException {
|
||||
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(khFile));
|
||||
try {
|
||||
for (HostEntry entry : entries)
|
||||
for (KnownHostEntry entry : entries)
|
||||
bos.write((entry.getLine() + LS).getBytes(IOUtils.UTF8));
|
||||
} finally {
|
||||
bos.close();
|
||||
@@ -130,7 +129,7 @@ public class OpenSSHKnownHosts
|
||||
/**
|
||||
* Append a single entry
|
||||
*/
|
||||
public void write(HostEntry entry)
|
||||
public void write(KnownHostEntry entry)
|
||||
throws IOException {
|
||||
final BufferedWriter writer = new BufferedWriter(new FileWriter(khFile, true));
|
||||
try {
|
||||
@@ -185,7 +184,7 @@ public class OpenSSHKnownHosts
|
||||
EntryFactory() {
|
||||
}
|
||||
|
||||
public HostEntry parseEntry(String line)
|
||||
public KnownHostEntry parseEntry(String line)
|
||||
throws IOException {
|
||||
if (isComment(line)) {
|
||||
return new CommentEntry(line);
|
||||
@@ -228,11 +227,7 @@ public class OpenSSHKnownHosts
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isHashed(hostnames)) {
|
||||
return new HashedEntry(marker, hostnames, type, key);
|
||||
} else {
|
||||
return new SimpleEntry(marker, hostnames, type, key);
|
||||
}
|
||||
return new HostEntry(marker, hostnames, type, key);
|
||||
}
|
||||
|
||||
private boolean isBits(String type) {
|
||||
@@ -254,25 +249,22 @@ public class OpenSSHKnownHosts
|
||||
|
||||
}
|
||||
|
||||
public interface HostEntry {
|
||||
public interface KnownHostEntry {
|
||||
KeyType getType();
|
||||
|
||||
String getFingerprint();
|
||||
|
||||
boolean appliesTo(String host)
|
||||
throws IOException;
|
||||
boolean appliesTo(String host) throws IOException;
|
||||
|
||||
boolean appliesTo(KeyType type, String host)
|
||||
throws IOException;
|
||||
boolean appliesTo(KeyType type, String host) throws IOException;
|
||||
|
||||
boolean verify(PublicKey key)
|
||||
throws IOException;
|
||||
boolean verify(PublicKey key) throws IOException;
|
||||
|
||||
String getLine();
|
||||
}
|
||||
|
||||
public static class CommentEntry
|
||||
implements HostEntry {
|
||||
implements KnownHostEntry {
|
||||
private final String comment;
|
||||
|
||||
public CommentEntry(String comment) {
|
||||
@@ -290,8 +282,7 @@ public class OpenSSHKnownHosts
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(String host)
|
||||
throws IOException {
|
||||
public boolean appliesTo(String host) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -311,17 +302,20 @@ public class OpenSSHKnownHosts
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class AbstractEntry
|
||||
implements HostEntry {
|
||||
public static class HostEntry implements KnownHostEntry {
|
||||
|
||||
protected final OpenSSHKnownHosts.Marker marker;
|
||||
final OpenSSHKnownHosts.Marker marker;
|
||||
private final String hostPart;
|
||||
protected final KeyType type;
|
||||
protected final PublicKey key;
|
||||
private final KnownHostMatchers.HostMatcher matcher;
|
||||
|
||||
public AbstractEntry(Marker marker, KeyType type, PublicKey key) {
|
||||
HostEntry(Marker marker, String hostPart, KeyType type, PublicKey key) throws SSHException {
|
||||
this.marker = marker;
|
||||
this.hostPart = hostPart;
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.matcher = KnownHostMatchers.createMatcher(hostPart);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -335,8 +329,17 @@ public class OpenSSHKnownHosts
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(PublicKey key)
|
||||
throws IOException {
|
||||
public boolean appliesTo(String host) throws IOException {
|
||||
return matcher.match(host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(KeyType type, String host) throws IOException {
|
||||
return this.type == type && matcher.match(host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(PublicKey key) throws IOException {
|
||||
return key.equals(this.key) && marker != Marker.REVOKED;
|
||||
}
|
||||
|
||||
@@ -356,88 +359,8 @@ public class OpenSSHKnownHosts
|
||||
return Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
|
||||
}
|
||||
|
||||
protected abstract String getHostPart();
|
||||
}
|
||||
|
||||
public static class SimpleEntry
|
||||
extends AbstractEntry {
|
||||
private final List<String> hosts;
|
||||
private final String hostnames;
|
||||
|
||||
public SimpleEntry(Marker marker, String hostnames, KeyType type, PublicKey key) {
|
||||
super(marker, type, key);
|
||||
this.hostnames = hostnames;
|
||||
hosts = Arrays.asList(hostnames.split(","));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHostPart() {
|
||||
return hostnames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(String host)
|
||||
throws IOException {
|
||||
return hosts.contains(host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(KeyType type, String host)
|
||||
throws IOException {
|
||||
return type == this.type && hosts.contains(host);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HashedEntry
|
||||
extends AbstractEntry {
|
||||
private final MAC sha1 = new HMACSHA1();
|
||||
|
||||
private final String hashedHost;
|
||||
private final String salt;
|
||||
|
||||
private byte[] saltyBytes;
|
||||
|
||||
public HashedEntry(Marker marker, String hash, KeyType type, PublicKey key)
|
||||
throws SSHException {
|
||||
super(marker, type, key);
|
||||
this.hashedHost = hash;
|
||||
{
|
||||
final String[] hostParts = hashedHost.split("\\|");
|
||||
if (hostParts.length != 4)
|
||||
throw new SSHException("Unrecognized format for hashed hostname");
|
||||
salt = hostParts[2];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(String host)
|
||||
throws IOException {
|
||||
return hashedHost.equals(hashHost(host));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(KeyType type, String host)
|
||||
throws IOException {
|
||||
return this.type == type && hashedHost.equals(hashHost(host));
|
||||
}
|
||||
|
||||
private String hashHost(String host)
|
||||
throws IOException {
|
||||
sha1.init(getSaltyBytes());
|
||||
return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
|
||||
}
|
||||
|
||||
private byte[] getSaltyBytes()
|
||||
throws IOException {
|
||||
if (saltyBytes == null) {
|
||||
saltyBytes = Base64.decode(salt);
|
||||
}
|
||||
return saltyBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHostPart() {
|
||||
return hashedHost;
|
||||
return hostPart;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,12 +379,12 @@ public class OpenSSHKnownHosts
|
||||
}
|
||||
|
||||
public static Marker fromString(String str) {
|
||||
for (Marker m: values())
|
||||
if (m.sMarker.equals(str))
|
||||
for (Marker m: values()) {
|
||||
if (m.sMarker.equals(str)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class KeyProviderUtil {
|
||||
public static KeyFormat detectKeyFileFormat(File location)
|
||||
throws IOException {
|
||||
return detectKeyFileFormat(new FileReader(location),
|
||||
new File(location + ".pub").exists());
|
||||
new File(location + ".pub").exists() || new File(location + "-cert.pub").exists());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,10 +57,14 @@ public class OpenSSHKeyFile
|
||||
|
||||
@Override
|
||||
public void init(File location) {
|
||||
final File f = new File(location + ".pub");
|
||||
if (f.exists())
|
||||
// try cert key location first
|
||||
File pubKey = new File(location + "-cert.pub");
|
||||
if (!pubKey.exists()) {
|
||||
pubKey = new File(location + ".pub");
|
||||
}
|
||||
if (pubKey.exists())
|
||||
try {
|
||||
initPubKey(new FileReader(f));
|
||||
initPubKey(new FileReader(pubKey));
|
||||
} catch (IOException e) {
|
||||
// let super provide both public & private key
|
||||
log.warn("Error reading public key file: {}", e.toString());
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.password;
|
||||
|
||||
import java.io.Console;
|
||||
import java.util.IllegalFormatException;
|
||||
|
||||
/** A PasswordFinder that reads a password from a console */
|
||||
public class ConsolePasswordFinder implements PasswordFinder {
|
||||
|
||||
public static final String DEFAULT_FORMAT = "Enter passphrase for %s:";
|
||||
|
||||
private final Console console;
|
||||
private final String promptFormat;
|
||||
private final int maxTries;
|
||||
|
||||
private int numTries;
|
||||
|
||||
public ConsolePasswordFinder() {
|
||||
this(System.console());
|
||||
}
|
||||
|
||||
public ConsolePasswordFinder(Console console) {
|
||||
this(console, DEFAULT_FORMAT, 3);
|
||||
}
|
||||
|
||||
public ConsolePasswordFinder(Console console, String promptFormat, int maxTries) {
|
||||
checkFormatString(promptFormat);
|
||||
this.console = console;
|
||||
this.promptFormat = promptFormat;
|
||||
this.maxTries = maxTries;
|
||||
this.numTries = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] reqPassword(Resource<?> resource) {
|
||||
numTries++;
|
||||
if (console == null) {
|
||||
// the request cannot be serviced
|
||||
return null;
|
||||
}
|
||||
return console.readPassword(promptFormat, resource.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry(Resource<?> resource) {
|
||||
return numTries < maxTries;
|
||||
}
|
||||
|
||||
private static void checkFormatString(String promptFormat) {
|
||||
try {
|
||||
String.format(promptFormat, "");
|
||||
} catch (IllegalFormatException e) {
|
||||
throw new IllegalArgumentException("promptFormat must have no more than one %s and no other markers", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class FileSystemFile
|
||||
implements LocalSourceFile, LocalDestFile {
|
||||
@@ -83,8 +84,9 @@ public class FileSystemFile
|
||||
}
|
||||
});
|
||||
|
||||
if (childFiles == null)
|
||||
if (childFiles == null) {
|
||||
throw new IOException("Error listing files in directory: " + this);
|
||||
}
|
||||
|
||||
final List<FileSystemFile> children = new ArrayList<FileSystemFile>();
|
||||
for (File f : childFiles) {
|
||||
@@ -113,12 +115,13 @@ public class FileSystemFile
|
||||
@Override
|
||||
public int getPermissions()
|
||||
throws IOException {
|
||||
if (isDirectory())
|
||||
if (isDirectory()) {
|
||||
return 0755;
|
||||
else if (isFile())
|
||||
} else if (isFile()) {
|
||||
return 0644;
|
||||
else
|
||||
} else {
|
||||
throw new IOException("Unsupported file type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,8 +133,9 @@ public class FileSystemFile
|
||||
@Override
|
||||
public void setLastModifiedTime(long t)
|
||||
throws IOException {
|
||||
if (!file.setLastModified(t * 1000))
|
||||
if (!file.setLastModified(t * 1000)) {
|
||||
log.warn("Could not set last modified time for {} to {}", file, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,22 +147,41 @@ public class FileSystemFile
|
||||
!(FilePermission.OTH_W.isIn(perms) || FilePermission.GRP_W.isIn(perms)));
|
||||
final boolean x = file.setExecutable(FilePermission.USR_X.isIn(perms),
|
||||
!(FilePermission.OTH_X.isIn(perms) || FilePermission.GRP_X.isIn(perms)));
|
||||
if (!(r && w && x))
|
||||
if (!(r && w && x)) {
|
||||
log.warn("Could not set permissions for {} to {}", file, Integer.toString(perms, 16));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemFile getChild(String name) {
|
||||
validateIsChildPath(name);
|
||||
return new FileSystemFile(new File(file, name));
|
||||
}
|
||||
|
||||
private void validateIsChildPath(String name) {
|
||||
String[] split = name.split("/");
|
||||
Stack<String> s = new Stack<String>();
|
||||
for (String component : split) {
|
||||
if (component == null || component.isEmpty() || ".".equals(component)) {
|
||||
continue;
|
||||
} else if ("..".equals(component) && !s.isEmpty()) {
|
||||
s.pop();
|
||||
continue;
|
||||
} else if ("..".equals(component)) {
|
||||
throw new IllegalArgumentException("Cannot traverse higher than " + file + " to get child " + name);
|
||||
}
|
||||
s.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemFile getTargetFile(String filename)
|
||||
throws IOException {
|
||||
FileSystemFile f = this;
|
||||
|
||||
if (f.isDirectory())
|
||||
if (f.isDirectory()) {
|
||||
f = f.getChild(filename);
|
||||
}
|
||||
|
||||
if (!f.getFile().exists()) {
|
||||
if (!f.getFile().createNewFile())
|
||||
@@ -174,12 +197,15 @@ public class FileSystemFile
|
||||
throws IOException {
|
||||
FileSystemFile f = this;
|
||||
|
||||
if (f.getFile().exists())
|
||||
if (f.getFile().exists()) {
|
||||
if (f.isDirectory()) {
|
||||
if (!f.getName().equals(dirname))
|
||||
if (!f.getName().equals(dirname)) {
|
||||
f = f.getChild(dirname);
|
||||
} else
|
||||
}
|
||||
} else {
|
||||
throw new IOException(f + " - already exists as a file; directory required");
|
||||
}
|
||||
}
|
||||
|
||||
if (!f.getFile().exists() && !f.getFile().mkdir())
|
||||
throw new IOException("Failed to create directory: " + f);
|
||||
|
||||
@@ -75,9 +75,9 @@ public class SCPDownloadClient extends AbstractSCPClient {
|
||||
engine.signal("Start status OK");
|
||||
|
||||
String msg = engine.readMessage();
|
||||
do
|
||||
do {
|
||||
process(engine.getTransferListener(), null, msg, targetFile);
|
||||
while (!(msg = engine.readMessage()).isEmpty());
|
||||
} while (!(msg = engine.readMessage()).isEmpty());
|
||||
}
|
||||
|
||||
private long parseLong(String longString, String valType)
|
||||
@@ -93,15 +93,17 @@ public class SCPDownloadClient extends AbstractSCPClient {
|
||||
|
||||
private int parsePermissions(String cmd)
|
||||
throws SCPException {
|
||||
if (cmd.length() != 5)
|
||||
if (cmd.length() != 5) {
|
||||
throw new SCPException("Could not parse permissions from `" + cmd + "`");
|
||||
}
|
||||
return Integer.parseInt(cmd.substring(1), 8);
|
||||
}
|
||||
|
||||
private boolean process(TransferListener listener, String bufferedTMsg, String msg, LocalDestFile f)
|
||||
throws IOException {
|
||||
if (msg.length() < 1)
|
||||
if (msg.length() < 1) {
|
||||
throw new SCPException("Could not parse message `" + msg + "`");
|
||||
}
|
||||
|
||||
switch (msg.charAt(0)) {
|
||||
|
||||
@@ -139,8 +141,9 @@ public class SCPDownloadClient extends AbstractSCPClient {
|
||||
final List<String> dMsgParts = tokenize(dMsg, 3, true); // D<perms> 0 <dirname>
|
||||
final long length = parseLong(dMsgParts.get(1), "dir length");
|
||||
final String dirname = dMsgParts.get(2);
|
||||
if (length != 0)
|
||||
if (length != 0) {
|
||||
throw new IOException("Remote SCP command sent strange directory length: " + length);
|
||||
}
|
||||
|
||||
final TransferListener dirListener = listener.directory(dirname);
|
||||
{
|
||||
@@ -186,9 +189,9 @@ public class SCPDownloadClient extends AbstractSCPClient {
|
||||
private static List<String> tokenize(String msg, int totalParts, boolean consolidateTail)
|
||||
throws IOException {
|
||||
List<String> parts = Arrays.asList(msg.split(" "));
|
||||
if (parts.size() < totalParts ||
|
||||
(!consolidateTail && parts.size() != totalParts))
|
||||
if (parts.size() < totalParts || (!consolidateTail && parts.size() != totalParts)) {
|
||||
throw new IOException("Could not parse message received from remote SCP: " + msg);
|
||||
}
|
||||
|
||||
if (consolidateTail && totalParts < parts.size()) {
|
||||
final StringBuilder sb = new StringBuilder(parts.get(totalParts - 1));
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/** @see <a href="http://blogs.sun.com/janp/entry/how_the_scp_protocol_works">SCP Protocol</a> */
|
||||
/** @see <a href="https://blogs.oracle.com/janp/entry/how_the_scp_protocol_works">SCP Protocol</a> */
|
||||
class SCPEngine {
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class SCPEngine {
|
||||
|
||||
void sendMessage(String msg) throws IOException {
|
||||
log.debug("Sending message: {}", msg);
|
||||
scp.getOutputStream().write((msg + LF).getBytes(IOUtils.UTF8));
|
||||
scp.getOutputStream().write((msg + LF).getBytes(scp.getRemoteCharset()));
|
||||
scp.getOutputStream().flush();
|
||||
check("Message ACK received");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 com.hierynomus.sshj.transport.verification
|
||||
|
||||
import com.hierynomus.sshj.transport.verification.KnownHostMatchers
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
class KnownHostMatchersSpec extends Specification {
|
||||
|
||||
@Unroll
|
||||
def "should #yesno match host #host with pattern #pattern"() {
|
||||
given:
|
||||
def matcher = KnownHostMatchers.createMatcher(pattern)
|
||||
|
||||
expect:
|
||||
match == matcher.match(host)
|
||||
|
||||
where:
|
||||
pattern | host | match
|
||||
"aaa.bbb.com" | "aaa.bbb.com" | true
|
||||
"aaa.bbb.com" | "aaa.ccc.com" | false
|
||||
"*.bbb.com" | "aaa.bbb.com" | true
|
||||
"*.bbb.com" | "aaa.ccc.com" | false
|
||||
"aaa.*.com" | "aaa.bbb.com" | true
|
||||
"aaa.*.com" | "aaa.ccc.com" | true
|
||||
"aaa.bbb.*" | "aaa.bbb.com" | true
|
||||
"aaa.bbb.*" | "aaa.ccc.com" | false
|
||||
"!*.bbb.com" | "aaa.bbb.com" | false
|
||||
"!*.bbb.com" | "aaa.ccc.com" | true
|
||||
"aaa.bbb.com,!*.ccc.com" | "xxx.yyy.com" | true
|
||||
"aaa.bbb.com,!*.ccc.com" | "aaa.bbb.com" | true
|
||||
"aaa.bbb.com,!*.ccc.com" | "aaa.ccc.com" | false
|
||||
"aaa.b??.com" | "aaa.bbb.com" | true
|
||||
"aaa.b??.com" | "aaa.bcd.com" | true
|
||||
"aaa.b??.com" | "aaa.ccd.com" | false
|
||||
"aaa.b??.com" | "aaa.bccd.com" | false
|
||||
"|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg=" | "192.168.1.61" | true
|
||||
"|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg=" | "192.168.2.61" | false
|
||||
yesno = match ? "" : "no"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.xfer
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class FileSystemFileSpec extends Specification {
|
||||
|
||||
def "should get child path"() {
|
||||
given:
|
||||
def file = new FileSystemFile("foo")
|
||||
|
||||
when:
|
||||
def child = file.getChild("bar")
|
||||
|
||||
then:
|
||||
child.getName() == "bar"
|
||||
}
|
||||
|
||||
def "should not traverse higher than original path when getChild is called"() {
|
||||
given:
|
||||
def file = new FileSystemFile("foo")
|
||||
|
||||
when:
|
||||
file.getChild("bar/.././foo/../../")
|
||||
|
||||
then:
|
||||
thrown(IllegalArgumentException.class)
|
||||
}
|
||||
|
||||
def "should ignore double slash (empty path component)"() {
|
||||
given:
|
||||
def file = new FileSystemFile("foo")
|
||||
|
||||
when:
|
||||
def child = file.getChild("bar//etc/passwd")
|
||||
|
||||
then:
|
||||
child.getFile().getPath() endsWith "foo/bar/etc/passwd"
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class FileUtil {
|
||||
FileInputStream fileInputStream = new FileInputStream(f);
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = IOUtils.readFully(fileInputStream);
|
||||
return byteArrayOutputStream.toString("UTF-8");
|
||||
return byteArrayOutputStream.toString(IOUtils.UTF8.displayName());
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fileInputStream);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||
import net.schmizz.sshj.common.Buffer.PlainBuffer;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BufferTest {
|
||||
|
||||
@@ -44,4 +48,103 @@ public class BufferTest {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowOnPutNegativeLongUInt64() {
|
||||
try {
|
||||
new PlainBuffer().putUInt64(-1l);
|
||||
fail("Added negative uint64 to buffer?");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowOnReadNegativeLongUInt64() {
|
||||
byte[] negativeLong = new byte[] { (byte) 0x80,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x01 };
|
||||
Buffer<?> buff = new PlainBuffer(negativeLong);
|
||||
|
||||
try {
|
||||
buff.readUInt64();
|
||||
fail("Read negative uint64 from buffer?");
|
||||
} catch (BufferException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowOnPutNegativeBigIntegerUInt64() {
|
||||
try {
|
||||
new PlainBuffer().putUInt64(new BigInteger("-1"));
|
||||
fail("Added negative uint64 to buffer?");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHaveCorrectValueForMaxUInt64() {
|
||||
byte[] maxUInt64InBytes = new byte[] { (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF };
|
||||
BigInteger maxUInt64 = new BigInteger(1, maxUInt64InBytes);
|
||||
new PlainBuffer().putUInt64(maxUInt64); // no exception
|
||||
|
||||
BigInteger tooBig = maxUInt64.add(BigInteger.ONE);
|
||||
try {
|
||||
new PlainBuffer().putUInt64(tooBig);
|
||||
fail("Added 2^64 (too big) as uint64 to buffer?");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCorrectlyEncodeAndDecodeUInt64Types() throws BufferException {
|
||||
// This number fits into a unsigned 64 bit integer but not a signed one.
|
||||
BigInteger bigUint64 = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).add(BigInteger.ONE);
|
||||
assertEquals(0x8000000000000001l, bigUint64.longValue());
|
||||
|
||||
Buffer<PlainBuffer> buff = new PlainBuffer();
|
||||
buff.putUInt64(bigUint64);
|
||||
byte[] data = buff.getCompactData();
|
||||
assertEquals(8, data.length);
|
||||
assertEquals((byte) 0x80, data[0]);
|
||||
assertEquals((byte) 0x00, data[1]);
|
||||
assertEquals((byte) 0x00, data[2]);
|
||||
assertEquals((byte) 0x00, data[3]);
|
||||
assertEquals((byte) 0x00, data[4]);
|
||||
assertEquals((byte) 0x00, data[5]);
|
||||
assertEquals((byte) 0x00, data[6]);
|
||||
assertEquals((byte) 0x01, data[7]);
|
||||
|
||||
byte[] asBinary = new byte[] { (byte) 0x80,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x00,
|
||||
(byte) 0x01 };
|
||||
buff = new PlainBuffer(asBinary);
|
||||
assertEquals(bigUint64, buff.readUInt64AsBigInteger());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHaveSameUInt64EncodingForBigIntegerAndLong() {
|
||||
long[] values = { 0l, 1l, 232634978082517765l, Long.MAX_VALUE - 1, Long.MAX_VALUE };
|
||||
for (long value : values) {
|
||||
byte[] bytesBigInt = new PlainBuffer().putUInt64(BigInteger.valueOf(value)).getCompactData();
|
||||
byte[] bytesLong = new PlainBuffer().putUInt64(value).getCompactData();
|
||||
assertArrayEquals("Value: " + value, bytesLong, bytesBigInt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,10 @@ public class KeyProviderUtilTest {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs8-blanks"));
|
||||
assertEquals(KeyFormat.PKCS8, format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenSshSigned() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "signed"));
|
||||
assertEquals(KeyFormat.OpenSSH, format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,32 +15,44 @@
|
||||
*/
|
||||
package net.schmizz.sshj.keyprovider;
|
||||
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import net.schmizz.sshj.util.KeyUtil;
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import net.schmizz.sshj.util.KeyUtil;
|
||||
|
||||
public class OpenSSHKeyFileTest {
|
||||
|
||||
@@ -134,7 +146,7 @@ public class OpenSSHKeyFileTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHaveCorrectFingerprintForECDSA() throws IOException, GeneralSecurityException {
|
||||
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException, GeneralSecurityException {
|
||||
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
|
||||
String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n";
|
||||
@@ -143,6 +155,26 @@ public class OpenSSHKeyFileTest {
|
||||
assertThat(expected, containsString(sshjFingerprintSshjKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException, GeneralSecurityException {
|
||||
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384"));
|
||||
String expected = "384 MD5:ee:9b:82:d1:47:01:16:1b:27:da:f5:27:fd:b2:eb:e2";
|
||||
PublicKey aPublic = keyFile.getPublic();
|
||||
String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic);
|
||||
assertThat(expected, containsString(sshjFingerprintSshjKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException, GeneralSecurityException {
|
||||
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521"));
|
||||
String expected = "521 MD5:22:e2:f4:3c:61:ae:e9:85:a1:4d:d9:6c:13:aa:eb:00";
|
||||
PublicKey aPublic = keyFile.getPublic();
|
||||
String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic);
|
||||
assertThat(expected, containsString(sshjFingerprintSshjKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHaveCorrectFingerprintForED25519() throws IOException {
|
||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||
@@ -161,6 +193,68 @@ public class OpenSSHKeyFileTest {
|
||||
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSuccessfullyLoadSignedRSAPublicKey() throws IOException {
|
||||
FileKeyProvider keyFile = new OpenSSHKeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/certificate/test_rsa"),
|
||||
PasswordUtils.createOneOff(correctPassphrase));
|
||||
assertNotNull(keyFile.getPrivate());
|
||||
PublicKey pubKey = keyFile.getPublic();
|
||||
assertEquals("RSA", pubKey.getAlgorithm());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Certificate<RSAPublicKey> certificate = (Certificate<RSAPublicKey>) pubKey;
|
||||
|
||||
assertEquals(new BigInteger("9223372036854775809"), certificate.getSerial());
|
||||
assertEquals("testrsa", certificate.getId());
|
||||
|
||||
assertEquals(2, certificate.getValidPrincipals().size());
|
||||
assertTrue(certificate.getValidPrincipals().contains("jeroen"));
|
||||
assertTrue(certificate.getValidPrincipals().contains("nobody"));
|
||||
|
||||
assertEquals(parseDate("2017-04-11 17:38:00 -0400"), certificate.getValidAfter());
|
||||
assertEquals(parseDate("2017-04-11 18:09:27 -0400"), certificate.getValidBefore());
|
||||
|
||||
assertEquals(0, certificate.getCritOptions().size());
|
||||
|
||||
Map<String, String> extensions = certificate.getExtensions();
|
||||
assertEquals(5, extensions.size());
|
||||
assertEquals("", extensions.get("permit-X11-forwarding"));
|
||||
assertEquals("", extensions.get("permit-agent-forwarding"));
|
||||
assertEquals("", extensions.get("permit-port-forwarding"));
|
||||
assertEquals("", extensions.get("permit-pty"));
|
||||
assertEquals("", extensions.get("permit-user-rc"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSuccessfullyLoadSignedDSAPublicKey() throws IOException {
|
||||
FileKeyProvider keyFile = new OpenSSHKeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/certificate/test_dsa"),
|
||||
PasswordUtils.createOneOff(correctPassphrase));
|
||||
assertNotNull(keyFile.getPrivate());
|
||||
PublicKey pubKey = keyFile.getPublic();
|
||||
assertEquals("DSA", pubKey.getAlgorithm());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Certificate<RSAPublicKey> certificate = (Certificate<RSAPublicKey>) pubKey;
|
||||
|
||||
assertEquals(new BigInteger("123"), certificate.getSerial());
|
||||
assertEquals("testdsa", certificate.getId());
|
||||
|
||||
assertEquals(1, certificate.getValidPrincipals().size());
|
||||
assertTrue(certificate.getValidPrincipals().contains("jeroen"));
|
||||
|
||||
assertEquals(parseDate("2017-04-11 17:37:00 -0400"), certificate.getValidAfter());
|
||||
assertEquals(parseDate("2017-04-12 03:38:49 -0400"), certificate.getValidBefore());
|
||||
|
||||
assertEquals(1, certificate.getCritOptions().size());
|
||||
assertEquals("10.0.0.0/8", certificate.getCritOptions().get("source-address"));
|
||||
|
||||
assertEquals(1, certificate.getExtensions().size());
|
||||
assertEquals("", certificate.getExtensions().get("permit-pty"));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
@@ -182,4 +276,13 @@ public class OpenSSHKeyFileTest {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Date parseDate(String date) {
|
||||
DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
|
||||
try {
|
||||
return f.parse(date);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.signature;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||
|
||||
public class VerificationTest {
|
||||
|
||||
@Test
|
||||
public void testECDSA256Verifies() throws BufferException {
|
||||
byte[] K_S = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, -8, 35, 96, -97, 65, -33, -128, -58, -64, -73, -51, 10, -28, 20, -59, 86, -88, -24, 126, 29, 115, 26, -88, 31, -115, 87, -109, -4, 61, 108, 28, 31, -66, 79, 107, 17, 24, 93, 114, -25, 121, 57, -58, 10, 26, -36, -100, -120, -7, -103, 86, 72, -109, -82, 111, 73, 4, -98, 58, 28, -3, -91, 28, 84");
|
||||
byte[] H = fromString("61, 55, -62, -122, -93, 82, -63, 25, -52, -13, -41, -29, 78, 101, 22, -75, 113, 59, -72, -92, -2, 39, -52, -89, 127, 80, -77, -82, 67, 3, -21, -53");
|
||||
byte[] sig = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 73, 0, 0, 0, 33, 0, -19, 50, -123, -35, 93, 50, 3, 40, -79, 110, -99, 6, -78, 40, -31, -26, -119, 113, -101, 109, -27, 12, 47, -119, -83, 107, -7, 116, 2, 97, 84, 32, 0, 0, 0, 32, 69, -44, 52, -119, 22, -60, -33, -105, -41, 45, 36, 112, -59, 49, -90, -110, -13, -114, 115, -86, 29, 30, 127, -44, 96, 57, -49, 39, -83, 50, -8, 123");
|
||||
|
||||
PublicKey hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
|
||||
Signature signature = new SignatureECDSA.Factory256().create();
|
||||
signature.init(hostKey, null);
|
||||
signature.update(H, 0, H.length);
|
||||
|
||||
Assert.assertTrue("ECDSA256 signature verifies", signature.verify(sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECDSA384Verifies() throws BufferException {
|
||||
byte[] K_S = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 51, 56, 52, 0, 0, 0, 8, 110, 105, 115, 116, 112, 51, 56, 52, 0, 0, 0, 97, 4, 105, 52, -67, 89, 21, 53, -125, -26, -23, -125, 48, 119, -63, -66, 30, -46, -110, 21, 14, -96, -28, 40, -108, 60, 120, 110, 58, 30, -56, 37, 6, -17, -25, 109, 84, 67, -19, 0, -30, -33, 54, 94, -121, 49, 68, -66, 14, 6, 76, -51, 102, -123, 59, -24, -34, 79, -51, 64, -48, -45, 21, -42, -96, -123, -27, -21, 15, 56, -96, -12, 73, -10, 113, -20, -22, 38, 100, 38, -85, -113, 46, 36, 17, -30, 89, 40, 16, 104, 123, 50, 8, 122, 49, -41, -97, 95");
|
||||
byte[] H = fromString("-46, 22, -52, 62, -100, -43, -68, -88, 98, 31, 116, -77, 27, -92, 127, 25, -43, -63, -42, -106, -53, 26, -61, 69, -38, -73, 94, -70, -99, -6, -78, 61");
|
||||
byte[] sig = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 51, 56, 52, 0, 0, 0, 105, 0, 0, 0, 48, 58, -5, -53, 17, -127, -32, 74, 123, -84, -1, 80, 96, 49, -77, -109, 22, -90, 115, -111, 40, 2, 4, 56, 51, 92, -30, 39, -61, -92, -76, -105, 45, 52, -31, 116, 44, -32, -65, 57, 44, 26, 45, 59, -115, 95, 113, 114, -89, 0, 0, 0, 49, 0, -56, 65, 59, 111, -26, -72, 127, 47, -15, 14, -34, 56, 5, 34, 28, -78, -13, 26, -22, -41, -86, -36, -112, 10, 91, 48, -77, -84, 93, 111, -84, 59, 42, -128, -22, 91, -4, -31, -89, -37, 107, -27, 28, -119, -36, 93, 25, -49");
|
||||
|
||||
PublicKey hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
|
||||
Signature signature = new SignatureECDSA.Factory384().create();
|
||||
signature.init(hostKey, null);
|
||||
signature.update(H, 0, H.length);
|
||||
|
||||
Assert.assertTrue("ECDSA384 signature verifies", signature.verify(sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECDSA521Verifies() throws BufferException {
|
||||
byte[] K_S = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 53, 50, 49, 0, 0, 0, 8, 110, 105, 115, 116, 112, 53, 50, 49, 0, 0, 0, -123, 4, 1, -56, 55, 64, -73, -109, 95, 94, -107, -116, -46, -16, 119, -66, -68, 41, -103, -66, 102, -123, -69, 59, -8, 106, 72, 75, 7, 56, -79, 109, -88, 77, 12, -97, -109, -32, -60, 64, -75, -48, 50, -51, -68, -81, 75, 110, -7, -79, -32, -36, -73, -7, -65, -24, 40, -74, 58, 43, -26, -5, -55, 125, -32, -89, -54, -111, 0, 81, 37, -73, 60, 69, 107, -108, 115, 60, -61, 22, 6, -128, -69, -28, 122, -26, -37, -117, 121, -106, -126, 23, -90, 127, 73, -58, -113, -61, 105, 68, 116, 85, -115, -47, 90, 122, 109, -21, 127, 39, -75, -58, -109, 73, -82, -122, -11, -44, -87, 85, -100, -4, -123, -31, 126, -94, 127, 96, 9, -30, 70, -113, -42, 28");
|
||||
byte[] H = fromString("-36, -107, -95, 2, 93, -111, -19, -107, 118, -7, 116, -33, 58, -90, -63, -60, -5, -23, 7, 56, -128, -22, -15, 26, -97, 2, 50, -93, 21, -21, 69, 105");
|
||||
byte[] sig = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 53, 50, 49, 0, 0, 0, -117, 0, 0, 0, 66, 1, 31, -111, 69, -37, 33, 24, -95, 53, -124, -33, 41, 65, -96, -112, -102, -33, 123, 30, -108, 102, 127, -27, 72, 101, -108, -123, 6, 107, 83, -72, -121, 87, -86, 75, 114, 50, -60, -75, -46, 7, -63, 84, -114, -91, 113, 52, 26, 102, -11, 76, 99, 9, 19, -73, -42, -3, 57, 41, -42, 13, -81, 18, -3, -49, -50, 0, 0, 0, 65, 102, 60, -2, 123, 91, -8, 120, 42, 118, 118, -9, -112, 72, 8, 61, -49, -45, 63, 112, 61, -55, -122, -109, 4, -39, 95, 3, -4, -43, 98, 39, 4, 63, 78, 78, 51, 77, 75, -23, 19, -46, 117, -115, -95, 90, -43, 108, -47, -90, 84, 98, 50, -97, -37, -14, -115, -76, 14, -61, 91, 107, 23, -112, 22, -15");
|
||||
|
||||
PublicKey hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
|
||||
Signature signature = new SignatureECDSA.Factory521().create();
|
||||
signature.init(hostKey, null);
|
||||
signature.update(H, 0, H.length);
|
||||
|
||||
Assert.assertTrue("ECDSA521 signature verifies", signature.verify(sig));
|
||||
}
|
||||
|
||||
private byte[] fromString(String string) {
|
||||
String[] split = string.split(", ");
|
||||
byte[] res = new byte[split.length];
|
||||
|
||||
for (int i = 0; i < split.length; i++)
|
||||
res[i] = Byte.parseByte(split[i]);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.password;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.Console;
|
||||
|
||||
public class TestConsolePasswordFinder {
|
||||
|
||||
private static final String FORMAT = "%s";
|
||||
|
||||
@Test
|
||||
public void testReqPassword() {
|
||||
char[] expectedPassword = "password".toCharArray();
|
||||
|
||||
Console console = Mockito.mock(Console.class);
|
||||
Mockito.when(console.readPassword(Mockito.anyString(), Mockito.any()))
|
||||
.thenReturn(expectedPassword);
|
||||
|
||||
Resource resource = Mockito.mock(Resource.class);
|
||||
char[] password = new ConsolePasswordFinder(console).reqPassword(resource);
|
||||
|
||||
Assert.assertArrayEquals("Password should match mocked return value",
|
||||
expectedPassword, password);
|
||||
Mockito.verifyNoMoreInteractions(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReqPasswordNullConsole() {
|
||||
Resource<?> resource = Mockito.mock(Resource.class);
|
||||
char[] password = new ConsolePasswordFinder(null, FORMAT, 1).reqPassword(resource);
|
||||
|
||||
Assert.assertNull("Password should be null with null console", password);
|
||||
Mockito.verifyNoMoreInteractions(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldRetry() {
|
||||
Resource<String> resource = new PrivateKeyStringResource("");
|
||||
ConsolePasswordFinder finder = new ConsolePasswordFinder(null, FORMAT, 1);
|
||||
Assert.assertTrue("Should allow a retry at first", finder.shouldRetry(resource));
|
||||
|
||||
finder.reqPassword(resource);
|
||||
Assert.assertFalse("Should stop allowing retries after one interaction", finder.shouldRetry(resource));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPromptFormat() {
|
||||
Assert.assertNotNull(
|
||||
"Empty format should create valid ConsolePasswordFinder",
|
||||
new ConsolePasswordFinder(null, "", 1));
|
||||
Assert.assertNotNull(
|
||||
"Single-string format should create valid ConsolePasswordFinder",
|
||||
new ConsolePasswordFinder(null, FORMAT, 1));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testPromptFormatTooManyMarkers() {
|
||||
new ConsolePasswordFinder(null, "%s%s", 1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testPromptFormatWrongMarkerType() {
|
||||
new ConsolePasswordFinder(null, "%d", 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,8 @@
|
||||
package net.schmizz.sshj.util;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -51,7 +53,7 @@ public class BufferTest {
|
||||
public void setUp()
|
||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
// for position test
|
||||
byte[] data = "Hello".getBytes("UTF-8");
|
||||
byte[] data = "Hello".getBytes(IOUtils.UTF8);
|
||||
posBuf = new Buffer.PlainBuffer(data);
|
||||
handyBuf = new Buffer.PlainBuffer();
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@ package net.schmizz.sshj.util.gss;
|
||||
|
||||
import org.ietf.jgss.*;
|
||||
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||
@@ -34,7 +35,7 @@ public class BogusGSSContext
|
||||
private static final byte[] MIC = fromString("LGTM");
|
||||
|
||||
private static byte[] fromString(String s) {
|
||||
return s.getBytes(Charset.forName("UTF-8"));
|
||||
return s.getBytes(IOUtils.UTF8);
|
||||
}
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
30
src/test/resources/keyformats/signed
Normal file
30
src/test/resources/keyformats/signed
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,DEF5F558DB233F512527D38775CF90A6
|
||||
|
||||
EQb59FGxEYNaLoYZ1GRtLdQVYe3DtQyAq42OvJyPF2xFpiV+U63TDzHBeSJnf4yK
|
||||
FWcWRbmFM5XL5jpuw7oUtg+bFOYsSjRMTGNpxcXDoByfRubLb3RPMlmVCENcwTXa
|
||||
pF1QuKQYj2+DXRam5y2w7A6rznd5lFDRM57kApGSMcrWwNz2WDyvuqlPTo6Wsj1j
|
||||
SWOQb9Te/ww1t8iEHryAITzUSRhbZG2epGh85QvuKhBebBd9TNRZwqwaZPx+j87/
|
||||
JGvq2RzttIydciLRBx3kYFI7JV1TGTbe+Hd10Yis9jBttqmEpB9Zyoug1Lubg3E2
|
||||
s45jk0CVAFp+44dKhk6K0uX4cjhC9uok6cAGZ7DYMokxEfCiJc7zJJgLvbil2lvK
|
||||
fbewUoiXGLtCPaDe1UXhmkYXBL2BqrBa2PTYlB0JQzFn//9qWW6RVqLpltWSFcFT
|
||||
nGQpRKZLSQhHLn+90X4lAuolUlrpWqgREiGSHlIgihv8mz9uAbHWSvSQE3q0dKSb
|
||||
OgU1CDVsxd0mdkb6ZNeS1iT50uwCpwiUw4Cx9xZp0xdzjiN15ED8eLI/nDFCZXhA
|
||||
jA0AK/cPzlO/Vc3uoM8+3PUkiMKd4glJzWkkE9pEiPlTQ3xivxUM4wQq6pgrjT13
|
||||
YI5PH5FkGNXYEeNxGnL+VXrWnxItd7ZVctG+3p6OKr9ShDxfYfw7WHk2n2o/s9rP
|
||||
j8eSq0G/Dr5ZoaMipPX0aP/BXzZZVVsnFc0SmGfcuIGDug+hjs5OKcrvi6QteJ6u
|
||||
dlsZJUy1YYnc/7T43TMllnouCHQ4TN01JTJSFS0IuKUrDoXI2DBSf2nE4J+04Cno
|
||||
bC5WZCmThM2tWdFiqsRn4I5oZ8vEl6ffhzgwLs/8fJSwzwCwLraMSMWJMJibnG/8
|
||||
cn3/Mwzm6aDMpRqu7h6s2tDctdZJEdRcwjD5tdPg09CLsNvG7nfJWi8RL/PxSuC1
|
||||
m5KKK9rbXVRzg011QELrxTBUAGcH/YHEsOZNrIexyWG99eJ5Y3tEpyaxHVVIsT/o
|
||||
+bsC8SEhADWKmfQmzZz8UbUQLs5SOxa6mutxudzmvdLnGmHk8fsBO7MbxRyBjqau
|
||||
+o5/ClbNzwSUSQQ8W3dpEpU/7udYAtHjwIWwVk7lwUqe/s7p2G9f5LDegJfJAXiU
|
||||
zGetpnYYFd1n4xQs22UPHS0+RaFLsYszvSv+LUEpVJ+zIWSB5hp7OrWLiQpGnQVH
|
||||
YydQUrxt8AYnhdrBbsxk652XFhBZzzbA9AlEHLhiMXDIh7XamFNk/S6fVGgsACGo
|
||||
hu2Ui50lHIRgNKds2tp41G74Vgv20lu3htU0wN9nDwjxATu+sX/0IaWwZIj1iuFf
|
||||
8YK6P0yP5rWyzAfDQWnh15SHE6zAvKYwyZtG7OWBYmd3whyR+VGuzCKw2uLh6JBx
|
||||
5GdNScf5szD1KSxqWwfY5tVLSn/gsSgCAptp5tiIdazOID1OkuM24yYRWoCvxT52
|
||||
qlfG5LHIhYUCzOdRK9DDSgSBqXCaiN3VD3KLOBfeSKSoen0h+CsIz9kmL73liisM
|
||||
+V8knyffjXfb/yCtV1b2xD9bds2si49Mif4aS1SX6bnBT+A5z/f/N3BGrdtIaMFl
|
||||
-----END RSA PRIVATE KEY-----
|
||||
1
src/test/resources/keyformats/signed-cert.pub
Normal file
1
src/test/resources/keyformats/signed-cert.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3ZotKIydji2P4WBzJjEHH1+n0VBVEyeZDb1AHj4HbUoAAAADAQABAAABAQDu1ngN8Oe+N32oDQ9CSw59fEM4Xo2HaAM/LFZffSj6gwS+BrKVYcupZCoUsLjNaOXudeivrI7+MTtfpPeCXKfNw6Bhmh16OXDdkFuEraLotSkibqNleGPrJW5KB6OBgTv7bSx249oobPLZ9Xog2fO888lsCFMCLMuoiEQM1Y3ZDcoqVRGBSAjki/QLtqWFeouiYdfCdzk80TccxORqNanK6ePQ1pTcOR/OtGToXeJEqqxX51wdSvWjCHGXmjjuQSM/Eznb86LbQtG79koqL4tuDVYvtrnIXUX7/aXicVmCT8gNFxZBP3uX6OJLT0CCZol2F/mu6pq7JLbhhfRCv6/NgAAAAAAAAAEAAAABAAAADHVzZXJfamVyb2VucwAAABYAAAAHamVyb2VuMQAAAAdqZXJvZW4yAAAAAFjtLVAAAAAAWO00lQAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDZ0a09cp+hsK7F5eg4etwemWyJjA97LlSxAsWOxNNOskNi2FDrhYCpMejr1rdrB1yHy3UYlG0AG3bmzX10uyad17/AHlKvNMQh6rysbe6DDgdPOVQtZv5UCCuvPDnnUAn++SXqxYbvVFSK48BJXeRk2mKrnC2iWsnJCW81j9ICA2S4LEtYE0DiYRrM2UT9lUgmcwxLGqL6JkWbJTF+5KLyRvPGtb06Uo5KOdFlyjEVZ437j/wNTrn2tbQJaMT5qaNuh4Py8WV7aCnX73YfJHdrnCbhOP1MBX9nT+dpMsLYXnCi0K/LLCKJIWn8yNwxPmrUc0OkqOYQWbjGkKn2dt/3AAABDwAAAAdzc2gtcnNhAAABACvwMQl9HsmjpLEt2MMhwyyTcwYsy6lUNa0lWs/YJlR70e/EzriZZVwXGpNYnXW5KgXOA020bhXgpPijBqWOxOAoHyE1CpLAuMDpYAxjnlNy391GzcUrQM74nPeBuE1TGpatS314Gx+iv98EaPdlvtommgh/Ggsb48n9xNrOuonYXwgUOpjvPYlDWvB3klUea4qvhru/23xzKf7DuIGSffB++IANkm1+TJ8vskE44F/VvVo4CCKEY7HaRHTSvfQ4yaNJCEWg0KcFbI79ICbB3mpHRKeDaqjJj4+6858D4/SYrBPOoCr8QNWR/kvYjrWD5qvHZMLO6x+3lEDuh76MNbk= choover@ma-lt-choover
|
||||
15
src/test/resources/keytypes/certificate/test_dsa
Normal file
15
src/test/resources/keytypes/certificate/test_dsa
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN DSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,F30B96BBE72D14E7E408E43D484642E1
|
||||
|
||||
A+fs86aamKouxGIQLbJc8dF6wzUqXo5stco6SjGFA78mutESj6EWWnU49/JAyWmK
|
||||
t20qKnnOlRNVVGw8h+FoRR1ukA9hkcc4Yg8bvHw21B45bswd165gJPLoOsacocr+
|
||||
487GztRao44+bT5tzkS/pCo0ianjJpfmlRPr8tVkrM0lA9SYWuVjhzm20mKfW8Q0
|
||||
iQk4xZMDS01BZ2BM1cEs8YsR5xXwV84i9iS9Evr5J3+V3xhilZiNSiYLT2kqQ+u7
|
||||
ccqVgybUb7OF2nd1GDY75E3hY0V+pXjPzFn0Er2hK8o76W61s52baVr2xOTq/wmH
|
||||
Ra3FCzj8M7xagprOYsVqza7oLt6lOK4VJQzFntoCDNpAqZDL77vFJz+0E2ZI/+cG
|
||||
1HSt889w0obu6D1XorsBx+LuNJZqwMtwYQMbjr1fXvRLktM4E0gUfyFgeGYJqvl+
|
||||
4AV24MII/+D1K5pnA3Q0Ban+dpLUqH9dGd7dplol12gzSpRtLHRgjv/GggZUIUjh
|
||||
MBTGdLkMHjteph0VFxeNiahydV707Gz9oc35e1MzeAi8dDqPNM7T1XDLV6Tqm2+x
|
||||
j2KBlXpkVhHEJ3IvDxO+1g==
|
||||
-----END DSA PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguS/sL4az6garEmUIM9ELqcaEfRJlPICG3lGthEJsWwEAAACBAJuvlcNNkgWfJV8y4HWxzith+6EZLGfd3jYqfXmITYDW8830cAmU7xC/aXK5r0LaSKxpyW/G/Z1396j69yxN1R1EC4fDvcEjTzmkcxKTrm8FQzFew7rkpm5H6MZq7PfrA/ph1nrDNLzO8Pzbw4trrr5yHuh3pQHt/WBSz+PiZPULAAAAFQDyrWY0qq7RR2NOslTkL7iBRVihKwAAAIA68Qp50ZqMvlGbnQQdYlH5HG1+tTs56Arc1Dgau8npyH9kTb0txpdijIwXhbwRN2eq0KQ3iF1XOzJSI5nWH2ONaaxK3Da4o8yt7/PQUFBDYqDoE+59XGOsupVKwpFiRPJURigoT943XdYaTRP5lX9nztVb/lFqppG/hdH8iMVJVwAAAIAbgIwWCMroqlEdsXKyM3wzYEHj8jTPTdr00WpV/WBghYittcfV3Cwp8HNSe1OyiA240CMFVLaKGr9PdoqU/an9WHb1skTuz2UNBs8uKJY3pISAZXHiOkza07iwTUcvQTZS+CctIlL1kxj2LTkI/Auy+Eiup40lrbIm87F6+LwSXgAAAAAAAAB7AAAAAQAAAAd0ZXN0ZHNhAAAACgAAAAZqZXJvZW4AAAAAWO1MfAAAAABY7dmJAAAAJAAAAA5zb3VyY2UtYWRkcmVzcwAAAA4AAAAKMTAuMC4wLjAvOAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAABsgAAAAdzc2gtZHNzAAAAgQCVc14O81iWe8XfsffXLYwzGr39QrlPQum2WLr3TfRCT+yKkyu3sLBO+3d25XpD1U2MQ1RJ7aJGET0m3QrO3U4CZGV2Fl3e6aLVrzvppR+LHoZa+kOl1bHfiCJ6NLib2bYNa1lIJ5L74lwYK9ZZ85M0Sun0DbNyNOgUX3OcG7oZiQAAABUA1x8Y8ZPR70C/BtQDnjq5Lx+p/cMAAACAGcSOjhD111FJMmi2T1nqyAjMreZx7CfQyfmg+s3dsePm8qsBZNiGRUVgCen14293O1AcMiBsGxI2a2+Kb4V3VzuT64zucrw51ZuqXu79T+KXDdTifgNoNFVPg+sorXyuzc0Z33BTdY8NAkpjJbjhKCYs3FVa4ygGNDC9pyh7nHsAAACBAIxQmlDKKcu0chmM9WjDqSosKJxADRtLmD1Drv1ag2DF3cKFPH5xtFqSpF9bsKeuhIhWeXiwytfeeHufdvno2VTvBJkOFFj5b4jsB2s3GyyJT9Z9TWFYecIj3t8t3QLlmDnGr8cHwmHpcbZmPBnm1hM9DA9OWkQaJWHccsjwSLI0AAAANwAAAAdzc2gtZHNzAAAAKDyWWSqjzoS2RmaBcm1CXVU7ZIx4yg3SAJEGP+ss0Jw27PjDhe54OJ4= choover@ma-lt-choover
|
||||
30
src/test/resources/keytypes/certificate/test_rsa
Normal file
30
src/test/resources/keytypes/certificate/test_rsa
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,5F1AEDBE1E9E4D83EBD9C7B71CD84DB5
|
||||
|
||||
N8SuKdWK3dEA7K6dNodjE++qqXPpVXJlDhiJbrXm1vbs2u+hckzZflK9OQZU1SsY
|
||||
hs6ZyFg/jagdMNEKKrCEK60QMcMg1iPlkMvTwEvQmdWSWtMiTFes9Ec4njUbcbgN
|
||||
qHzixGfWjpTXCRq3QC2Bydq7+njvIEUeLIjTaCIXqwoZoMcBc0N6Aj529gbgDD/i
|
||||
JC0auaSfejSZQm4Ppn+OuO1JKgNkwsjoTrrZjbKHuayYQaioaR5OcAKgSSs5YDdo
|
||||
SJI+PDeIOVb6aMcXwYP5J7C9zzkZU1Ml3dGHk2JMs0Omu2gICRm3mXXmoHRkiBKj
|
||||
2h7EsghiV/Be4lkOF+T3HN19F4G44EIUj7DotbtqNPEFRZa+SSpN3OZmLJQSwBXD
|
||||
A0zek5BxDu7M4VfbLryxNWcHinLe8fBKLQJe3F2/c5ZbMA4Z9098FIL1xWOqvbzS
|
||||
SjiuWhIDxdYVeVe579mUlWjPrMF3NnD4BP9oQlYmbSTxL7B486Ckh7ACFzQhUXVV
|
||||
t2JNZC2FcxM2WU7cHABvGUQNeq/Y2pB5Xq8AmzdpdAs2VZLHtgOvmnPjfo+Q3s1Y
|
||||
rlRe5sCkyn0Ju4M09sMBIQfwszVKa3/l/ZVrG5LCOEHkF+35ex6Gz8ePRlm5jhnU
|
||||
ZQVuLjxbnjnDMaJ0c0IoW7s44cdEDkv3fDzfQHrPzM50oFUN5WOKKWFnT4V1DBLJ
|
||||
9YW8AcNL6bSlgZNkLntn5Wvl3BBKbf5SMmqegfR3eqz50KNwzKKQU8dcIwkJsK2s
|
||||
Z5fNWbydgNDZhrebJL/jpuXpXcvfO0cVMja7y/O0bQ6M37Arpk8bQhTBKZZSaXeF
|
||||
I0Bx3IOxsodWpA9O3Yum8fqqPGrCxsAsP/mWNG0Ov29Myi3MeZ0zgQH283En0Pnx
|
||||
8hvDlw8vNcFe22Jycgg4846FlIWzkT7VfDVxDoYCyjlMTZ2ASys8llH5in4i1w4m
|
||||
j0ZREJE/+evCXBHLoTO96Rgnjt7hp9g62FHJ+ivRlCQVY6sTB03GNv//4v7xGMPF
|
||||
S4eBB9gnGKEEe8zRcOPgUEhUOrFv9cUpdhYz/SLuTZIPPiNwkAMZ6zM77FMsrdWw
|
||||
18wiemOhizCd/JZDlH9COue6EXnr7rexmTp7rUsJOTq8q3rpYmz2s0rGJbqxyxlq
|
||||
BiCjJqN004ZmtFCTD0wCGuNsVLiNyWSspIfzWXkfCyO0SbiH8QjlkUZVr456/5sI
|
||||
cvCQ3ltAFfxR8wMZlfgtT3mEu8JAQCml89yMbttisdfz48XnLeXAzZuZzEr4OllN
|
||||
AMQ8RINlZC2fxqV25jJi+E1da0yvwM9x0NPaM/ZUQqGzMf+dGldcVWdlNFBsthx0
|
||||
wULaGfTYmmn6rBdbeGlt0JKGGL8Ak8+adNSHHxaJuL9W/5wLH41HsE89ZldlTHuI
|
||||
GeHM1lDA/DwO85c4Fk7Ai0Ny44PjdGn27pLNhAfoT2HivF7zMNV+SUXU57iL/qdR
|
||||
yJYGZJyXdreK4qaSCx7BdaK0AFvhBTXW/uRXJCX/XW6zdzdqy5/wkqWt1sWHKTxf
|
||||
Z0CBKWd/se6JCQoixG2Cpo87SeLZsJEscK8eDdLDQwJdKdmIqgw6LddF7RPpq/5D
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb0+Llm+3BKq6n5YoMepI1/Iqp4WnSFYYbpm9Y0jLbJMAAAADAQABAAABAQDF9lt1iAGZWGqNtKR05HRMSyRSG2li0Hq732U2yQ9MmWSAEwltBXILCUrTnkJOWIMYyKY4Y22K+aIWMmnJdE8sehfZ6d+gz20yZ8kHndB7CnKn6oxh6kW5NAGye3kDR808L836U2VxMMgpTH/lBi4909tKf6vAY2qoG/LW4ftFWcf0jU7U8lwlvJ4mjJ9Ld5ObaJOa1h3URHWpiXuSa/JFVTMVAz8Z7h6hXpwruv2HQlKJGs2axP71pGR6Vni2fDZ4kWn5dObv2OA5VLiTD+YZtQ0edAEKORyETaCDwgCgX4qNwdGE/A37c1B6K6tGZrWGjXaPACpOFjwjVVhEI8TPgAAAAAAAAAEAAAABAAAAB3Rlc3Ryc2EAAAAUAAAABmplcm9lbgAAAAZub2JvZHkAAAAAWO1MuAAAAABY7VQXAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBANnRrT1yn6GwrsXl6Dh63B6ZbImMD3suVLECxY7E006yQ2LYUOuFgKkx6OvWt2sHXIfLdRiUbQAbdubNfXS7Jp3Xv8AeUq80xCHqvKxt7oMOB085VC1m/lQIK688OedQCf75JerFhu9UVIrjwEld5GTaYqucLaJayckJbzWP0gIDZLgsS1gTQOJhGszZRP2VSCZzDEsaovomRZslMX7kovJG88a1vTpSjko50WXKMRVnjfuP/A1Oufa1tAloxPmpo26Hg/LxZXtoKdfvdh8kd2ucJuE4/UwFf2dP52kywthecKLQr8ssIokhafzI3DE+atRzQ6So5hBZuMaQqfZ23/cAAAEPAAAAB3NzaC1yc2EAAAEA0uyAwWCFNFEZXLuy1+lwd9VtgCcC50HKMyHywKzkyKlDIbsKbCLcQG74f0nDP7c+8jAFRCxLfaGZ55SYXkPoXMOPqFsln7YvidC7nlKhF2Q7FgOBd04SoriAvi1M24EAE+Y9LUz6idLEuegXt32cobv+fqVKRBZ0V6UpDc5W58/6lDk2YMNOIbQQNy34Lf1HqIhxaWC6pmnT3rPcZylvZI7wP58DdhInWoEVMZawZ0m0Py2C2Pb7zOwFY81nXucCHUfCL4UngN1QOLg9/0vwGZmUVkTnPsbeH6gcBoQY6XdjbPB8HP/a1Ugr2wsTyFJKdc1+5r/KtcCLiXe1SmmHQA== choover@ma-lt-choover
|
||||
1
src/test/resources/keytypes/certificate/test_rsa.pub
Normal file
1
src/test/resources/keytypes/certificate/test_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF9lt1iAGZWGqNtKR05HRMSyRSG2li0Hq732U2yQ9MmWSAEwltBXILCUrTnkJOWIMYyKY4Y22K+aIWMmnJdE8sehfZ6d+gz20yZ8kHndB7CnKn6oxh6kW5NAGye3kDR808L836U2VxMMgpTH/lBi4909tKf6vAY2qoG/LW4ftFWcf0jU7U8lwlvJ4mjJ9Ld5ObaJOa1h3URHWpiXuSa/JFVTMVAz8Z7h6hXpwruv2HQlKJGs2axP71pGR6Vni2fDZ4kWn5dObv2OA5VLiTD+YZtQ0edAEKORyETaCDwgCgX4qNwdGE/A37c1B6K6tGZrWGjXaPACpOFjwjVVhEI8TP choover@ma-lt-choover
|
||||
1
src/test/resources/keytypes/test_ecdsa_nistp384.pub
Normal file
1
src/test/resources/keytypes/test_ecdsa_nistp384.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBGk0vVkVNYPm6YMwd8G+HtKSFQ6g5CiUPHhuOh7IJQbv521UQ+0A4t82XocxRL4OBkzNZoU76N5PzUDQ0xXWoIXl6w84oPRJ9nHs6iZkJquPLiQR4lkoEGh7Mgh6MdefXw==
|
||||
1
src/test/resources/keytypes/test_ecdsa_nistp521.pub
Normal file
1
src/test/resources/keytypes/test_ecdsa_nistp521.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHIN0C3k19elYzS8He+vCmZvmaFuzv4akhLBzixbahNDJ+T4MRAtdAyzbyvS275seDct/m/6Ci2Oivm+8l94KfKkQBRJbc8RWuUczzDFgaAu+R65tuLeZaCF6Z/ScaPw2lEdFWN0Vp6bet/J7XGk0muhvXUqVWc/IXhfqJ/YAniRo/WHA==
|
||||
@@ -0,0 +1,2 @@
|
||||
# incubating feature to allow mocking final classes
|
||||
mock-maker-inline
|
||||
Reference in New Issue
Block a user