blob: 3d65d87a827112969a2fc710680117d68b52e4d9 [file] [log] [blame]
/*
* 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);
}
}
}