Skip to content

Commit

Permalink
Attempt to parse Certificate and PrivateKey in CLI credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud Dumont committed Apr 26, 2015
1 parent e1e2a2d commit 50f98e8
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### Version 4.6
* Decode any x509 cert or private key in CLI credentials

### Version 4.5
* Adds `ZoneApi.put()` and `ZoneApi.delete()`
* Adds zone add, update and delete to the CLI
Expand Down
9 changes: 8 additions & 1 deletion cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
compile project(':denominator-core')
compile 'io.airlift:airline:0.7'
compile 'org.yaml:snakeyaml:1.15'
compile 'org.bouncycastle:bcpkix-jdk15on:1.50'
// TODO: the following should really only be in the fatJar
compile project(':denominator-dynect')
compile project(':denominator-ultradns')
Expand Down Expand Up @@ -37,7 +38,13 @@ task fatJar(dependsOn: classes, type: Jar) {
doFirst {
// Delay evaluation until the compile configuration is ready
from {
configurations.compile.collect { zipTree(it).matching { exclude "**/${providerFile}" } }
configurations.compile.collect { zipTree(it).matching {
exclude "**/${providerFile}"
// remove all signature files
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
} }
}
}

Expand Down
55 changes: 53 additions & 2 deletions cli/src/main/java/denominator/cli/Denominator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.io.Files;
import com.google.gson.Gson;
Expand All @@ -20,11 +24,20 @@
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.X509CertParser;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.yaml.snakeyaml.Yaml;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
Expand Down Expand Up @@ -262,11 +275,12 @@ public static abstract class DenominatorCommand implements Runnable {
@SuppressWarnings("unchecked")
public void run() {
if (providerName != null && credentialArgs != null) {
credentials = ListCredentials.from(credentialArgs);
credentials = ListCredentials.from(Lists.transform(credentialArgs, decodeAnyPems));
} else if (providerConfigurationName != null) {
Map<?, ?> configFromFile = getConfigFromFile();
if (configFromFile != null) {
credentials = MapCredentials.from(Map.class.cast(configFromFile.get("credentials")));
credentials = MapCredentials.from(
Maps.transformValues(Map.class.cast(configFromFile.get("credentials")), decodeAnyPems));
providerName = configFromFile.get("provider").toString();
if (configFromFile.containsKey("url")) {
url = configFromFile.get("url").toString();
Expand Down Expand Up @@ -309,6 +323,43 @@ public void run() {
}
}

private static final Function<Object, Object> maybeDecodeX509Pem = new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input instanceof String && input.toString().contains("BEGIN CERTIFICATE")) {
try {
X509CertParser x509CertParser = new X509CertParser();
x509CertParser.engineInit(new ByteArrayInputStream(input.toString().getBytes()));
return x509CertParser.engineRead();
} catch (Exception ex) {
return input;
}
}
return input;
}
};

private static final Function<Object, Object> maybeDecodePrivateKeyPem = new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input instanceof String && input.toString().contains("BEGIN RSA PRIVATE KEY")) {
try {
PEMKeyPair pemKeyPair = (PEMKeyPair) new PEMParser(new StringReader(input.toString())).readObject();
PrivateKeyInfo privateKeyInfo = pemKeyPair.getPrivateKeyInfo();
KeyFactory keyFact = KeyFactory.getInstance(
privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), new BouncyCastleProvider());
return keyFact.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded()));
} catch (Exception ex) {
return input;
}
}
return input;
}
};

private static final Function<Object, Object> decodeAnyPems =
Functions.compose(maybeDecodeX509Pem, maybeDecodePrivateKeyPem);

/**
* Load configuration for given providerConfigurationName from a YAML configuration file.
*/
Expand Down
58 changes: 57 additions & 1 deletion cli/src/test/java/denominator/cli/DenominatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import java.io.IOException;
import java.net.URL;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
Expand Down Expand Up @@ -199,6 +201,29 @@ public Iterator<String> doRun(DNSApiManager mgr) {
zoneList.run();
}

@Test
public void testParseCertAndPrivateKeyInCredentials() {
ZoneList zoneList = new ZoneList() {
@Override
public Iterator<String> doRun(DNSApiManager mgr) {
assertThat(configPath).isEqualTo(getTestConfigPath("test-config-cert.yml"));
Map mapCredentials = Map.class.cast(credentials);

assertThat(((X509Certificate) mapCredentials.get("x509Certificate")).getSubjectDN().getName())
.containsIgnoringCase("C=US,ST=California,O=NetflixOSS,OU=Denominator,CN=Denominator");

PrivateKey privateKey = (PrivateKey) mapCredentials.get("privateKey");
assertThat(privateKey.getAlgorithm()).isEqualToIgnoringCase("RSA");
assertThat(privateKey.getFormat()).isEqualToIgnoringCase("PKCS#8");

return Collections.<String>emptyList().iterator();
}
};
zoneList.providerConfigurationName = "blah";
zoneList.configPath = getTestConfigPath("test-config-cert.yml");
zoneList.run();
}

