/*
* Copyright 2010 netling project <http://netling.org>
*
* 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 org.netling.ssh.userauth.keyprovider;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.netling.ssh.common.Buffer;
import org.netling.ssh.common.KeyType;
import org.netling.ssh.userauth.password.PasswordFinder;
import org.netling.ssh.userauth.password.PrivateKeyFileResource;
import org.netling.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Source for the following documentation: putty 0.6.0 source / sshpubk.c
* -----------------------------------------------------------------------
*
* PuTTY's own format for SSH-2 keys is as follows:
*
* The file is text. Lines are terminated by CRLF, although CR-only
* and LF-only are tolerated on input.
*
* The first line says "PuTTY-User-Key-File-2: " plus the name of the
* algorithm ("ssh-dss", "ssh-rsa" etc).
*
* The next line says "Encryption: " plus an encryption type.
* Currently the only supported encryption types are "aes256-cbc"
* and "none".
*
* The next line says "Comment: " plus the comment string.
*
* Next there is a line saying "Public-Lines: " plus a number N.
* The following N lines contain a base64 encoding of the public
* part of the key. This is encoded as the standard SSH-2 public key
* blob (with no initial length): so for RSA, for example, it will
* read
*
* string "ssh-rsa"
* mpint exponent
* mpint modulus
*
* Next, there is a line saying "Private-Lines: " plus a number N,
* and then N lines containing the (potentially encrypted) private
* part of the key. For the key type "ssh-rsa", this will be
* composed of
*
* mpint private_exponent
* mpint p (the larger of the two primes)
* mpint q (the smaller prime)
* mpint iqmp (the inverse of q modulo p)
* data padding (to reach a multiple of the cipher block size)
*
* And for "ssh-dss", it will be composed of
*
* mpint x (the private key parameter)
* [ string hash 20-byte hash of mpints p || q || g only in old format ]
*
* Finally, there is a line saying "Private-MAC: " plus a hex
* representation of a HMAC-SHA-1 of:
*
* string name of algorithm ("ssh-dss", "ssh-rsa")
* string encryption type
* string comment
* string public-blob
* string private-plaintext (the plaintext version of the
* private part, including the final
* padding)
*
* The key to the MAC is itself a SHA-1 hash of:
*
* data "putty-private-key-file-mac-key"
* data passphrase
*
* (An empty passphrase is used for unencrypted keys.)
*
* If the key is encrypted, the encryption key is derived from the
* passphrase by means of a succession of SHA-1 hashes. Each hash
* is the hash of:
*
* uint32 sequence-number
* data passphrase
*
* where the sequence-number increases from zero. As many of these
* hashes are used as necessary.
*
* For backwards compatibility with snapshots between 0.51 and
* 0.52, we also support the older key file format, which begins
* with "PuTTY-User-Key-File-1" (version number differs). In this
* format the Private-MAC: field only covers the private-plaintext
* field and nothing else (and without the 4-byte string length on
* the front too). Moreover, the Private-MAC: field can be replaced
* with a Private-Hash: field which is a plain SHA-1 hash instead of
* an HMAC (this was generated for unencrypted keys).
*/
/**
* Represents a Putty (.ppk) keyfile.
*
* @see PKCS8KeyFile
*/
public class PuTTYKeyFile
implements FileKeyProvider {
private static final Logger logger = LoggerFactory.getLogger(PuTTYKeyFile.class);
public static class Factory
implements org.netling.ssh.common.Factory.Named<FileKeyProvider> {
@Override
public FileKeyProvider create() {
return new PuTTYKeyFile();
}
@Override
public String getName() {
return "PuTTY";
}
}
private final Logger log = LoggerFactory.getLogger(getClass());
private File location;
private PasswordFinder pwdf;
protected PrivateKeyFileResource resource;
private KeyType type;
private PublicKey pubKey;
private PrivateKey privKey;
@Override
public KeyType getType()
throws IOException {
if (type == null)
readTypeAndPublicKey();
return type;
}
@Override
public PublicKey getPublic()
throws IOException {
if (pubKey == null)
readTypeAndPublicKey();
return pubKey;
}
@Override
public PrivateKey getPrivate() {
if (privKey == null)
readPrivateKey();
return privKey;
}
@Override
public void init(File location) {
init(location, null);
}
@Override
public void init(File location, PasswordFinder pwdf) {
this.location = location;
this.pwdf = pwdf;
resource = new PrivateKeyFileResource(location.getAbsoluteFile());
}
private void readTypeAndPublicKey()
throws IOException {
final BufferedReader br = new BufferedReader(new FileReader(location));
try {
final String header = br.readLine();
final String encryption = br.readLine();
br.readLine(); // comment
final String pubKeyHeader = br.readLine();
final int pubLines = getHeaderValue(pubKeyHeader);
StringBuilder keyBuf = new StringBuilder();
for (int i = 0; i < pubLines; ++i) {
keyBuf.append(br.readLine());
}
String keyType = header.split(" ")[1].trim();
type = KeyType.fromString(keyType);
pubKey = new Buffer.PlainBuffer(Base64.decode(keyBuf.toString())).readPublicKey();
// Read the private key too
final String privKeyHeader = br.readLine();
final int privLines = getHeaderValue(privKeyHeader);
StringBuilder privKeyBuf = new StringBuilder();
for (int i = 0; i < privLines; ++i) {
privKeyBuf.append(br.readLine());
}
System.out.println(header);
System.out.println("Encryption=" + encryption);
System.out.println(privKeyBuf.toString());
Buffer privateKey = new Buffer.PlainBuffer(Base64.decode(privKeyBuf.toString()));
BigInteger privExponent = privateKey.readMPInt();
BigInteger p = privateKey.readMPInt();
BigInteger q = privateKey.readMPInt();
BigInteger iqmp = privateKey.readMPInt();
System.out.println("p=" + p + ",q=" + q);
final String mac = br.readLine().split(":")[1].trim();
System.out.println(mac);
} finally {
br.close();
}
}
int getHeaderValue(final String header) {
return Integer.valueOf(header.split(":")[1].trim());
}
private PrivateKey readPrivateKey() {
throw new RuntimeException("TODO");
}
}