1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.jclouds.crypto;
20
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.base.Throwables.propagate;
23 import static org.jclouds.crypto.CryptoStreams.base64;
24 import static org.jclouds.crypto.CryptoStreams.hex;
25 import static org.jclouds.crypto.CryptoStreams.md5;
26 import static org.jclouds.crypto.Pems.privateKeySpec;
27
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.StringWriter;
33 import java.math.BigInteger;
34 import java.security.KeyFactory;
35 import java.security.KeyPair;
36 import java.security.KeyPairGenerator;
37 import java.security.NoSuchAlgorithmException;
38 import java.security.SecureRandom;
39 import java.security.interfaces.RSAPrivateKey;
40 import java.security.interfaces.RSAPublicKey;
41 import java.security.spec.InvalidKeySpecException;
42 import java.security.spec.KeySpec;
43 import java.security.spec.RSAPrivateCrtKeySpec;
44 import java.security.spec.RSAPublicKeySpec;
45 import java.util.Map;
46
47 import org.bouncycastle.openssl.PEMWriter;
48 import org.jclouds.encryption.internal.Base64;
49 import org.jclouds.io.InputSuppliers;
50 import org.jclouds.util.Strings2;
51
52 import com.google.common.annotations.Beta;
53 import com.google.common.base.Joiner;
54 import com.google.common.base.Splitter;
55 import com.google.common.base.Throwables;
56 import com.google.common.collect.ImmutableMap;
57 import com.google.common.collect.Iterables;
58 import com.google.common.collect.ImmutableMap.Builder;
59 import com.google.common.io.InputSupplier;
60
61
62
63
64
65
66
67
68
69 @Beta
70 public class SshKeys {
71
72
73
74
75
76
77
78
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
91
92
93
94
95
96
97
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
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
128
129
130
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
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
174
175
176 }
177
178
179
180
181
182
183
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
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
203
204 public static boolean privateKeyHasFingerprint(RSAPrivateCrtKeySpec privateKey, String fingerprint) {
205 return fingerprint(privateKey.getPublicExponent(), privateKey.getModulus()).equals(fingerprint);
206 }
207
208
209
210
211
212
213
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
224
225
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
237
238
239
240 public static String fingerprintPublicKey(String publicKeyOpenSSH) {
241 RSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
242 return fingerprint(publicKeySpec.getPublicExponent(), publicKeySpec.getModulus());
243 }
244
245
246
247
248 public static boolean privateKeyHasSha1(RSAPrivateCrtKeySpec privateKey, String fingerprint) {
249 return sha1(privateKey).equals(fingerprint);
250 }
251
252
253
254
255
256
257
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
268
269
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
281
282
283
284
285
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
305
306 public static boolean publicKeyHasFingerprint(RSAPublicKeySpec publicKey, String fingerprint) {
307 return fingerprint(publicKey.getPublicExponent(), publicKey.getModulus()).equals(fingerprint);
308 }
309
310
311
312
313
314
315
316
317 public static boolean publicKeyHasFingerprint(String publicKeyOpenSSH, String fingerprint) {
318 return publicKeyHasFingerprint(publicKeySpecFromOpenSSH(publicKeyOpenSSH), fingerprint);
319 }
320
321
322
323
324
325
326
327
328
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
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 }