/*
* 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<
ByteBuf>();
Matcher m =
CERT_PATTERN.
matcher(
content);
int
start = 0;
for (;;) {
if (!
m.
find(
start)) {
break;
}
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 http://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() { }
}