-
Notifications
You must be signed in to change notification settings - Fork 606
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #279 from hierynomus/issue-276
Support for OpenSSH new key file format (fixes #276)
- Loading branch information
Showing
12 changed files
with
344 additions
and
257 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/* | ||
* 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.userauth.keyprovider; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.security.GeneralSecurityException; | ||
import java.security.KeyPair; | ||
import java.security.PublicKey; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import net.i2p.crypto.eddsa.EdDSAPrivateKey; | ||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; | ||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; | ||
import net.schmizz.sshj.common.*; | ||
import net.schmizz.sshj.common.Buffer.PlainBuffer; | ||
import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider; | ||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; | ||
import net.schmizz.sshj.userauth.keyprovider.KeyFormat; | ||
|
||
import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.CURVE_ED25519_SHA512; | ||
|
||
/** | ||
* Reads a key file in the new OpenSSH format. | ||
* The format is described in the following document: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key | ||
*/ | ||
public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { | ||
private static final Logger logger = LoggerFactory.getLogger(OpenSSHKeyV1KeyFile.class); | ||
private static final String BEGIN = "-----BEGIN "; | ||
private static final String END = "-----END "; | ||
private static final byte[] AUTH_MAGIC = "openssh-key-v1\0".getBytes(); | ||
public static final String OPENSSH_PRIVATE_KEY = "OPENSSH PRIVATE KEY-----"; | ||
|
||
public static class Factory | ||
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> { | ||
|
||
@Override | ||
public FileKeyProvider create() { | ||
return new OpenSSHKeyV1KeyFile(); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return KeyFormat.OpenSSHv1.name(); | ||
} | ||
} | ||
|
||
@Override | ||
protected KeyPair readKeyPair() throws IOException { | ||
BufferedReader reader = new BufferedReader(resource.getReader()); | ||
try { | ||
if (!checkHeader(reader)) { | ||
throw new IOException("This key is not in 'openssh-key-v1' format"); | ||
} | ||
|
||
String keyFile = readKeyFile(reader); | ||
byte[] decode = Base64.decode(keyFile); | ||
PlainBuffer keyBuffer = new PlainBuffer(decode); | ||
return readDecodedKeyPair(keyBuffer); | ||
|
||
} catch (GeneralSecurityException e) { | ||
throw new SSHRuntimeException(e); | ||
} finally { | ||
IOUtils.closeQuietly(reader); | ||
} | ||
} | ||
|
||
private KeyPair readDecodedKeyPair(final PlainBuffer keyBuffer) throws IOException, GeneralSecurityException { | ||
byte[] bytes = new byte[AUTH_MAGIC.length]; | ||
keyBuffer.readRawBytes(bytes); // byte[] AUTH_MAGIC | ||
if (!ByteArrayUtils.equals(bytes, 0, AUTH_MAGIC, 0, AUTH_MAGIC.length)) { | ||
throw new IOException("This key does not contain the 'openssh-key-v1' format magic header"); | ||
} | ||
|
||
String cipherName = keyBuffer.readString(); // string ciphername | ||
String kdfName = keyBuffer.readString(); // string kdfname | ||
String kdfOptions = keyBuffer.readString(); // string kdfoptions | ||
|
||
int nrKeys = keyBuffer.readUInt32AsInt(); // int number of keys N; Should be 1 | ||
if (nrKeys != 1) { | ||
throw new IOException("We don't support having more than 1 key in the file (yet)."); | ||
} | ||
PublicKey publicKey = readPublicKey(new PlainBuffer(keyBuffer.readBytes())); // string publickey1 | ||
PlainBuffer privateKeyBuffer = new PlainBuffer(keyBuffer.readBytes()); // string (possibly) encrypted, padded list of private keys | ||
if ("none".equals(cipherName)) { | ||
logger.debug("Reading unencrypted keypair"); | ||
return readUnencrypted(privateKeyBuffer, publicKey); | ||
} else { | ||
logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + kdfOptions); | ||
throw new IOException("Cannot read encrypted keypair with " + cipherName + " yet."); | ||
} | ||
} | ||
|
||
private PublicKey readPublicKey(final PlainBuffer plainBuffer) throws Buffer.BufferException, GeneralSecurityException { | ||
return KeyType.fromString(plainBuffer.readString()).readPubKeyFromBuffer(plainBuffer); | ||
} | ||
|
||
private String readKeyFile(final BufferedReader reader) throws IOException { | ||
StringBuilder sb = new StringBuilder(); | ||
String line = reader.readLine(); | ||
while (!line.startsWith(END)) { | ||
sb.append(line); | ||
line = reader.readLine(); | ||
} | ||
return sb.toString(); | ||
} | ||
|
||
private boolean checkHeader(final BufferedReader reader) throws IOException { | ||
String line = reader.readLine(); | ||
while (line != null && !line.startsWith(BEGIN)) { | ||
line = reader.readLine(); | ||
} | ||
line = line.substring(BEGIN.length()); | ||
return line.startsWith(OPENSSH_PRIVATE_KEY); | ||
} | ||
|
||
private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey publicKey) throws IOException, GeneralSecurityException { | ||
int privKeyListSize = keyBuffer.available(); | ||
if (privKeyListSize % 8 != 0) { | ||
throw new IOException("The private key section must be a multiple of the block size (8)"); | ||
} | ||
int checkInt1 = keyBuffer.readUInt32AsInt(); // uint32 checkint1 | ||
int checkInt2 = keyBuffer.readUInt32AsInt(); // uint32 checkint2 | ||
if (checkInt1 != checkInt2) { | ||
throw new IOException("The checkInts differed, the key was not correctly decoded."); | ||
} | ||
// The private key section contains both the public key and the private key | ||
String keyType = keyBuffer.readString(); // string keytype | ||
logger.info("Read key type: {}", keyType); | ||
|
||
byte[] pubKey = keyBuffer.readBytes(); // string publickey (again...) | ||
keyBuffer.readUInt32(); | ||
byte[] privKey = new byte[32]; | ||
keyBuffer.readRawBytes(privKey); // string privatekey | ||
keyBuffer.readRawBytes(new byte[32]); // string publickey (again...) | ||
String comment = keyBuffer.readString(); // string comment | ||
byte[] padding = new byte[keyBuffer.available()]; | ||
keyBuffer.readRawBytes(padding); // char[] padding | ||
for (int i = 0; i < padding.length; i++) { | ||
if ((int) padding[i] != i + 1) { | ||
throw new IOException("Padding of key format contained wrong byte at position: " + i); | ||
} | ||
} | ||
return new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512)))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
src/main/java/net/schmizz/sshj/userauth/keyprovider/BaseFileKeyProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* Copyright (C)2009 - SSHJ Contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package net.schmizz.sshj.userauth.keyprovider; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.Reader; | ||
import java.security.KeyPair; | ||
import java.security.PrivateKey; | ||
import java.security.PublicKey; | ||
|
||
import net.schmizz.sshj.common.KeyType; | ||
import net.schmizz.sshj.userauth.password.*; | ||
|
||
public abstract class BaseFileKeyProvider implements FileKeyProvider { | ||
protected Resource<?> resource; | ||
protected PasswordFinder pwdf; | ||
protected KeyPair kp; | ||
|
||
protected KeyType type; | ||
|
||
@Override | ||
public void init(Reader location) { | ||
assert location != null; | ||
resource = new PrivateKeyReaderResource(location); | ||
} | ||
|
||
@Override | ||
public void init(Reader location, PasswordFinder pwdf) { | ||
init(location); | ||
this.pwdf = pwdf; | ||
} | ||
|
||
@Override | ||
public void init(File location) { | ||
assert location != null; | ||
resource = new PrivateKeyFileResource(location.getAbsoluteFile()); | ||
} | ||
|
||
@Override | ||
public void init(File location, PasswordFinder pwdf) { | ||
init(location); | ||
this.pwdf = pwdf; | ||
} | ||
|
||
@Override | ||
public void init(String privateKey, String publicKey) { | ||
assert privateKey != null; | ||
assert publicKey == null; | ||
resource = new PrivateKeyStringResource(privateKey); | ||
} | ||
|
||
@Override | ||
public void init(String privateKey, String publicKey, PasswordFinder pwdf) { | ||
init(privateKey, publicKey); | ||
this.pwdf = pwdf; | ||
} | ||
|
||
@Override | ||
public PrivateKey getPrivate() | ||
throws IOException { | ||
return kp != null ? kp.getPrivate() : (kp = readKeyPair()).getPrivate(); | ||
} | ||
|
||
@Override | ||
public PublicKey getPublic() | ||
throws IOException { | ||
return kp != null ? kp.getPublic() : (kp = readKeyPair()).getPublic(); | ||
} | ||
|
||
@Override | ||
public KeyType getType() | ||
throws IOException { | ||
return type != null ? type : (type = KeyType.fromKey(getPublic())); | ||
} | ||
|
||
|
||
protected abstract KeyPair readKeyPair() throws IOException; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ public enum KeyFormat { | |
PKCS5, | ||
PKCS8, | ||
OpenSSH, | ||
OpenSSHv1, | ||
PuTTY, | ||
Unknown | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.