/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security.ssh;
import com.emc.storageos.security.exceptions.SecurityException;
import com.emc.storageos.security.keystore.impl.Base64Util;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.*;
import java.security.spec.ECPoint;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
/**
* PEM encoding utils for ssl and ssh. Append some information when encoding public keys for ssh
*/
public class PEMUtil {
public static final String PRIVSTE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----\n";
public static final String PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----";
static final String PRIVATE_KEY_FOOTER_WITH_NEWLINE = "\n" + PRIVATE_KEY_FOOTER + "\n";
public static boolean isPKCS8Key(String privateKey) {
return privateKey.contains(PRIVSTE_KEY_HEADER);
}
public static byte[] extractPKCS8Key(String privateKey) {
String encodedKey = privateKey.replace(PEMUtil.PRIVSTE_KEY_HEADER, "");
encodedKey = encodedKey.replace(PEMUtil.PRIVATE_KEY_FOOTER, "");
return Base64.decodeBase64(encodedKey);
}
public static byte[] decodePKCS8PrivateKey(String privateKey) throws Exception {
String encodedKey = privateKey.replace(PEMUtil.PRIVSTE_KEY_HEADER, "");
encodedKey = encodedKey.replace(PEMUtil.PRIVATE_KEY_FOOTER, "");
byte[] buf = Base64.decodeBase64(encodedKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buf);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec).getEncoded();
}
/**
* encode private key into PKCS8 PEM String
*
* @param keyBytes
* @return
*/
public static String encodePrivateKey(byte[] keyBytes) {
byte[] b64Key = Base64Util.encodeWithNewLine(keyBytes);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(PRIVSTE_KEY_HEADER.getBytes(), 0, PRIVSTE_KEY_HEADER.length());
out.write(b64Key, 0, b64Key.length);
// if the length of encoded key is multiple of 64, \n is appended by base64 lib. so don't append again.
String footer = (b64Key[b64Key.length - 1] == '\n') ?
PRIVATE_KEY_FOOTER : PRIVATE_KEY_FOOTER_WITH_NEWLINE;
out.write(footer.getBytes(), 0, footer.length());
return out.toString().replace("\0", "").replace("\r", "");
}
public static String encodeRSAPubKey(byte[] keyBytes, String user) throws Exception {
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(SSHParam.KeyAlgo.RSA.name()).
generatePublic(new X509EncodedKeySpec(keyBytes));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw SecurityException.fatals.failedToLoadPublicKey(e);
}
String publicKeyEncoded;
try (ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(byteOs)) {
if (!publicKey.getAlgorithm().equals("RSA")) {
throw new IllegalArgumentException("Unknown public key encoding: "
+ publicKey.getAlgorithm());
}
RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
dos.writeInt("ssh-rsa".getBytes().length);
dos.write("ssh-rsa".getBytes());
dos.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length);
dos.write(rsaPublicKey.getPublicExponent().toByteArray());
dos.writeInt(rsaPublicKey.getModulus().toByteArray().length);
dos.write(rsaPublicKey.getModulus().toByteArray());
publicKeyEncoded = "ssh-rsa ";
publicKeyEncoded += new String(Base64.encodeBase64(byteOs.toByteArray()));
if (StringUtils.isNotBlank(user)) {
publicKeyEncoded += " " + user;
}
}
return publicKeyEncoded;
}
public static String encodeDSAPubKey(byte[] keyBytes) throws IOException {
String publicKeyEncoded = null;
String userName = null;
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(SSHParam.KeyAlgo.DSA.name()).
generatePublic(new X509EncodedKeySpec(keyBytes));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw SecurityException.fatals.failedToLoadPublicKey(e);
}
try (ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(byteOs)) {
DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
DSAParams dsaParams = dsaPublicKey.getParams();
dos.writeInt("ssh-dss".getBytes().length);
dos.write("ssh-dss".getBytes());
dos.writeInt(dsaParams.getP().toByteArray().length);
dos.write(dsaParams.getP().toByteArray());
dos.writeInt(dsaParams.getQ().toByteArray().length);
dos.write(dsaParams.getQ().toByteArray());
dos.writeInt(dsaParams.getG().toByteArray().length);
dos.write(dsaParams.getG().toByteArray());
dos.writeInt(dsaPublicKey.getY().toByteArray().length);
dos.write(dsaPublicKey.getY().toByteArray());
publicKeyEncoded = "ssh-dss ";
publicKeyEncoded += new String(Base64.encodeBase64(byteOs.toByteArray()));
if (userName != null && !userName.isEmpty()) {
publicKeyEncoded += " " + userName;
}
}
return publicKeyEncoded;
}
public static String encodeECPubKey(byte[] keyBytes) throws IOException {
String publicKeyEncoded = "";
try (ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(byteOs)) {
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance(SSHParam.KeyAlgo.ECDSA.name()).
generatePublic(new X509EncodedKeySpec(keyBytes));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw SecurityException.fatals.failedToLoadPublicKey(e);
}
ECPublicKey ecPubKey = (ECPublicKey) publicKey;
String curveName = null;
int fieldSize = ecPubKey.getParams().getCurve().getField().getFieldSize();
switch (fieldSize) {
case 256:
curveName = "nistp256";
break;
case 384:
curveName = "nistp384";
break;
case 521:
curveName = "nistp521";
break;
}
String fullName = "ecdsa-sha2-" + curveName;
dos.writeInt(fullName.getBytes().length);
dos.write(fullName.getBytes());
dos.writeInt(curveName.getBytes().length);
dos.write(curveName.getBytes());
ECPoint group = ecPubKey.getW();
int elementSize = (ecPubKey.getParams().getCurve().getField().getFieldSize() + 7) / 8;
byte[] M = new byte[2 * elementSize + 1];
M[0] = 0x04;
byte[] affineX = group.getAffineX().toByteArray();
int startPos = dropWhile(affineX, 0x00);
int length = affineX.length - startPos;
System.arraycopy(affineX, startPos, M, 1 + elementSize - length, length);
byte[] affineY = group.getAffineY().toByteArray();
startPos = dropWhile(affineY, 0x00);
length = affineY.length - startPos;
System.arraycopy(affineY, startPos, M, 1 + elementSize + elementSize - length, length);
dos.writeInt(M.length);
dos.write(M);
publicKeyEncoded = fullName + " ";
publicKeyEncoded += new String(Base64.encodeBase64(byteOs.toByteArray()));
}
return publicKeyEncoded;
}
private static int dropWhile(byte[] buf, int x) {
int i = 0;
for (i = 0; i < buf.length; i++) {
if (buf[i] != x) {
break;
}
}
return i;
}
}