package io.airlift.airship.coordinator.auth;
import com.google.common.base.Throwables;
import com.google.common.io.Resources;
import com.google.common.primitives.Bytes;
import io.airlift.airship.coordinator.auth.ssh.DerReader;
import io.airlift.airship.coordinator.auth.ssh.PemDecoder;
import io.airlift.airship.coordinator.auth.ssh.PemDsaPrivateKey;
import io.airlift.airship.coordinator.auth.ssh.PemRsaPrivateKey;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Arrays;
import static com.google.common.base.Charsets.UTF_8;
import static io.airlift.airship.coordinator.auth.ssh.DerType.SEQUENCE;
import static org.apache.commons.codec.binary.Hex.encodeHexString;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
/*
Change to the resource directory:
cd src/test/resources/io/airlift/airship/coordinator/auth
Generate test keys:
ssh-keygen -t rsa -C testkey -N '' -f testkey.rsa
ssh-keygen -t dsa -C testkey -N '' -f testkey.dsa
Add test keys to SSH agent:
ssh-add testkey.{rsa,dsa}
Create signatures using the agent in Ruby:
require 'ssh/key/signer'
signer = SSH::Key::Signer.new
x = signer.sign('Hello world')
rsa = x.find {|i| i.identity.comment == 'testkey.rsa' }
dsa = x.find {|i| i.identity.comment == 'testkey.dsa' }
File.open('signature.rsa', 'wb') {|f| f.write(rsa.signature) }
File.open('signature.dsa', 'wb') {|f| f.write(dsa.signature) }
*/
public class TestPublicKey
{
private static final byte[] message = "Hello world".getBytes(UTF_8);
private PublicKey rsaPublicKey;
private PublicKey dsaPublicKey;
private PemRsaPrivateKey rsaPrivateKey;
private PemDsaPrivateKey dsaPrivateKey;
@BeforeMethod
public void setup()
throws IOException
{
rsaPublicKey = PublicKey.valueOf(loadResource("testkey.rsa.pub"));
dsaPublicKey = PublicKey.valueOf(loadResource("testkey.dsa.pub"));
rsaPrivateKey = (PemRsaPrivateKey) PemDecoder.decodeSshPrivateKey(loadResource("testkey.rsa"));
dsaPrivateKey = (PemDsaPrivateKey) PemDecoder.decodeSshPrivateKey(loadResource("testkey.dsa"));
}
@Test
public void testRsaVerify()
throws IOException
{
byte[] signature = generateRsaSignature(message);
assertTrue(rsaPublicKey.verifySignature(signature, message));
}
@Test
public void testDsaVerify()
throws IOException
{
byte[] signature = generateDsaSignature(message);
assertTrue(dsaPublicKey.verifySignature(signature, message));
}
@Test
public void testRsaVerifyFromAgent()
throws IOException
{
byte[] signature = loadBinaryResource("signature.rsa");
assertTrue(rsaPublicKey.verifySignature(signature, message));
}
@Test
public void testDsaVerifyFromAgent()
throws IOException
{
byte[] signature = loadBinaryResource("signature.dsa");
assertTrue(dsaPublicKey.verifySignature(signature, message));
}
@Test
public void testDsaVerifyFromAgentWithEnvelope()
throws IOException
{
byte[] signature = loadBinaryResource("signature-envelope.dsa");
assertTrue(dsaPublicKey.verifySignature(signature, message));
}
private byte[] generateRsaSignature(byte[] message)
throws IOException
{
return sign(rsaPrivateKey, message, "SHA1withRSA");
}
private byte[] generateDsaSignature(byte[] message)
{
try {
byte[] javaSignature = sign(dsaPrivateKey, message, "SHA1withDSA");
return toSshDsaRawSignature(javaSignature);
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
private byte[] toSshDsaRawSignature(byte[] javaSignature)
throws IOException
{
// strip off the sequence envelope
byte[] sequence = new DerReader(javaSignature).readEntry(SEQUENCE);
// read the two integers of the key
DerReader derReader = new DerReader(sequence);
ByteArrayOutputStream rawSignature = new ByteArrayOutputStream();
rawSignature.write(getBytes(derReader.readBigInteger()));
rawSignature.write(getBytes(derReader.readBigInteger()));
return rawSignature.toByteArray();
}
public byte[] sign(PrivateKey key, byte[] message, String algorithm)
{
try {
Signature signature = Signature.getInstance(algorithm);
signature.initSign(key);
signature.update(message);
return signature.sign();
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
private String loadResource(String name)
throws IOException
{
return Resources.toString(getClass().getResource(name), UTF_8);
}
private byte[] loadBinaryResource(String name)
throws IOException
{
return Resources.toByteArray(getClass().getResource(name));
}
private static byte[] getBytes(BigInteger n)
{
byte[] bytes = n.toByteArray();
if ((bytes.length == 21) && (bytes[0] == 0)) {
bytes = Arrays.copyOfRange(bytes, 1, 21);
}
while (bytes.length < 20) {
bytes = Bytes.concat(new byte[] {0}, bytes);
}
assertEquals(bytes.length, 20, encodeHexString(bytes));
assertEquals(new BigInteger(1, bytes), n, encodeHexString(bytes));
return bytes;
}
}