package org.bbssh.crypto; import java.io.IOException; import net.rim.device.api.crypto.CryptoException; import net.rim.device.api.crypto.CryptoInteger; import net.rim.device.api.crypto.CryptoTokenException; import net.rim.device.api.crypto.CryptoUnsupportedOperationException; import net.rim.device.api.crypto.DSACryptoSystem; import net.rim.device.api.crypto.DSAPrivateKey; import net.rim.device.api.crypto.DSAPublicKey; import net.rim.device.api.crypto.PKCS1SignatureVerifier; import net.rim.device.api.crypto.RSACryptoSystem; import net.rim.device.api.crypto.RSAPrivateKey; import net.rim.device.api.crypto.RSAPublicKey; import net.rim.device.api.crypto.SHA1Digest; import net.rim.device.api.io.Base64OutputStream; import org.bbssh.ssh.packets.SshPacket2; import org.bbssh.util.Tools; public class SignatureTools { public static byte[] encodePublicKey(RSAPrivateKey key) throws CryptoException { SshPacket2 p = new SshPacket2(); p.putString("ssh-rsa"); p.putMpInt(key.getE()); p.putMpInt(key.getN()); return p.getBytes(); } // @todo unfortunately we don't know that we'll be able to use DSAPrivateKey, // because RIM only supports up to 1024 bit keys... public static byte[] encodePublicKey(DSAPrivateKey key) throws CryptoException { SshPacket2 p = new SshPacket2(); DSACryptoSystem ds = key.getDSACryptoSystem(); p.putString("ssh-dss"); p.putMpInt(ds.getP()); p.putMpInt(ds.getQ()); p.putMpInt(ds.getG()); p.putMpInt(key.getPublicKeyData()); return p.getBytes(); } public static String getExportedKey(DSAPrivateKey key) throws CryptoException, IOException { byte[] encoded = encodePublicKey(key); return "ssh-rsa " + Base64OutputStream.encodeAsString(encoded, 0, encoded.length, false, false); } public static String getExportedKey(RSAPrivateKey key) throws CryptoException, IOException { byte[] encoded = encodePublicKey(key); return "ssh-dss " + Base64OutputStream.encodeAsString(encoded, 0, encoded.length, false, false); } public static byte[] encodeSSHRSASignature(byte[] s) throws IOException { TypesWriter tw = new TypesWriter(); tw.writeString("ssh-rsa"); /* "The value for 'rsa_signature_blob' is encoded as a string * containing s (which is an integer, without lengths or padding, unsigned and in * network byte order)." */ /* Remove first zero sign byte, if present */ int offset = ((s.length > 1) && (s[0] == 0x00)) ? 1 : 0; tw.writeString(s, offset, s.length - offset); return tw.getBytes(); } public static byte[] encodeSSHDSASignature(byte[] r, byte[] s) { TypesWriter tw = new TypesWriter(); tw.writeString("ssh-dss"); byte[] a40 = new byte[40]; /* Patch (unsigned) r and s into the target array. */ // int roffset = (r.length > 1 && r[0] == 0x00) ? 1 : 0; int r_copylen = (r.length < 20) ? r.length : 20; // int soffset = (s.length > 1 && s[0] == 0x00) ? 1 : 0; int s_copylen = (s.length < 20) ? s.length : 20; System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); tw.writeString(a40, 0, 40); return tw.getBytes(); } /** * Perform standard RSA signature verification, using RIM's crypto API. * * @param tr contains public key * @param signature signature to use in verification * @param message message to verify * @return true if signature verifies message successfully. * @throws IOException */ public static boolean verifyRSASignature(TypesReader tr, byte[] signature, byte[] message) throws IOException { try { byte[] e = tr.readByteString(); byte[] n = tr.readByteString(); n = Tools.removeBytePadding(n); RSACryptoSystem cs = new RSACryptoSystem(n.length * 8); TypesReader sig = new TypesReader(signature); sig.readString(); // "ssh-rsa" byte[] s = sig.readByteString(); RSAPublicKey pk = new RSAPublicKey(cs, e, n); PKCS1SignatureVerifier v = new PKCS1SignatureVerifier(pk, new SHA1Digest(), s, 0); v.update(message); if (v.verify()) return true; return false; } catch (Throwable e1) { throw new IOException(e1.toString() + " - " + e1.getMessage()); } } /** * This method implements a DSA signature verification (quick-reference: * http://en.wikipedia.org/wiki/Digital_Signature_Algorithm#Verifying) using the RIM CryptoInteger class to do most * of the heavy lifting. * * Note that we're not able to use the RIM crypto library: it is compatible with DSA keys compliant with FIPS-186-2 * which allows for key of up to 1024 bits in length. However FIPS-186-3 (approved jun 2009) allows for up to 3072 * bit. Many more installations are using key lengths of > 1024 bits; but these will not pass RIM crypto validation. * * Therefore we've implemented our own validation (which does not check bit length component at all) to work around * this. * * @param message message to sign * @param sig signature * @param p * @param q * @param g * @param y * @throws IOException */ public static void verifyDSASignature(byte[] message, byte[] sig, CryptoInteger p, CryptoInteger q, CryptoInteger g, CryptoInteger y) throws IOException { SHA1Digest md = new SHA1Digest(); SshPacket2 buf = new SshPacket2(); buf.putBytes(sig); buf.getByteString(); // algorithm wihch we know to be ssh-dss byte[] blob = buf.getByteString(); md.update(message); // extract R and S from the input int rslen = blob.length / 2; CryptoInteger r = new CryptoInteger(blob, 0, rslen); CryptoInteger s = new CryptoInteger(blob, rslen, rslen); CryptoInteger m = new CryptoInteger(md.getDigest()); CryptoInteger wc = s.invert(q); CryptoInteger u1 = m.multiply(wc, q); CryptoInteger u2 = r.multiply(wc, q); CryptoInteger zero = new CryptoInteger(0); if (zero.compareTo(r) >= 0 || q.compareTo(r) <= 0) { throw new IOException("DSA signature invalid: r/q out of range."); } if (zero.compareTo(s) >= 0 || q.compareTo(s) <= 0) { throw new IOException("DSA signature invalid: s/q out of range."); } u1 = g.exponent(u1, p); u2 = y.exponent(u2, p);// biginteger.modpow CryptoInteger vc = u1.multiply(u2, p).mod(q); if (!vc.equals(r)) { throw new IOException("DSA signature verification failed."); } } public static Object exportPublicKey(RSAPublicKey pk) throws IOException, CryptoTokenException, CryptoUnsupportedOperationException { SshPacket2 p = new SshPacket2(); p.putString("ssh-rsa"); p.putMpInt(pk.getE()); p.putMpInt(pk.getN()); byte[] encoded = p.getBytes(); return "ssh-rsa " + Base64OutputStream.encodeAsString(encoded, 0, encoded.length, false, false); } public static Object exportPublicKey(DSAPublicKey pk) throws CryptoTokenException, CryptoUnsupportedOperationException, IOException { // public byte[] encode() { SshPacket2 p = new SshPacket2(); DSACryptoSystem cs = pk.getDSACryptoSystem(); p.putString("ssh-dss"); p.putMpInt(cs.getP()); p.putMpInt(cs.getQ()); p.putMpInt(cs.getG()); p.putMpInt(pk.getPublicKeyData()); byte[] encoded = p.getBytes(); return "ssh-dss " + Base64OutputStream.encodeAsString(encoded, 0, encoded.length, false, false); } }