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 63310d1432d3e2216978c19ee577981a3c6a73da Author: Mark Thomas <ma...@apache.org> AuthorDate: Thu Jan 9 17:54:04 2020 +0000 Add support for RFC 5915, unencrypted EC key files with JSSE based TLS --- java/org/apache/tomcat/util/buf/Asn1Parser.java | 6 ++ java/org/apache/tomcat/util/buf/Asn1Writer.java | 95 ++++++++++++++++++++++ .../tomcat/util/net/jsse/LocalStrings.properties | 3 +- java/org/apache/tomcat/util/net/jsse/PEMFile.java | 94 ++++++++++++++++++++- webapps/docs/changelog.xml | 4 + 5 files changed, 199 insertions(+), 3 deletions(-) diff --git a/java/org/apache/tomcat/util/buf/Asn1Parser.java b/java/org/apache/tomcat/util/buf/Asn1Parser.java index 35fe161..e8e5727 100644 --- a/java/org/apache/tomcat/util/buf/Asn1Parser.java +++ b/java/org/apache/tomcat/util/buf/Asn1Parser.java @@ -83,6 +83,12 @@ public class Asn1Parser { } + public void parseBytes(byte[] dest) { + System.arraycopy(source, pos, dest, 0, dest.length); + pos += dest.length; + } + + private int next() { return source[pos++] & 0xFF; } diff --git a/java/org/apache/tomcat/util/buf/Asn1Writer.java b/java/org/apache/tomcat/util/buf/Asn1Writer.java new file mode 100644 index 0000000..f485517 --- /dev/null +++ b/java/org/apache/tomcat/util/buf/Asn1Writer.java @@ -0,0 +1,95 @@ +/* + * 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; + +public class Asn1Writer { + + public static byte[] writeSequence(byte[]... components) { + int len = 0; + for (byte[] component : components) { + len += component.length; + } + + byte[] combined = new byte[len]; + int pos = 0; + for (byte[] component : components) { + System.arraycopy(component, 0, combined, pos, component.length); + pos += component.length; + } + + return writeTag((byte) 0x30, combined); + } + + + public static byte[] writeInteger(int value) { + // How many bytes required to write the value? No more than 4 for int. + int valueSize = 1; + while ((value >> (valueSize * 8)) > 0) { + valueSize++; + } + + byte[] valueBytes = new byte[valueSize]; + int i = 0; + while (valueSize > 0) { + valueBytes[i] = (byte) (value >> (8 * (valueSize - 1))); + value = value >> 8; + valueSize--; + i++; + } + + return writeTag((byte) 0x02, valueBytes); + } + + public static byte[] writeOctetString(byte[] data) { + return writeTag((byte) 0x04, data); + } + + public static byte[] writeTag(byte tagId, byte[] data) { + int dataSize = data.length; + // How many bytes to write the length? + int lengthSize = 1; + if (dataSize >127) { + // 1 byte we have is now used to record how many bytes we need to + // record a length > 127 + // Result is lengthSize = 1 + number of bytes to record length + do { + lengthSize++; + } + while ((dataSize >> (lengthSize * 8)) > 0); + } + + // 1 for tag + lengthSize + dataSize + byte[] result = new byte[1 + lengthSize + dataSize]; + result[0] = tagId; + if (dataSize < 128) { + result[1] = (byte) dataSize; + } else { + // lengthSize is 1 + number of bytes for length + result[1] = (byte) (127 + lengthSize); + int i = lengthSize; + while (dataSize > 0) { + result[i] = (byte) (dataSize & 0xFF); + dataSize = dataSize >> 8; + i--; + } + } + + System.arraycopy(data, 0, result, 1 + lengthSize, data.length); + + return result; + } +} diff --git a/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties b/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties index 0f9b29e..a129ca1 100644 --- a/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties +++ b/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties @@ -31,4 +31,5 @@ jsseUtil.noVerificationDepth=The truststoreProvider [{0}] does not support the c 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 +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 +pemFile.notValidRFC5915=The provided key file does not conform to RFC 5915 \ 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 47c8787..a0962e7 100644 --- a/java/org/apache/tomcat/util/net/jsse/PEMFile.java +++ b/java/org/apache/tomcat/util/net/jsse/PEMFile.java @@ -44,6 +44,7 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import org.apache.tomcat.util.buf.Asn1Parser; +import org.apache.tomcat.util.buf.Asn1Writer; import org.apache.tomcat.util.codec.binary.Base64; import org.apache.tomcat.util.file.ConfigFileLoader; import org.apache.tomcat.util.res.StringManager; @@ -57,6 +58,9 @@ public class PEMFile { private static final StringManager sm = StringManager.getManager(PEMFile.class); + private static final byte[] OID_EC_PUBLIC_KEY = + new byte[] { 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 }; + private String filename; private List<X509Certificate> certificates = new ArrayList<>(); private PrivateKey privateKey; @@ -105,6 +109,9 @@ public class PEMFile { case "PRIVATE KEY": privateKey = part.toPrivateKey(null, keyAlgorithm, Format.PKCS8); break; + case "EC PRIVATE KEY": + privateKey = part.toPrivateKey(null, "EC", Format.RFC5915); + break; case "ENCRYPTED PRIVATE KEY": privateKey = part.toPrivateKey(password, keyAlgorithm, Format.PKCS8); break; @@ -149,6 +156,10 @@ public class PEMFile { keySpec = new PKCS8EncodedKeySpec(decode()); break; } + case RFC5915: { + keySpec = new PKCS8EncodedKeySpec(rfc5915ToPkcs8(decode())); + break; + } } } else { EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decode()); @@ -182,13 +193,91 @@ public class PEMFile { } + /* + * RFC5915: SEQ + * INT value = 1 + * OCTET STRING len = 32 bytes + * [0] + * OID named EC + * [1] + * BIT STRING len = 520 bits + * + * PKCS8: SEQ + * INT value = 0 + * SEQ + * OID 1.2.840.10045.2.1 (EC public key) + * OID named EC + * OCTET STRING + * SEQ + * INT value = 1 + * OCTET STRING len = 32 bytes + * [1] + * BIT STRING len = 520 bits + * + */ + private byte[] rfc5915ToPkcs8(byte[] source) { + // Parse RFC 5915 format EC private key + Asn1Parser p = new Asn1Parser(source); + + // Type (sequence) + p.parseTag(0x30); + // Length + p.parseFullLength(); + + // Version + BigInteger version = p.parseInt(); + if (version.intValue() != 1) { + throw new IllegalArgumentException(sm.getString("pemFile.notValidRFC5915")); + } + + // Private key + p.parseTag(0x04); + int privateKeyLen = p.parseLength(); + byte[] privateKey = new byte[privateKeyLen]; + p.parseBytes(privateKey); + + // [0] OID + p.parseTag(0xA0); + int oidLen = p.parseLength(); + byte[] oid = new byte[oidLen]; + p.parseBytes(oid); + if (oid[0] != 0x06) { + throw new IllegalArgumentException(sm.getString("pemFile.notValidRFC5915")); + } + + // [1] Public key + p.parseTag(0xA1); + int publicKeyLen = p.parseLength(); + byte[] publicKey = new byte[publicKeyLen]; + p.parseBytes(publicKey); + if (publicKey[0] != 0x03) { + throw new IllegalArgumentException(sm.getString("pemFile.notValidRFC5915")); + } + + + // Write out PKCS#8 format + return Asn1Writer.writeSequence( + Asn1Writer.writeInteger(0), + Asn1Writer.writeSequence( + OID_EC_PUBLIC_KEY, + oid), + Asn1Writer.writeOctetString( + Asn1Writer.writeSequence( + Asn1Writer.writeInteger(1), + Asn1Writer.writeOctetString(privateKey), + Asn1Writer.writeTag((byte) 0xA1, publicKey)) + ) + ); + } + + 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 + // Type (sequence) p.parseTag(0x30); // Length p.parseFullLength(); @@ -207,6 +296,7 @@ public class PEMFile { private enum Format { PKCS1, - PKCS8 + PKCS8, + RFC5915 } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 6e400de..921e03e 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -125,6 +125,10 @@ <bug>64007</bug>: Cancel selection key in poller before wrapper close to avoid possible deadlock. (remm) </fix> + <add> + Add support for RFC 5915 formatted, unencrypted EC key files when using + a JSSE based TLS connector. (markt) + </add> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org