170 lines
5.5 KiB
Java
170 lines
5.5 KiB
Java
/*
|
|
* Copyright 2014 The Netty Project
|
|
*
|
|
* The Netty Project 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 io.netty.handler.ssl;
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.Unpooled;
|
|
import io.netty.handler.codec.base64.Base64;
|
|
import io.netty.util.CharsetUtil;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.security.KeyException;
|
|
import java.security.KeyStore;
|
|
import java.security.cert.CertificateException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Reads a PEM file and converts it into a list of DERs so that they are imported into a {@link KeyStore} easily.
|
|
*/
|
|
final class PemReader {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PemReader.class);
|
|
|
|
private static final Pattern CERT_PATTERN = Pattern.compile(
|
|
"-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header
|
|
"([a-z0-9+/=\\r\\n]+)" + // Base64 text
|
|
"-+END\\s+.*CERTIFICATE[^-]*-+", // Footer
|
|
Pattern.CASE_INSENSITIVE);
|
|
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);
|
|
|
|
static ByteBuf[] readCertificates(File file) throws CertificateException {
|
|
try {
|
|
InputStream in = new FileInputStream(file);
|
|
|
|
try {
|
|
return readCertificates(in);
|
|
} finally {
|
|
safeClose(in);
|
|
}
|
|
} catch (FileNotFoundException e) {
|
|
throw new CertificateException("could not find certificate file: " + file);
|
|
}
|
|
}
|
|
|
|
static ByteBuf[] readCertificates(InputStream in) throws CertificateException {
|
|
String content;
|
|
try {
|
|
content = readContent(in);
|
|
} catch (IOException e) {
|
|
throw new CertificateException("failed to read certificate input stream", e);
|
|
}
|
|
|
|
List<ByteBuf> certs = new ArrayList<>();
|
|
Matcher m = CERT_PATTERN.matcher(content);
|
|
int start = 0;
|
|
while (m.find(start)) {
|
|
|
|
ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
|
|
ByteBuf der = Base64.decode(base64);
|
|
base64.release();
|
|
certs.add(der);
|
|
|
|
start = m.end();
|
|
}
|
|
|
|
if (certs.isEmpty()) {
|
|
throw new CertificateException("found no certificates in input stream");
|
|
}
|
|
|
|
return certs.toArray(new ByteBuf[0]);
|
|
}
|
|
|
|
static ByteBuf readPrivateKey(File file) throws KeyException {
|
|
try {
|
|
InputStream in = new FileInputStream(file);
|
|
|
|
try {
|
|
return readPrivateKey(in);
|
|
} finally {
|
|
safeClose(in);
|
|
}
|
|
} catch (FileNotFoundException e) {
|
|
throw new KeyException("could not find key file: " + file);
|
|
}
|
|
}
|
|
|
|
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" +
|
|
" (see https://netty.io/wiki/sslcontextbuilder-and-private-key.html for more information)");
|
|
}
|
|
|
|
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(InputStream in) {
|
|
try {
|
|
in.close();
|
|
} catch (IOException e) {
|
|
logger.warn("Failed to close a stream.", e);
|
|
}
|
|
}
|
|
|
|
private static void safeClose(OutputStream out) {
|
|
try {
|
|
out.close();
|
|
} catch (IOException e) {
|
|
logger.warn("Failed to close a stream.", e);
|
|
}
|
|
}
|
|
|
|
private PemReader() { }
|
|
}
|