@Test
// denominator -p mock zone add -n denominator.io. -e nil@denominator.io
public void testZoneAdd() {
Expand Down Expand Up @@ -346,8 +371,39 @@ public Iterator<String> doRun(DNSApiManager mgr) {
zoneList.run();
}

@Test
public void testParseCertAndPrivateKeyInCLICredentials() throws IOException {
ZoneList zoneList = new ZoneList() {
@Override
public Iterator<String> doRun(DNSApiManager mgr) {
ListCredentials listCredentials = (ListCredentials) credentials;

assertThat(((X509Certificate) listCredentials.get(0)).getSubjectDN().getName())
.containsIgnoringCase("C=US,ST=California,O=NetflixOSS,OU=Denominator,CN=Denominator");

PrivateKey privateKey = (PrivateKey) listCredentials.get(1);
assertThat(privateKey.getAlgorithm()).isEqualToIgnoringCase("RSA");
assertThat(privateKey.getFormat()).isEqualToIgnoringCase("PKCS#8");

return Collections.<String>emptyList().iterator();
}
};
zoneList.providerConfigurationName = "blah";
zoneList.providerName = "mock";
Map configFromYaml = zoneList.getConfigFromYaml(
zoneList.getFileContentsFromPath(getTestConfigPath("test-config-cert.yml")));
Map credentials = Map.class.cast(configFromYaml.get("credentials"));
zoneList.credentialArgs = asList(credentials.get("x509Certificate").toString(),
credentials.get("privateKey").toString());
zoneList.run();
}

private String getTestConfigPath() {
URL res = getClass().getClassLoader().getResource("test-config.yml");
return getTestConfigPath("test-config.yml");
}

private String getTestConfigPath(String fileName) {
URL res = getClass().getClassLoader().getResource(fileName);
return res != null ? res.getFile() : null;
}

Expand Down
58 changes: 58 additions & 0 deletions cli/src/test/resources/test-config-cert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: blah
provider: mock
url: mem:mock3
credentials:
x509Certificate:
|
-----BEGIN CERTIFICATE-----
MIID1jCCAr6gAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UEBwwLTG9zIEFuZ2VsZXMxEzARBgNV
BAoMCk5ldGZsaXhPU1MxFjAUBgNVBAsMDURlbm9taW5hdG9yQ0ExFjAUBgNVBAMM
DURlbm9taW5hdG9yQ0EwHhcNMTUwNDIzMDYwMTA4WhcNMjUwNDIwMDYwMTA4WjBj
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UECgwKTmV0
ZmxpeE9TUzEUMBIGA1UECwwLRGVub21pbmF0b3IxFDASBgNVBAMMC0Rlbm9taW5h
dG9yMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+UdULbQ7kft49Xb
rUh5zERon+a7rwdJ5fNpu4O1A8pgKP4qTYBlIL0Wl2+4RSki4ayNR0LTSeyHClZL
znf8PAX6l17IshWdDkl3tAi89ZhYfQvs/wZAiReK5j1lYbon2fMUS+OnNwHvoUTX
VAF0c3AOgq4ZX2h83rcxbgDb0SMIdrE9Jl2EAHzlk9NM9nRFOfWF6ZKV5s7ir96W
roudJ3lh+bNu+Xr5AGC2QmuOfBdj9BvrBqFPCsWDmCFspiUoHjmZYTrcV4URH1Qg
em/NvdHi6hfRxk2DnUSY94ie722LQB45dH9QOKzeqi9hsuaurfDexbZXiUoH/4FK
gcurqwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM
IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUSrwesJpYDp3vow0h64xo
EdVcOiMwHwYDVR0jBBgwFoAUtbuY6iksQLHLP/HOuPNnXV7q6F4wDQYJKoZIhvcN
AQEFBQADggEBAMd+R1o0HSG9ZMXWWAaFdP79gYdCB8HPc+XXFaSpPz1oim3B9lbd
dFcR5+fFwbPw/Rjmb21J7MvzoPXzj6sLdKM09mTdl9YoSbTK25M/Do19LRgY44e1
GyuRxWA5fgtWzBD8syi2P2uqN9F8edsVrppC2qq6rnl8Y/AwWSJuHboNVxcOEIIC
3RUvnDINjfJ+kCxiZ+fZzhtkALworqt1UC0DIDuLHexrLkZ6A6kMtygJTnDmfcRa
RDmAASyzrUld/Mzr54WlA9qjv+ZjYM5IELcQ/saiOTlFRqP4ahWDFyqJCakBmvPw
RaeSI2iMpSDPB46Ce5wmM9S+xFJKgeARNiY=
-----END CERTIFICATE-----
privateKey:
|
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAy+UdULbQ7kft49XbrUh5zERon+a7rwdJ5fNpu4O1A8pgKP4q
TYBlIL0Wl2+4RSki4ayNR0LTSeyHClZLznf8PAX6l17IshWdDkl3tAi89ZhYfQvs
/wZAiReK5j1lYbon2fMUS+OnNwHvoUTXVAF0c3AOgq4ZX2h83rcxbgDb0SMIdrE9
Jl2EAHzlk9NM9nRFOfWF6ZKV5s7ir96WroudJ3lh+bNu+Xr5AGC2QmuOfBdj9Bvr
BqFPCsWDmCFspiUoHjmZYTrcV4URH1Qgem/NvdHi6hfRxk2DnUSY94ie722LQB45
dH9QOKzeqi9hsuaurfDexbZXiUoH/4FKgcurqwIDAQABAoIBABL3LNLlLmn7ptUa
f2SO6UVm15SIEc0mEGmjSqJ2jc/cczVc7vJmjwGMlR1g9vL38JatRjbqsFyf86+y
dzqtTnkG1VhfKKvn9sPtcYT9ixYNJa37/f96xWX7GqBv9Y4xkqNjSPAGLPeg5nlJ
vf3CZOKX+cGOEC4fuf29bIue6NN7FDxFTjLi88qWRqxpuzt6ERkWgq5/MCJgKjtE
xAgNY6VwZ/4zyPE1Wuf0RfD+gggsTGKgxi+VE6IUZaXFM/v/VQ1dkUBrAve/UEPO
5ILA/mCLn3KeYMs47ViZciSAVRo0a45TReaV6Ii7thOP6NyYgZHuBsyoGQ0XJRSQ
9N+k6AECgYEA6DwSe20+I98Bhslc5bZDBNwIQbC6f6XPbDkeQldOVRFZdg9JtKjL
vAdp9LZZRRbVfHroMs9/lH1OPuyCFsF0bAwBqDBW2fhhN+CLDkyhzLr0o2Y8o2VM
eDjgRStBRQJxi4bLFhdUKxSYTrMOwKNzzePHHO0KNh8tfvJYoKr6I+sCgYEA4MKe
UStMyQ2S1ZtpUNvat9XhWYNZJvxzLL6wO50PGZr+bv9rcYneb3qtmwbYX/XKHJmS
wvN3mVQEC81vOEdSn1FExEHYccs5A3Hi7SeGu708qRRoz+hSqI5v+IDrkUCS8CeR
cuhuX6eY+tSbvW11pI0JI39alsgr5fS3Ypc/Z0ECgYEArWwOsTUG7mqeDTVZtgDB
U2OslsGOw7vihYKx4gCockYedFc+Rza1zZgLu75gM4O1sNtngmoJb62TPNII401b
z0O4CQzwDp24m62Gqgr32JbUESFLrwj6bdyrPXvrpckJWZQzHDAXnoQmqL52Cc0F
sX6jNiqdMYVeyCnZnUGQwjkCgYB4RQwsiQwMpdvFu+TOJOdT6fotSRDiwMHs1pk9
PR0AaaTuxnS7C7hgVPgi9bXBG6gsR6117ow4GvITUrnKFrhKklVG46krs0Bt7Hou
xzDcszWLkGVJo5rfoWCfTHVBg5ldMv8syt2NCATfa/dulO0XwXR7b0GNX83Fvn6H
HkIKQQKBgQCMF/yQLC5YeWDWpl/PZYQt4RMSVj52eECyc+Q/pZERhUBgIeq9v36v
QnOVQB96YVYd1cO9poaalI2R4ljwWuIbPTFIUcXs7f3qYEY6tiulY2LigehCnFcx
i25KfcqnvI/wCY2kkqAjpNZdjKwnjfbkOShhHba8Dss9hkXqGmhl6Q==
-----END RSA PRIVATE KEY-----

0 comments on commit 50f98e8

Please sign in to comment.