Skip to content

Commit

Permalink
xds: Add EC key support
Browse files Browse the repository at this point in the history
io.grpc.util.CertificateUtils does much of the same thing as xds's
CertificateUtils, but also supports EC keys. The xds code pre-dates the
grpc-util class, so it isn't surprising it wasn't using it.

There's a good number of usages of the xds CertificateUtils, so I just
got rid of the duplicate implementation, but didn't yet bother changing
callers io.grpc.util.
  • Loading branch information
ejona86 committed Jan 10, 2024
1 parent cb03bd2 commit 100d5a5
Show file tree
Hide file tree
Showing 4 changed files with 11 additions and 129 deletions.
Expand Up @@ -16,50 +16,19 @@

package io.grpc.xds.internal.security.trust;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.CharsetUtil;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Contains certificate utility method(s).
*/
public final class CertificateUtils {
private static final Logger logger = Logger.getLogger(CertificateUtils.class.getName());

private static CertificateFactory factory;
private static final Pattern KEY_PATTERN = Pattern.compile(
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" // Header
+ "([a-z0-9+/=\\r\\n]+)" // Base64 text
+ "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer
Pattern.CASE_INSENSITIVE);

private static synchronized void initInstance() throws CertificateException {
if (factory == null) {
factory = CertificateFactory.getInstance("X.509");
}
}

/**
* Generates X509Certificate array from a file on disk.
*
Expand All @@ -73,72 +42,15 @@ static X509Certificate[] toX509Certificates(File file) throws CertificateExcepti
}

/** Generates X509Certificate array from the {@link InputStream}. */
public static synchronized X509Certificate[] toX509Certificates(InputStream inputStream)
public static X509Certificate[] toX509Certificates(InputStream inputStream)
throws CertificateException, IOException {
initInstance();
Collection<? extends Certificate> certs = factory.generateCertificates(inputStream);
return certs.toArray(new X509Certificate[0]);

}

/** See {@link CertificateFactory#generateCertificate(InputStream)}. */
public static synchronized X509Certificate toX509Certificate(InputStream inputStream)
throws CertificateException, IOException {
initInstance();
Certificate cert = factory.generateCertificate(inputStream);
return (X509Certificate) cert;
return io.grpc.util.CertificateUtils.getX509Certificates(inputStream);
}

/** Generates a {@link PrivateKey} from the {@link InputStream}. */
public static PrivateKey getPrivateKey(InputStream inputStream)
throws Exception {
ByteBuf encodedKeyBuf = readPrivateKey(inputStream);
byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
encodedKeyBuf.readBytes(encodedKey).release();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encodedKey);
return KeyFactory.getInstance("RSA").generatePrivate(spec);
}

private static ByteBuf readPrivateKey(InputStream in) throws KeyException {
String content;
try {
content = readContent(in);
} catch (IOException e) {
throw new KeyException("failed to read key input stream", e);
}
Matcher m = KEY_PATTERN.matcher(content);
if (!m.find()) {
throw new KeyException("could not find a PKCS #8 private key in input stream");
}
ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
ByteBuf der = Base64.decode(base64);
base64.release();
return der;
}

private static String readContent(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
byte[] buf = new byte[8192];
for (; ; ) {
int ret = in.read(buf);
if (ret < 0) {
break;
}
out.write(buf, 0, ret);
}
return out.toString(CharsetUtil.US_ASCII.name());
} finally {
safeClose(out);
}
}

private static void safeClose(OutputStream out) {
try {
out.close();
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to close a stream.", e);
}
return io.grpc.util.CertificateUtils.getPrivateKey(inputStream);
}

private CertificateUtils() {}
Expand Down
Expand Up @@ -17,9 +17,7 @@
package io.grpc.xds.internal.security;

import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.io.CharStreams;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.BoolValue;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
Expand All @@ -32,14 +30,11 @@
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
import io.grpc.internal.testing.TestUtils;
import io.grpc.testing.TlsTesting;
import io.grpc.util.CertificateUtils;
import io.grpc.xds.EnvoyServerProtoData;
import io.grpc.xds.internal.security.trust.CertificateUtils;
import io.netty.handler.ssl.SslContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
Expand Down Expand Up @@ -195,22 +190,11 @@ public static EnvoyServerProtoData.UpstreamTlsContext buildUpstreamTlsContext(
/** Gets a cert from contents of a resource. */
public static X509Certificate getCertFromResourceName(String resourceName)
throws IOException, CertificateException {
try (ByteArrayInputStream bais =
new ByteArrayInputStream(getResourceContents(resourceName).getBytes(UTF_8))) {
return CertificateUtils.toX509Certificate(bais);
try (InputStream cert = TlsTesting.loadCert(resourceName)) {
return CertificateUtils.getX509Certificates(cert)[0];
}
}

/** Gets contents of a certs resource. */
public static String getResourceContents(String resourceName) throws IOException {
InputStream inputStream = TlsTesting.loadCert(resourceName);
String text = null;
try (Reader reader = new InputStreamReader(inputStream, UTF_8)) {
text = CharStreams.toString(reader);
}
return text;
}

@SuppressWarnings("deprecation")
private static CommonTlsContext buildCommonTlsContextForCertProviderInstance(
String certInstanceName,
Expand Down
Expand Up @@ -16,19 +16,13 @@

package io.grpc.xds.internal.security.certprovider;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.io.CharStreams;
import io.grpc.internal.FakeClock;
import io.grpc.internal.TimeProvider;
import io.grpc.internal.testing.TestUtils;
import io.grpc.util.CertificateUtils;
import io.grpc.xds.internal.security.certprovider.FileWatcherCertificateProviderProvider.ScheduledExecutorServiceFactory;
import io.grpc.xds.internal.security.trust.CertificateUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
Expand All @@ -44,17 +38,9 @@ static PrivateKey getPrivateKey(String resourceName)

static X509Certificate getCertFromResourceName(String resourceName)
throws IOException, CertificateException {
return CertificateUtils.toX509Certificate(
new ByteArrayInputStream(getResourceContents(resourceName).getBytes(UTF_8)));
}

private static String getResourceContents(String resourceName) throws IOException {
InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName);
String text = null;
try (Reader reader = new InputStreamReader(inputStream, UTF_8)) {
text = CharStreams.toString(reader);
try (InputStream cert = TestUtils.class.getResourceAsStream("/certs/" + resourceName)) {
return CertificateUtils.getX509Certificates(cert)[0];
}
return text;
}

/** Allow tests to register a provider using test clock.
Expand Down
Expand Up @@ -270,12 +270,12 @@ public void getCertificate_badKeyFile() throws IOException, InterruptedException
CLIENT_PEM_FILE,
SERVER_0_PEM_FILE,
CA_PEM_FILE,
java.security.KeyException.class,
java.security.spec.InvalidKeySpecException.class,
0,
1,
0,
0,
"could not find a PKCS #8 private key in input stream");
"Neither RSA nor EC worked");
}

@Test
Expand Down

0 comments on commit 100d5a5

Please sign in to comment.