| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jclouds.digitalocean.ssh; |
| |
| import static com.google.common.base.Joiner.on; |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Splitter.fixedLength; |
| import static com.google.common.base.Throwables.propagate; |
| import static com.google.common.collect.Iterables.get; |
| import static com.google.common.collect.Iterables.size; |
| import static com.google.common.io.BaseEncoding.base16; |
| import static com.google.common.io.BaseEncoding.base64; |
| import static org.jclouds.util.Strings2.toStringAndClose; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.math.BigInteger; |
| import java.security.interfaces.DSAParams; |
| import java.security.interfaces.DSAPublicKey; |
| import java.security.spec.DSAPublicKeySpec; |
| |
| import com.google.common.base.Charsets; |
| import com.google.common.base.Splitter; |
| import com.google.common.hash.HashCode; |
| import com.google.common.hash.Hashing; |
| import com.google.common.io.ByteSource; |
| |
| /** |
| * Utility methods to work with DSA SSH keys. |
| * <p> |
| * Methods in this class should be moved to the {@link org.jclouds.ssh.SshKeys} class. |
| * |
| * @author Sergi Castro |
| * @author Ignasi Barrera |
| * |
| * @see org.jclouds.ssh.SshKeys |
| */ |
| public class DSAKeys { |
| |
| public static String encodeAsOpenSSH(DSAPublicKey key) { |
| DSAParams params = key.getParams(); |
| byte[] keyBlob = keyBlob(params.getP(), params.getQ(), params.getG(), key.getY()); |
| return "ssh-dss " + base64().encode(keyBlob); |
| } |
| |
| /** |
| * Executes {@link org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)} on the |
| * string which was OpenSSH Base64 Encoded {@code id_rsa.pub} |
| * |
| * @param idRsaPub formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...} |
| * @see org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier) |
| */ |
| public static DSAPublicKeySpec publicKeySpecFromOpenSSH(String idDsaPub) { |
| try { |
| return publicKeySpecFromOpenSSH(ByteSource.wrap(idDsaPub.getBytes(Charsets.UTF_8))); |
| } catch (IOException e) { |
| throw propagate(e); |
| } |
| } |
| |
| /** |
| * Returns {@link DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub} |
| * |
| * @param supplier the input stream factory, formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...} |
| * |
| * @return the {@link DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub} |
| * @throws IOException if an I/O error occurs |
| */ |
| public static DSAPublicKeySpec publicKeySpecFromOpenSSH(ByteSource supplier) throws IOException { |
| InputStream stream = supplier.openStream(); |
| Iterable<String> parts = Splitter.on(' ').split(toStringAndClose(stream).trim()); |
| checkArgument(size(parts) >= 2 && "ssh-dss".equals(get(parts, 0)), "bad format, should be: ssh-dss AAAAB3..."); |
| stream = new ByteArrayInputStream(base64().decode(get(parts, 1))); |
| String marker = new String(readLengthFirst(stream)); |
| checkArgument("ssh-dss".equals(marker), "looking for marker ssh-dss but got %s", marker); |
| BigInteger p = new BigInteger(readLengthFirst(stream)); |
| BigInteger q = new BigInteger(readLengthFirst(stream)); |
| BigInteger g = new BigInteger(readLengthFirst(stream)); |
| BigInteger y = new BigInteger(readLengthFirst(stream)); |
| return new DSAPublicKeySpec(y, p, q, g); |
| } |
| |
| /** |
| * @param publicKeyOpenSSH RSA public key in OpenSSH format |
| * @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} |
| */ |
| public static String fingerprintPublicKey(String publicKeyOpenSSH) { |
| DSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH); |
| return fingerprint(publicKeySpec.getP(), publicKeySpec.getQ(), publicKeySpec.getG(), publicKeySpec.getY()); |
| } |
| |
| /** |
| * Create a fingerprint per the following <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00" |
| * >spec</a> |
| * |
| * @return hex fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9} |
| */ |
| public static String fingerprint(BigInteger p, BigInteger q, BigInteger g, BigInteger y) { |
| byte[] keyBlob = keyBlob(p, q, g, y); |
| return hexColonDelimited(Hashing.md5().hashBytes(keyBlob)); |
| } |
| |
| /** |
| * @see org.jclouds.ssh.SshKeys |
| */ |
| private static String hexColonDelimited(HashCode hc) { |
| return on(':').join(fixedLength(2).split(base16().lowerCase().encode(hc.asBytes()))); |
| } |
| |
| /** |
| * @see org.jclouds.ssh.SshKeys |
| */ |
| private static byte[] keyBlob(BigInteger p, BigInteger q, BigInteger g, BigInteger y) { |
| try { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| writeLengthFirst("ssh-dss".getBytes(), out); |
| writeLengthFirst(p.toByteArray(), out); |
| writeLengthFirst(q.toByteArray(), out); |
| writeLengthFirst(g.toByteArray(), out); |
| writeLengthFirst(y.toByteArray(), out); |
| return out.toByteArray(); |
| } catch (IOException e) { |
| throw propagate(e); |
| } |
| } |
| |
| /** |
| * @see org.jclouds.ssh.SshKeys |
| */ |
| // http://www.ietf.org/rfc/rfc4253.txt |
| private static byte[] readLengthFirst(InputStream in) throws IOException { |
| int byte1 = in.read(); |
| int byte2 = in.read(); |
| int byte3 = in.read(); |
| int byte4 = in.read(); |
| int length = (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0); |
| byte[] val = new byte[length]; |
| in.read(val, 0, length); |
| return val; |
| } |
| |
| /** |
| * @see org.jclouds.ssh.SshKeys |
| */ |
| // http://www.ietf.org/rfc/rfc4253.txt |
| private static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException { |
| out.write(array.length >>> 24 & 0xFF); |
| out.write(array.length >>> 16 & 0xFF); |
| out.write(array.length >>> 8 & 0xFF); |
| out.write(array.length >>> 0 & 0xFF); |
| if (array.length == 1 && array[0] == (byte) 0x00) { |
| out.write(new byte[0]); |
| } else { |
| out.write(array); |
| } |
| } |
| } |