This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit f1a93f0945cd4d90ea8e63d700c7cc5d70ae16e1 Author: Mark Thomas <ma...@apache.org> AuthorDate: Fri Jul 12 16:34:43 2019 +0100 Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=63524 part 2 of 2 Enable PKCS#1 format private keys to be read into the in-memory key store. --- java/org/apache/tomcat/util/buf/Asn1Parser.java | 89 ++++++++++++++++++++++ .../apache/tomcat/util/buf/LocalStrings.properties | 3 + .../tomcat/util/net/jsse/LocalStrings.properties | 2 + java/org/apache/tomcat/util/net/jsse/PEMFile.java | 54 +++++++++++-- webapps/docs/changelog.xml | 7 ++ 5 files changed, 150 insertions(+), 5 deletions(-) diff --git a/java/org/apache/tomcat/util/buf/Asn1Parser.java b/java/org/apache/tomcat/util/buf/Asn1Parser.java new file mode 100644 index 0000000..35fe161 --- /dev/null +++ b/java/org/apache/tomcat/util/buf/Asn1Parser.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 org.apache.tomcat.util.buf; + +import java.math.BigInteger; + +import org.apache.tomcat.util.res.StringManager; + +/** + * This is a very basic ASN.1 parser that provides the limited functionality + * required by Tomcat. It is a long way from a complete parser. + * + * TODO: Consider extending this parser and refactoring the SpnegoTokenFixer to + * use it. + */ +public class Asn1Parser { + + private static final StringManager sm = StringManager.getManager(Asn1Parser.class); + + private final byte[] source; + + private int pos = 0; + + + public Asn1Parser(byte[] source) { + this.source = source; + } + + + public void parseTag(int tag) { + int value = next(); + if (value != tag) { + throw new IllegalArgumentException(sm.getString("asn1Parser.tagMismatch", + Integer.valueOf(tag), Integer.valueOf(value))); + } + } + + + public void parseFullLength() { + int len = parseLength(); + if (len + pos != source.length) { + throw new IllegalArgumentException(sm.getString("asn1Parser.lengthInvalid", + Integer.valueOf(len), Integer.valueOf(source.length - pos))); + } + } + + + public int parseLength() { + int len = next(); + if (len > 127) { + int bytes = len - 128; + len = 0; + for (int i = 0; i < bytes; i++) { + len = len << 8; + len = len + next(); + } + } + return len; + } + + + public BigInteger parseInt() { + parseTag(0x02); + int len = parseLength(); + byte[] val = new byte[len]; + System.arraycopy(source, pos, val, 0, len); + pos += len; + return new BigInteger(val); + } + + + private int next() { + return source[pos++] & 0xFF; + } +} diff --git a/java/org/apache/tomcat/util/buf/LocalStrings.properties b/java/org/apache/tomcat/util/buf/LocalStrings.properties index 9c47113..d595e75 100644 --- a/java/org/apache/tomcat/util/buf/LocalStrings.properties +++ b/java/org/apache/tomcat/util/buf/LocalStrings.properties @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +asn1Parser.lengthInvalid=Invalid length [{0}] bytes reported when the input data length is [{1}] bytes +asn1Parser.tagMismatch=Expected to find value [{0}] but found value [{1}] + b2cConverter.unknownEncoding=The character encoding [{0}] is not supported byteBufferUtils.cleaner=Cannot use direct ByteBuffer cleaner, memory leaking may occur diff --git a/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties b/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties index 931410a..2bcb836 100644 --- a/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties +++ b/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties @@ -35,3 +35,5 @@ jsseUtil.noCrlSupport=The truststoreProvider [{0}] does not support the certific jsseUtil.noVerificationDepth=The truststoreProvider [{0}] does not support the certificateVerificationDepth configuration option jsseUtil.trustedCertNotChecked=The validity dates of the trusted certificate with alias [{0}] were not checked as the certificate was of an unknown type jsseUtil.trustedCertNotValid=The trusted certificate with alias [{0}] and DN [{1}] is not valid due to [{2}]. Certificates signed by this trusted certificate WILL be accepted + +pemFile.noMultiPrimes=The PKCS#1 certificate is in multi-prime format and Java does not provide an API for constructing an RSA private key object from that format \ No newline at end of file diff --git a/java/org/apache/tomcat/util/net/jsse/PEMFile.java b/java/org/apache/tomcat/util/net/jsse/PEMFile.java index 9a3541f..47c8787 100644 --- a/java/org/apache/tomcat/util/net/jsse/PEMFile.java +++ b/java/org/apache/tomcat/util/net/jsse/PEMFile.java @@ -21,6 +21,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; @@ -32,6 +33,7 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; import java.util.ArrayList; import java.util.List; @@ -41,6 +43,7 @@ import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; +import org.apache.tomcat.util.buf.Asn1Parser; import org.apache.tomcat.util.codec.binary.Base64; import org.apache.tomcat.util.file.ConfigFileLoader; import org.apache.tomcat.util.res.StringManager; @@ -100,10 +103,13 @@ public class PEMFile { for (Part part : parts) { switch (part.type) { case "PRIVATE KEY": - privateKey = part.toPrivateKey(null, keyAlgorithm); + privateKey = part.toPrivateKey(null, keyAlgorithm, Format.PKCS8); break; case "ENCRYPTED PRIVATE KEY": - privateKey = part.toPrivateKey(password, keyAlgorithm); + privateKey = part.toPrivateKey(password, keyAlgorithm, Format.PKCS8); + break; + case "RSA PRIVATE KEY": + privateKey = part.toPrivateKey(null, keyAlgorithm, Format.PKCS1); break; case "CERTIFICATE": case "X509 CERTIFICATE": @@ -129,11 +135,21 @@ public class PEMFile { return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(decode())); } - public PrivateKey toPrivateKey(String password, String keyAlgorithm) throws GeneralSecurityException, IOException { - KeySpec keySpec; + public PrivateKey toPrivateKey(String password, String keyAlgorithm, Format format) + throws GeneralSecurityException, IOException { + KeySpec keySpec = null; if (password == null) { - keySpec = new PKCS8EncodedKeySpec(decode()); + switch (format) { + case PKCS1: { + keySpec = parsePKCS1(decode()); + break; + } + case PKCS8: { + keySpec = new PKCS8EncodedKeySpec(decode()); + break; + } + } } else { EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decode()); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(privateKeyInfo.getAlgName()); @@ -164,5 +180,33 @@ public class PEMFile { throw exception; } + + + private RSAPrivateCrtKeySpec parsePKCS1(byte[] source) { + Asn1Parser p = new Asn1Parser(source); + + // https://en.wikipedia.org/wiki/X.690#BER_encoding + // https://tools.ietf.org/html/rfc8017#page-55 + + // Type + p.parseTag(0x30); + // Length + p.parseFullLength(); + + BigInteger version = p.parseInt(); + if (version.intValue() == 1) { + // JRE doesn't provide a suitable constructor for multi-prime + // keys + throw new IllegalArgumentException(sm.getString("pemFile.noMultiPrimes")); + } + return new RSAPrivateCrtKeySpec(p.parseInt(), p.parseInt(), p.parseInt(), p.parseInt(), + p.parseInt(), p.parseInt(), p.parseInt(), p.parseInt()); + } + } + + + private enum Format { + PKCS1, + PKCS8 } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index e898ca8..d1f80e7 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -65,6 +65,13 @@ Windows operating systems that required multiple smaller pollsets to be used are no longer supported. (markt) </scode> + <fix> + <bug>63524</bug>: Improve the handling of PEM file based keys and + certificates that do not include a full certificate chain when + configuring the internal, in-memory key store. Improve the handling of + PKCS#1 formatted private keys when configuring the internal, in-memory + key store. (markt) + </fix> </changelog> </subsection> <subsection name="Cluster"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org