1 /* 2 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. 3 * Please refer to the LICENSE.txt for licensing details. 4 */ 5 package ch.ethz.ssh2.signature; 6 7 import java.io.IOException; 8 import java.math.BigInteger; 9 10 import ch.ethz.ssh2.crypto.SimpleDERReader; 11 import ch.ethz.ssh2.crypto.digest.SHA1; 12 import ch.ethz.ssh2.log.Logger; 13 import ch.ethz.ssh2.packets.TypesReader; 14 import ch.ethz.ssh2.packets.TypesWriter; 15 16 /** 17 * RSASHA1Verify. 18 * 19 * @author Christian Plattner 20 * @version $Id: RSASHA1Verify.java 41 2011-06-02 10:36:41Z dkocher (at) sudo.ch $ 21 */ 22 public class RSASHA1Verify 23 { 24 private static final Logger log = Logger.getLogger(RSASHA1Verify.class); 25 26 public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException 27 { 28 TypesReader tr = new TypesReader(key); 29 30 String key_format = tr.readString(); 31 32 if (key_format.equals("ssh-rsa") == false) 33 throw new IllegalArgumentException("This is not a ssh-rsa public key"); 34 35 BigInteger e = tr.readMPINT(); 36 BigInteger n = tr.readMPINT(); 37 38 if (tr.remain() != 0) 39 throw new IOException("Padding in RSA public key!"); 40 41 return new RSAPublicKey(e, n); 42 } 43 44 public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException 45 { 46 TypesWriter tw = new TypesWriter(); 47 48 tw.writeString("ssh-rsa"); 49 tw.writeMPInt(pk.getE()); 50 tw.writeMPInt(pk.getN()); 51 52 return tw.getBytes(); 53 } 54 55 public static RSASignature decodeSSHRSASignature(byte[] sig) throws IOException 56 { 57 TypesReader tr = new TypesReader(sig); 58 59 String sig_format = tr.readString(); 60 61 if (sig_format.equals("ssh-rsa") == false) 62 throw new IOException("Peer sent wrong signature format"); 63 64 /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string 65 * containing s (which is an integer, without lengths or padding, unsigned and in 66 * network byte order)." See also below. 67 */ 68 69 byte[] s = tr.readByteString(); 70 71 if (s.length == 0) 72 throw new IOException("Error in RSA signature, S is empty."); 73 74 if (log.isDebugEnabled()) 75 { 76 log.debug("Decoding ssh-rsa signature string (length: " + s.length + ")"); 77 } 78 79 if (tr.remain() != 0) 80 throw new IOException("Padding in RSA signature!"); 81 82 return new RSASignature(new BigInteger(1, s)); 83 } 84 85 public static byte[] encodeSSHRSASignature(RSASignature sig) throws IOException 86 { 87 TypesWriter tw = new TypesWriter(); 88 89 tw.writeString("ssh-rsa"); 90 91 /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string 92 * containing s (which is an integer, without lengths or padding, unsigned and in 93 * network byte order)." 94 */ 95 96 byte[] s = sig.getS().toByteArray(); 97 98 /* Remove first zero sign byte, if present */ 99 100 if ((s.length > 1) && (s[0] == 0x00)) 101 tw.writeString(s, 1, s.length - 1); 102 else 103 tw.writeString(s, 0, s.length); 104 105 return tw.getBytes(); 106 } 107 108 public static RSASignature generateSignature(byte[] message, RSAPrivateKey pk) throws IOException 109 { 110 SHA1 md = new SHA1(); 111 md.update(message); 112 byte[] sha_message = new byte[md.getDigestLength()]; 113 md.digest(sha_message); 114 115 byte[] der_header = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 116 0x04, 0x14 }; 117 118 int rsa_block_len = (pk.getN().bitLength() + 7) / 8; 119 120 int num_pad = rsa_block_len - (2 + der_header.length + sha_message.length) - 1; 121 122 if (num_pad < 8) 123 throw new IOException("Cannot sign with RSA, message too long"); 124 125 byte[] sig = new byte[der_header.length + sha_message.length + 2 + num_pad]; 126 127 sig[0] = 0x01; 128 129 for (int i = 0; i < num_pad; i++) 130 { 131 sig[i + 1] = (byte) 0xff; 132 } 133 134 sig[num_pad + 1] = 0x00; 135 136 System.arraycopy(der_header, 0, sig, 2 + num_pad, der_header.length); 137 System.arraycopy(sha_message, 0, sig, 2 + num_pad + der_header.length, sha_message.length); 138 139 BigInteger m = new BigInteger(1, sig); 140 141 BigInteger s = m.modPow(pk.getD(), pk.getN()); 142 143 return new RSASignature(s); 144 } 145 146 public static boolean verifySignature(byte[] message, RSASignature ds, RSAPublicKey dpk) throws IOException 147 { 148 SHA1 md = new SHA1(); 149 md.update(message); 150 byte[] sha_message = new byte[md.getDigestLength()]; 151 md.digest(sha_message); 152 153 BigInteger n = dpk.getN(); 154 BigInteger e = dpk.getE(); 155 BigInteger s = ds.getS(); 156 157 if (n.compareTo(s) <= 0) 158 { 159 log.warning("ssh-rsa signature: n.compareTo(s) <= 0"); 160 return false; 161 } 162 163 int rsa_block_len = (n.bitLength() + 7) / 8; 164 165 /* And now the show begins */ 166 167 if (rsa_block_len < 1) 168 { 169 log.warning("ssh-rsa signature: rsa_block_len < 1"); 170 return false; 171 } 172 173 byte[] v = s.modPow(e, n).toByteArray(); 174 175 int startpos = 0; 176 177 if ((v.length > 0) && (v[0] == 0x00)) 178 startpos++; 179 180 if ((v.length - startpos) != (rsa_block_len - 1)) 181 { 182 log.warning("ssh-rsa signature: (v.length - startpos) != (rsa_block_len - 1)"); 183 return false; 184 } 185 186 if (v[startpos] != 0x01) 187 { 188 log.warning("ssh-rsa signature: v[startpos] != 0x01"); 189 return false; 190 } 191 192 int pos = startpos + 1; 193 194 while (true) 195 { 196 if (pos >= v.length) 197 { 198 log.warning("ssh-rsa signature: pos >= v.length"); 199 return false; 200 } 201 if (v[pos] == 0x00) 202 break; 203 if (v[pos] != (byte) 0xff) 204 { 205 log.warning("ssh-rsa signature: v[pos] != (byte) 0xff"); 206 return false; 207 } 208 pos++; 209 } 210 211 int num_pad = pos - (startpos + 1); 212 213 if (num_pad < 8) 214 { 215 log.warning("ssh-rsa signature: num_pad < 8"); 216 return false; 217 } 218 219 pos++; 220 221 if (pos >= v.length) 222 { 223 log.warning("ssh-rsa signature: pos >= v.length"); 224 return false; 225 } 226 227 SimpleDERReader dr = new SimpleDERReader(v, pos, v.length - pos); 228 229 byte[] seq = dr.readSequenceAsByteArray(); 230 231 if (dr.available() != 0) 232 { 233 log.warning("ssh-rsa signature: dr.available() != 0"); 234 return false; 235 } 236 237 dr.resetInput(seq); 238 239 /* Read digestAlgorithm */ 240 241 byte digestAlgorithm[] = dr.readSequenceAsByteArray(); 242 243 /* Inspired by RFC 3347, however, ignoring the comment regarding old BER based implementations */ 244 245 if ((digestAlgorithm.length < 8) || (digestAlgorithm.length > 9)) 246 { 247 log.warning("ssh-rsa signature: (digestAlgorithm.length < 8) || (digestAlgorithm.length > 9)"); 248 return false; 249 } 250 251 byte[] digestAlgorithm_sha1 = new byte[] { 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00 }; 252 253 for (int i = 0; i < digestAlgorithm.length; i++) 254 { 255 if (digestAlgorithm[i] != digestAlgorithm_sha1[i]) 256 { 257 log.warning("ssh-rsa signature: digestAlgorithm[i] != digestAlgorithm_sha1[i]"); 258 return false; 259 } 260 } 261 262 byte[] digest = dr.readOctetString(); 263 264 if (dr.available() != 0) 265 { 266 log.warning("ssh-rsa signature: dr.available() != 0 (II)"); 267 return false; 268 } 269 270 if (digest.length != sha_message.length) 271 { 272 log.warning("ssh-rsa signature: digest.length != sha_message.length"); 273 return false; 274 } 275 276 for (int i = 0; i < sha_message.length; i++) 277 { 278 if (sha_message[i] != digest[i]) 279 { 280 log.warning("ssh-rsa signature: sha_message[i] != digest[i]"); 281 return false; 282 } 283 } 284 285 return true; 286 } 287 } 288