/** * Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved. * * For product documentation visit https://www.sshtools.com/ * * This file is part of J2SSH Maverick. * * J2SSH Maverick is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * J2SSH Maverick is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with J2SSH Maverick. If not, see <http://www.gnu.org/licenses/>. */ package com.sshtools.publickey; import java.io.IOException; import java.math.BigInteger; import com.sshtools.ssh.SshException; import com.sshtools.ssh.SshIOException; import com.sshtools.ssh.components.ComponentManager; import com.sshtools.ssh.components.Digest; import com.sshtools.ssh.components.SshCipher; import com.sshtools.ssh.components.SshDsaPublicKey; import com.sshtools.ssh.components.SshKeyPair; import com.sshtools.ssh.components.jce.TripleDesCbc; import com.sshtools.util.ByteArrayReader; import com.sshtools.util.ByteArrayWriter; class SSHCOMPrivateKeyFile extends Base64EncodedFileFormat implements SshPrivateKeyFile { static String BEGIN = "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----"; static String END = "---- END SSH2 ENCRYPTED PRIVATE KEY ----"; byte[] formattedkey; SSHCOMPrivateKeyFile(byte[] formattedkey) throws IOException { super(BEGIN, END); if (!isFormatted(formattedkey)) { throw new IOException("Key is not formatted in the ssh.com format"); } this.formattedkey = formattedkey; } public String getType() { return "SSH Communications Security"; } public static boolean isFormatted(byte[] formattedkey) { return isFormatted(formattedkey, BEGIN, END); } public boolean supportsPassphraseChange() { return false; } public boolean isPassphraseProtected() { try { byte[] keyblob = getKeyBlob(formattedkey); ByteArrayReader bar = new ByteArrayReader(keyblob); try { long magic = bar.readInt(); if (magic != 0x3f6ff9eb) { throw new IOException( "Invalid ssh.com key! Magic number not found"); } bar.readInt(); bar.readString(); String cipher = bar.readString(); return cipher.equals("3des-cbc"); } finally { bar.close(); } } catch (IOException ex) { } return false; } public SshKeyPair toKeyPair(String passphrase) throws IOException, InvalidPassphraseException { byte[] keyblob = getKeyBlob(formattedkey); boolean wasEncrypted = false; ByteArrayReader bar = new ByteArrayReader(keyblob); try { long magic = bar.readInt(); if (magic != 0x3f6ff9eb) throw new IOException( "Invalid ssh.com key! Magic number not found"); bar.readInt(); String type = bar.readString(); String cipher = bar.readString(); byte[] blob = bar.readBinaryString(); if (!cipher.equals("none")) { if (!cipher.equals("3des-cbc")) { throw new IOException("Unsupported cipher type " + cipher + " in ssh.com private key"); } SshCipher c = new TripleDesCbc(); byte[] iv = new byte[32]; byte[] keydata = makePassphraseKey(passphrase); c.init(SshCipher.DECRYPT_MODE, iv, keydata); c.transform(blob); wasEncrypted = true; } ByteArrayReader data = new ByteArrayReader(blob, 4, blob.length - 4); try { if (type.startsWith("if-modn{sign{rsa")) { BigInteger e = data.readMPINT32(); BigInteger d = data.readMPINT32(); BigInteger n = data.readMPINT32(); BigInteger u = data.readMPINT32(); BigInteger p = data.readMPINT32(); BigInteger q = data.readMPINT32(); SshKeyPair pair = new SshKeyPair(); pair.setPublicKey(ComponentManager.getInstance() .createRsaPublicKey(n, e)); pair.setPrivateKey(ComponentManager.getInstance() .createRsaPrivateCrtKey(n, e, d, p, q, u)); return pair; } else if (type.startsWith("dl-modp{sign{dsa")) { long predefined = data.readInt(); if (predefined != 0) throw new IOException( "Unexpected value in DSA key; this is an unsupported feature of ssh.com private keys"); BigInteger p = data.readMPINT32(); BigInteger g = data.readMPINT32(); BigInteger q = data.readMPINT32(); BigInteger y = data.readMPINT32(); BigInteger x = data.readMPINT32(); SshKeyPair pair = new SshKeyPair(); SshDsaPublicKey pub = ComponentManager.getInstance() .createDsaPublicKey(p, q, g, y); pair.setPublicKey(pub); pair.setPrivateKey(ComponentManager.getInstance() .createDsaPrivateKey(p, q, g, x, pub.getY())); return pair; } else throw new IOException("Unsupported ssh.com key type " + type); } catch (Throwable t) { if (wasEncrypted) throw new InvalidPassphraseException(); throw new IOException("Bad SSH.com private key format!"); } finally { data.close(); } } finally { bar.close(); } } private byte[] makePassphraseKey(String passphrase) throws IOException { ByteArrayWriter baw = new ByteArrayWriter(); try { Digest hash = (Digest) ComponentManager.getInstance() .supportedDigests().getInstance("MD5"); hash.putBytes(passphrase.getBytes()); byte[] tmp = hash.doFinal(); hash.reset(); hash.putBytes(passphrase.getBytes()); hash.putBytes(tmp); baw.write(tmp); baw.write(hash.doFinal()); return baw.toByteArray(); } catch (SshException e) { throw new SshIOException(e); } finally { try { baw.close(); } catch (IOException e) { } } } public void changePassphrase(String oldpassphrase, String newpassprase) throws IOException { /** @todo Implement this com.sshtools.publickey.SshPrivateKeyFile method */ throw new IOException( "Changing passphrase is not supported by the ssh.com key format engine"); } public byte[] getFormattedKey() throws IOException { return formattedkey; } }