You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2020/01/09 17:59:16 UTC

[tomcat] 01/02: Add support for RFC 5915, unencrypted EC key files with JSSE based TLS

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org