EMMA Coverage Report (generated Wed Oct 26 13:47:17 EDT 2011)
[all classes][org.jclouds.crypto]

COVERAGE SUMMARY FOR SOURCE FILE [SshKeys.java]

nameclass, %method, %block, %line, %
SshKeys.java100% (1/1)75%  (18/24)76%  (366/481)67%  (62.8/94)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SshKeys100% (1/1)75%  (18/24)76%  (366/481)67%  (62.8/94)
SshKeys (): void 0%   (0/1)0%   (0/3)0%   (0/1)
fingerprintPrivateKey (String): String 0%   (0/1)0%   (0/24)0%   (0/4)
fingerprintPublicKey (String): String 0%   (0/1)0%   (0/9)0%   (0/2)
publicKeyHasFingerprint (RSAPublicKeySpec, String): boolean 0%   (0/1)0%   (0/8)0%   (0/1)
publicKeyHasFingerprint (String, String): boolean 0%   (0/1)0%   (0/5)0%   (0/1)
sha1PrivateKey (String): String 0%   (0/1)0%   (0/21)0%   (0/4)
generate (): Map 100% (1/1)40%  (4/10)25%  (1/4)
publicKeySpecFromOpenSSH (String): RSAPublicKeySpec 100% (1/1)40%  (4/10)25%  (1/4)
sha1 (RSAPrivateCrtKeySpec): String 100% (1/1)57%  (16/28)25%  (2/8)
keyBlob (BigInteger, BigInteger): byte [] 100% (1/1)76%  (19/25)62%  (5/8)
writeLengthFirst (byte [], ByteArrayOutputStream): void 100% (1/1)82%  (40/49)86%  (6.9/8)
encodeAsPem (RSAPrivateKey): String 100% (1/1)82%  (18/22)75%  (6/8)
privateKeyMatchesPublicKey (RSAPrivateCrtKeySpec, RSAPublicKeySpec): boolean 100% (1/1)94%  (15/16)93%  (0.9/1)
publicKeySpecFromOpenSSH (InputSupplier): RSAPublicKeySpec 100% (1/1)99%  (68/69)100% (9/9)
encodeAsOpenSSH (RSAPublicKey): String 100% (1/1)100% (16/16)100% (2/2)
fingerprint (BigInteger, BigInteger): String 100% (1/1)100% (14/14)100% (2/2)
generate (KeyPairGenerator): Map 100% (1/1)100% (28/28)100% (5/5)
generateRsaKeyPair (KeyPairGenerator): KeyPair 100% (1/1)100% (11/11)100% (3/3)
privateKeyHasFingerprint (RSAPrivateCrtKeySpec, String): boolean 100% (1/1)100% (8/8)100% (1/1)
privateKeyHasFingerprint (String, String): boolean 100% (1/1)100% (20/20)100% (3/3)
privateKeyHasSha1 (RSAPrivateCrtKeySpec, String): boolean 100% (1/1)100% (5/5)100% (1/1)
privateKeyHasSha1 (String, String): boolean 100% (1/1)100% (20/20)100% (3/3)
privateKeyMatchesPublicKey (String, String): boolean 100% (1/1)100% (21/21)100% (3/3)
readLengthFirst (InputStream): byte [] 100% (1/1)100% (39/39)100% (8/8)

1/**
2 * Licensed to jclouds, Inc. (jclouds) under one or more
3 * contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  jclouds licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.jclouds.crypto;
20 
21import static com.google.common.base.Preconditions.checkArgument;
22import static com.google.common.base.Throwables.propagate;
23import static org.jclouds.crypto.CryptoStreams.base64;
24import static org.jclouds.crypto.CryptoStreams.hex;
25import static org.jclouds.crypto.CryptoStreams.md5;
26import static org.jclouds.crypto.Pems.privateKeySpec;
27 
28import java.io.ByteArrayInputStream;
29import java.io.ByteArrayOutputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.StringWriter;
33import java.math.BigInteger;
34import java.security.KeyFactory;
35import java.security.KeyPair;
36import java.security.KeyPairGenerator;
37import java.security.NoSuchAlgorithmException;
38import java.security.SecureRandom;
39import java.security.interfaces.RSAPrivateKey;
40import java.security.interfaces.RSAPublicKey;
41import java.security.spec.InvalidKeySpecException;
42import java.security.spec.KeySpec;
43import java.security.spec.RSAPrivateCrtKeySpec;
44import java.security.spec.RSAPublicKeySpec;
45import java.util.Map;
46 
47import org.bouncycastle.openssl.PEMWriter;
48import org.jclouds.encryption.internal.Base64;
49import org.jclouds.io.InputSuppliers;
50import org.jclouds.util.Strings2;
51 
52import com.google.common.annotations.Beta;
53import com.google.common.base.Joiner;
54import com.google.common.base.Splitter;
55import com.google.common.base.Throwables;
56import com.google.common.collect.ImmutableMap;
57import com.google.common.collect.Iterables;
58import com.google.common.collect.ImmutableMap.Builder;
59import com.google.common.io.InputSupplier;
60 
61/**
62 * Utilities for ssh key pairs
63 * 
64 * @author Adrian Cole
65 * @see <a href=
66 *      "http://stackoverflow.com/questions/3706177/how-to-generate-ssh-compatible-id-rsa-pub-from-java"
67 *      />
68 */
69@Beta
70public class SshKeys {
71 
72   /**
73    * Executes {@link Pems#publicKeySpecFromOpenSSH(InputSupplier)} on the string which was OpenSSH
74    * Base64 Encoded {@code id_rsa.pub}
75    * 
76    * @param idRsaPub
77    *           formatted {@code ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...}
78    * @see Pems#publicKeySpecFromOpenSSH(InputSupplier)
79    */
80   public static RSAPublicKeySpec publicKeySpecFromOpenSSH(String idRsaPub) {
81      try {
82         return publicKeySpecFromOpenSSH(InputSuppliers.of(idRsaPub));
83      } catch (IOException e) {
84         propagate(e);
85         return null;
86      }
87   }
88 
89   /**
90    * Returns {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
91    * 
92    * @param supplier
93    *           the input stream factory, formatted {@code ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...}
94    * 
95    * @return the {@link RSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
96    * @throws IOException
97    *            if an I/O error occurs
98    */
99   public static RSAPublicKeySpec publicKeySpecFromOpenSSH(InputSupplier<? extends InputStream> supplier)
100            throws IOException {
101      InputStream stream = supplier.getInput();
102      Iterable<String> parts = Splitter.on(' ').split(Strings2.toStringAndClose(stream));
103      checkArgument(Iterables.size(parts) >= 2 && "ssh-rsa".equals(Iterables.get(parts, 0)),
104               "bad format, should be: ssh-rsa AAAAB3...");
105      stream = new ByteArrayInputStream(Base64.decode(Iterables.get(parts, 1)));
106      String marker = new String(readLengthFirst(stream));
107      checkArgument("ssh-rsa".equals(marker), "looking for marker ssh-rsa but got %s", marker);
108      BigInteger publicExponent = new BigInteger(readLengthFirst(stream));
109      BigInteger modulus = new BigInteger(readLengthFirst(stream));
110      return new RSAPublicKeySpec(modulus, publicExponent);
111   }
112 
113   // http://www.ietf.org/rfc/rfc4253.txt
114   static byte[] readLengthFirst(InputStream in) throws IOException {
115      int byte1 = in.read();
116      int byte2 = in.read();
117      int byte3 = in.read();
118      int byte4 = in.read();
119      int length = ((byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0));
120      byte[] val = new byte[length];
121      in.read(val, 0, length);
122      return val;
123   }
124 
125   /**
126    * 
127    * @param used
128    *           to generate RSA key pairs
129    * @return new 2048 bit keyPair
130    * @see Crypto#rsaKeyPairGenerator()
131    */
132   public static KeyPair generateRsaKeyPair(KeyPairGenerator generator) {
133      SecureRandom rand = new SecureRandom();
134      generator.initialize(2048, rand);
135      return generator.genKeyPair();
136   }
137 
138   /**
139    * return a "public" -> rsa public key, "private" -> its corresponding private key
140    */
141   public static Map<String, String> generate() {
142      try {
143         return generate(KeyPairGenerator.getInstance("RSA"));
144      } catch (NoSuchAlgorithmException e) {
145         propagate(e);
146         return null;
147      }
148   }
149 
150   public static Map<String, String> generate(KeyPairGenerator generator) {
151      KeyPair pair = generateRsaKeyPair(generator);
152      Builder<String, String> builder = ImmutableMap.<String, String> builder();
153      builder.put("public", encodeAsOpenSSH(RSAPublicKey.class.cast(pair.getPublic())));
154      builder.put("private", encodeAsPem(RSAPrivateKey.class.cast(pair.getPrivate())));
155      return builder.build();
156   }
157 
158   public static String encodeAsOpenSSH(RSAPublicKey key) {
159      byte[] keyBlob = keyBlob(key.getPublicExponent(), key.getModulus());
160      return "ssh-rsa " + base64(keyBlob);
161   }
162 
163   public static String encodeAsPem(RSAPrivateKey key) {
164      StringWriter stringWriter = new StringWriter();
165      PEMWriter pemFormatWriter = new PEMWriter(stringWriter);
166      try {
167         pemFormatWriter.writeObject(key);
168         pemFormatWriter.close();
169      } catch (IOException e) {
170         Throwables.propagate(e);
171      }
172      return stringWriter.toString();
173      // TODO: understand why pem isn't passing testCanGenerate where keys are
174      // checked to match.
175      // return pem(key.getEncoded(), PRIVATE_PKCS1_MARKER, 64);
176   }
177 
178   /**
179    * @param privateKeyPEM
180    *           RSA private key in PEM format
181    * @param publicKeyOpenSSH
182    *           RSA public key in OpenSSH format
183    * @return true if the keypairs match
184    */
185   public static boolean privateKeyMatchesPublicKey(String privateKeyPEM, String publicKeyOpenSSH) {
186      KeySpec privateKeySpec = privateKeySpec(privateKeyPEM);
187      checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec,
188               "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec);
189      return privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec.class.cast(privateKeySpec),
190               publicKeySpecFromOpenSSH(publicKeyOpenSSH));
191   }
192 
193   /**
194    * @return true if the keypairs match
195    */
196   public static boolean privateKeyMatchesPublicKey(RSAPrivateCrtKeySpec privateKey, RSAPublicKeySpec publicKey) {
197      return privateKey.getPublicExponent().equals(publicKey.getPublicExponent())
198               && privateKey.getModulus().equals(publicKey.getModulus());
199   }
200 
201   /**
202    * @return true if the keypair has the same fingerprint as supplied
203    */
204   public static boolean privateKeyHasFingerprint(RSAPrivateCrtKeySpec privateKey, String fingerprint) {
205      return fingerprint(privateKey.getPublicExponent(), privateKey.getModulus()).equals(fingerprint);
206   }
207 
208   /**
209    * @param privateKeyPEM
210    *           RSA private key in PEM format
211    * @param fingerprint
212    *           ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
213    * @return true if the keypair has the same fingerprint as supplied
214    */
215   public static boolean privateKeyHasFingerprint(String privateKeyPEM, String fingerprint) {
216      KeySpec privateKeySpec = privateKeySpec(privateKeyPEM);
217      checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec,
218               "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec);
219      return privateKeyHasFingerprint(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), fingerprint);
220   }
221 
222   /**
223    * @param privateKeyPEM
224    *           RSA private key in PEM format
225    * @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
226    */
227   public static String fingerprintPrivateKey(String privateKeyPEM) {
228      KeySpec privateKeySpec = privateKeySpec(privateKeyPEM);
229      checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec,
230               "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec);
231      RSAPrivateCrtKeySpec certKeySpec = RSAPrivateCrtKeySpec.class.cast(privateKeySpec);
232      return fingerprint(certKeySpec.getPublicExponent(), certKeySpec.getModulus());
233   }
234 
235   /**
236    * @param publicKeyOpenSSH
237    *           RSA public key in OpenSSH format
238    * @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
239    */
240   public static String fingerprintPublicKey(String publicKeyOpenSSH) {
241      RSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
242      return fingerprint(publicKeySpec.getPublicExponent(), publicKeySpec.getModulus());
243   }
244 
245   /**
246    * @return true if the keypair has the same SHA1 fingerprint as supplied
247    */
248   public static boolean privateKeyHasSha1(RSAPrivateCrtKeySpec privateKey, String fingerprint) {
249      return sha1(privateKey).equals(fingerprint);
250   }
251 
252   /**
253    * @param privateKeyPEM
254    *           RSA private key in PEM format
255    * @param sha1HexColonDelimited
256    *           ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
257    * @return true if the keypair has the same fingerprint as supplied
258    */
259   public static boolean privateKeyHasSha1(String privateKeyPEM, String sha1HexColonDelimited) {
260      KeySpec privateKeySpec = privateKeySpec(privateKeyPEM);
261      checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec,
262               "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec);
263      return privateKeyHasSha1(RSAPrivateCrtKeySpec.class.cast(privateKeySpec), sha1HexColonDelimited);
264   }
265 
266   /**
267    * @param privateKeyPEM
268    *           RSA private key in PEM format
269    * @return sha1HexColonDelimited ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
270    */
271   public static String sha1PrivateKey(String privateKeyPEM) {
272      KeySpec privateKeySpec = privateKeySpec(privateKeyPEM);
273      checkArgument(privateKeySpec instanceof RSAPrivateCrtKeySpec,
274               "incorrect format expected RSAPrivateCrtKeySpec was %s", privateKeySpec);
275      RSAPrivateCrtKeySpec certKeySpec = RSAPrivateCrtKeySpec.class.cast(privateKeySpec);
276      return sha1(certKeySpec);
277   }
278 
279   /**
280    * Create a SHA-1 digest of the DER encoded private key.
281    * 
282    * @param publicExponent
283    * @param modulus
284    * 
285    * @return hex sha1HexColonDelimited ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
286    */
287   public static String sha1(RSAPrivateCrtKeySpec privateKey) {
288      try {
289         String sha1 = Joiner.on(":").join(
290                  Splitter.fixedLength(2).split(
291                           hex(CryptoStreams.sha1(KeyFactory.getInstance("RSA").generatePrivate(privateKey)
292                                    .getEncoded()))));
293         return sha1;
294      } catch (InvalidKeySpecException e) {
295         propagate(e);
296         return null;
297      } catch (NoSuchAlgorithmException e) {
298         propagate(e);
299         return null;
300      }
301   }
302 
303   /**
304    * @return true if the keypair has the same fingerprint as supplied
305    */
306   public static boolean publicKeyHasFingerprint(RSAPublicKeySpec publicKey, String fingerprint) {
307      return fingerprint(publicKey.getPublicExponent(), publicKey.getModulus()).equals(fingerprint);
308   }
309 
310   /**
311    * @param publicKeyOpenSSH
312    *           RSA public key in OpenSSH format
313    * @param fingerprint
314    *           ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
315    * @return true if the keypair has the same fingerprint as supplied
316    */
317   public static boolean publicKeyHasFingerprint(String publicKeyOpenSSH, String fingerprint) {
318      return publicKeyHasFingerprint(publicKeySpecFromOpenSSH(publicKeyOpenSSH), fingerprint);
319   }
320 
321   /**
322    * Create a fingerprint per the following <a
323    * href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00" >spec</a>
324    * 
325    * @param publicExponent
326    * @param modulus
327    * 
328    * @return hex fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
329    */
330   public static String fingerprint(BigInteger publicExponent, BigInteger modulus) {
331      byte[] keyBlob = keyBlob(publicExponent, modulus);
332      return Joiner.on(":").join(Splitter.fixedLength(2).split(hex(md5(keyBlob))));
333   }
334 
335   public static byte[] keyBlob(BigInteger publicExponent, BigInteger modulus) {
336      try {
337         ByteArrayOutputStream out = new ByteArrayOutputStream();
338         writeLengthFirst("ssh-rsa".getBytes(), out);
339         writeLengthFirst(publicExponent.toByteArray(), out);
340         writeLengthFirst(modulus.toByteArray(), out);
341         return out.toByteArray();
342      } catch (IOException e) {
343         propagate(e);
344         return null;
345      }
346   }
347 
348   // http://www.ietf.org/rfc/rfc4253.txt
349   static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException {
350      out.write((array.length >>> 24) & 0xFF);
351      out.write((array.length >>> 16) & 0xFF);
352      out.write((array.length >>> 8) & 0xFF);
353      out.write((array.length >>> 0) & 0xFF);
354      if (array.length == 1 && array[0] == (byte) 0x00)
355         out.write(new byte[0]);
356      else
357         out.write(array);
358   }
359}

[all classes][org.jclouds.crypto]
EMMA 2.0.5312 (C) Vladimir Roubtsov