1

I'm trying to use TLS using a key in an HSM, in Tomcat v10.0.x .

tl;dr: See my answer below with all the steps. Remainder is left below to allow search algorithms to find it.

What I'm wondering is, a: (grand scheme of things) should this work, and b: (specifics) what did I miss in the server.xml connectors?

<Connector port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
<Connector 
  protocol="org.apache.coyote.http11.Http11NioProtocol" 
  port="8443" 
  SSLEnabled="true" 
  scheme="https" 
  secure="true" 
  clientAuth="false" 
  >
  <SSLHostConfig>
    protocols="TLSv1.2"
    >
    <Certificate 
      type="RSA" 
      certificateKeystoreType="PKCS11" 
      certificateKeystoreFile="" 
      certificateKeystoreProvider="SunPKCS11-CryptoServer"
      certificateKeystorePassword="123456" 
    />
  </SSLHostConfig>
</Connector>

When Tomcat starts, I get various NullPointerExceptions (depending on what's in the connector). Examples:

27-Oct-2021 10:03:42.669 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["https-openssl-nio-443"]
27-Oct-2021 10:03:42.869 WARNING [main] org.apache.tomcat.util.net.openssl.OpenSSLContext.init Error initializing SSL context
    java.lang.NullPointerException: Cannot read the array length because "src" is null

and

27-Oct-2021 15:16:20.406 WARNING [main] org.apache.tomcat.util.net.openssl.OpenSSLContext.init Error initializing SSL context
    java.lang.NullPointerException
            at java.base/java.util.Base64$Encoder.encode(Base64.java:289)
            at java.base/java.util.Base64$Encoder.encodeToString(Base64.java:343)
            at org.apache.tomcat.util.net.openssl.OpenSSLContext.addCertificate(OpenSSLContext.java:408)
            at org.apache.tomcat.util.net.openssl.OpenSSLContext.init(OpenSSLContext.java:250)

The underlying HSM is correctly configured and has a key/certificate available in slot 0. SunPKCS11-CryptoServer does work for the keytool. When I start tomcat, the HSM logfile is populated with information that I expect to see.

If I try to use the port via curl, I get

curl -k -v --tlsv1.2 https://127.0.0.1:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* error:1408F10B:SSL routines:ssl3_get_record:wrong version number
* stopped the pause stream!
* Closing connection 0
curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number

and

curl -k -v --tlsv1.2 https://127.0.0.1:8443
* Rebuilt URL to: https://127.0.0.1:8443/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to 127.0.0.1:8443
* stopped the pause stream!
* Closing connection 0
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to 127.0.0.1:8443

And in the log, I get "invalid character found in method name" and hex values:

27-Oct-2021 15:22:58.552 INFO [http-nio-8080-exec-1] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header
    Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.
    java.lang.IllegalArgumentException: Invalid character found in method name [0x..0x..0x.. ...]. HTTP method names must be tokens

Tried with nio and nio2.

I would welcome any suggestions.

Per dave_thompson's suggestion, I added

sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation"

Now, Tomcat starts without an exception. When I try to curl to it, however:

curl -k -v --tlsv1.2 https://127.0.0.1:8443
* Rebuilt URL to: https://127.0.0.1:8443/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
    CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS alert, Server hello (2):
* error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error
* stopped the pause stream!
* Closing connection 0
curl: (35) error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error

curl -k -v --tlsv1.3 https://127.0.0.1:8443
* Rebuilt URL to: https://127.0.0.1:8443/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
    CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Unknown (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS Unknown, Unknown (21):
* TLSv1.3 (IN), TLS alert, Server hello (2):
* error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
* stopped the pause stream!
* Closing connection 0
curl: (35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure

The Tomcat log shows nothing, and, interestingly, the HSM log file does not show any new data. I would expect to see a call to the HSM as part of the DH key agreement.

Firefox returns

An error occurred during a connection to 127.0.0.1:8443. SSL peer was unable to negotiate an acceptable set of security parameters.

Error code: SSL_ERROR_HANDSHAKE_FAILURE_ALERT

And, openssl s_client -connect localhost:8443, in case this helps. This looks like a config error on what can be negotiated; while I can identify the problem I'm not sure what changes are needed where.

---
No client certificate CA names sent
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1324 bytes and written 307 bytes
Verification error: self signed certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 3096 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 18 (self signed certificate)
---

I enabled ssl:handshake in the Tomcat instance by adding

set "JAVA_OPTS=%JAVA_OPTS% -Djavax.net.debug=ssl:handshake"

to {install}\bin\catalina.bat.

And ... i can't even.

javax.net.ssl|DEBUG|17|https-jsse-nio-8443-exec-1|2021-10-28 16:22:21.925 \
    PDT|CertificateMessage.java:262|Produced server Certificate handshake message (
"Certificates": [
  "certificate" : {
    "version"            : "v3",
    "serial number"      : "00 EB 48 4B 73 73 35 C6 D8",
    "signature algorithm": "SHA256withRSA",
    "issuer"             : "CN=localhost, OU=SysEng, O=UInc, L=Ca, S=Ca, C=US",
    "not before"         : "2021-10-28 16:20:07.000 PDT",
    "not  after"         : "2022-01-26 15:20:07.000 PST",
    "subject"            : "CN=localhost, OU=SysEng, O=UInc, L=Ca, S=Ca, C=US",
    "subject public key" : "RSA",
    "extensions"         : [
      {
        ObjectId: 2.5.29.14 Criticality=false
        SubjectKeyIdentifier [
        KeyIdentifier [
        0000: 91 84 8C 3A 49 C6 2E E4   95 D2 8F 84 97 34 2C 4E  ...:I........4,N
        0010: F2 B9 D2 D0                                        ....
        ]
        ]
      }
    ]}
]
)
javax.net.ssl|ERROR|17|https-jsse-nio-8443-exec-1|2021-10-28 16:22:21.945 \
    PDT|TransportContext.java:316|Fatal (INTERNAL_ERROR): Unsupported signature \
    algorithm: rsa_pss_rsae_sha256 (
"throwable" : {
  java.security.InvalidKeyException: RSA key must be at least 512 bytes
        at jdk.crypto.cryptoki/sun.security.pkcs11.P11PSSSignature.checkKeySize(P11PSSSignature.java:352)
        at jdk.crypto.cryptoki/sun.security.pkcs11.P11PSSSignature.engineInitSign(P11PSSSignature.java:480)


F:\Tomcat\apache-tomcat-10.0.12\bin>cxitool dev=3001@127.0.0.1 LogonPass=USR_0000,ask Group=* ListKeys

idx algo  size type     group                    name                             spec
--------------------------------------------------------------------------------------
1   RSA   3072 prv      SLOT_0000                                                 6
2   X509  0    cert     SLOT_0000                                                 7

Following up on the latest comments from the most excellent dave_thompson, I regenerated the key because I couldn't figure out why a 3072 bit key was reporting 3096 in the openssl output. I generated a 4096 bit key (and fixed a problem in the certificate -dname) and then it worked.

rip...
  • 996
  • 5
  • 20
  • 2
    The JSSE implementation should be able to use the PKCS11 provider but the OpenSSL one cannot, and is the default if 'native' is present. Try setting `JSSEImplementation` per `http://tomcat.apache.org/tomcat-10.0-doc/config/http.html#SSL_Support_-_Connector_-_NIO_and_NIO2`. Although the newish support (since 8.5) for 'interchangeable' config might affect this. Trying to talk HTTPS to 8080 (an HTTP not HTTPS connector) is wrong and should fail as yours did: 'invalid version' in a curl that uses OpenSSL (not all do) and 'invalid chars' in Tomcat. – dave_thompson_085 Oct 27 '21 at 23:42
  • And progress is made. Success eludes, but sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation" gets me further. Tomcat launches without a NPE. I'll add more to the post about new outcomes. – rip... Oct 28 '21 at 15:32
  • We have two options for the PKCS11 API with the HSM ("R2" and "R3"). Both have the same result shown above in the late edits. – rip... Oct 28 '21 at 15:55
  • I think that this is migrating to a different question now, as @dave_thompson_085 did provide a good hint. I'll add an answer and then maybe open a new question for the handshake bit, if I can't find anything relevant. – rip... Oct 28 '21 at 19:32
  • The [EC]DHE might not go to the HSM because ephemeral keys can be done in sw, they don't need protection like the static authentication key -- and is that static key really 3096-bit RSA? That's quite unusual, though it _should_ work. I would expect to see something logged for at least the 1.2->internal-error case; you could try setting sysprop `javax.net.debug=ssl:handshake` to produce (a lot) more log info. – dave_thompson_085 Oct 29 '21 at 09:40
  • Weird. HSM's tools report key size is 3072. And I did the ssl:handshake, but didn't add the text because the output didn't make sense. Specifically: with a 2048 or 3072 RSA key, "java.security.InvalidKeyException: RSA key must be at least 512 bytes". I'll add more above. – rip... Oct 29 '21 at 15:13
  • It looks like you are making progress, and this is probably a tangent, but I would also recommend configuring the file to a valid path, even though it won't actually get read with the keystore type (`NUL` on Windows, `/dev/null` on Linux). I have seen many, many libraries that incorrectly assume that a trust store or key can only be file-based; they control flow based on the file configuration instead of the store type. So providing a dummy configuration can sometime be the trick to make them work. – erickson Oct 29 '21 at 15:40
  • 512 bytes is 4096 bits, which is _consistent_; it's not obvious why this is required, but HSMs are often used because an environment wants to enforce strict policies and requiring RSA-4096 is indeed a strict policy. – dave_thompson_085 Oct 30 '21 at 05:06

1 Answers1

1

Steps for Utimaco CryptoServer:

Configure HSM to use PKCS11 R2 or R3, depending on version of the firmware. It is possible to have both R2 and R3 configurations "live" and pick one in the server.xml file.

  1. set CS_PKCS11_R2_CFG=c:\ProgramData\Utimaco\PKCS11_R2\cs_pkcs11_R2.cfg
  2. set CS_PKCS11_R3_CFG=c:\ProgramData\Utimaco\PKCS11_R3\cs_pkcs11_R3.cfg
  3. Edit the respective .cfg file to point at your HSM(s) (see documentation)
  4. Edit the "C:\Program Files\Java\ -version-\conf\security\java.security" file

security.provider.13=SunPKCS11 c:/ProgramData/Utimaco/PKCS11_R2/java_r2_sunp11.cfg security.provider.14=SunPKCS11 c:/ProgramData/Utimaco/PKCS11_R3/java_r3_sunp11.cfg

  1. Create the two java_r#_sunp11.cfg files, using this pattern:
name = CryptoServerR2
description = SunPKCS11 provider offload to CryptoServer PKCS11 R2 library
library = {path_to}\cs_pkcs11_R2.dll
# library = {path_to}\cs_pkcs11_R3.dll

# Set slotListIndex to the slot (SLOT_xxxx), where SLOT_0003 would be 3
slotListIndex = 0

attributes(*,*,CKK_EC) = {
    CKA_DERIVE = true
}
attributes(generate,*,CKK_GENERIC_SECRET) = {
    CKA_SENSITIVE = false
    CKA_EXTRACTABLE = true
}

The {path_to} will change depending on the version of the installer you used to install the tools.

  1. Generate a key in the HSM using the Java keytool. This example uses the R2 configuration from steps 4 and 5 above, be sure to correctly personalize the -dname.
somepath> keytool -genkey -keyalg RSA -keysize 4096 -keystore NONE -storetype PKCS11 \
    -storepass {passphrase} -providername SunPKCS11-CryptoServerR2 -alias tomcat_demo \
    -dname "CN=localhost,OU=SysEng,O=UInc,L=Locality,S=StateOrProvince,C=Country"

At this point, Java is configured to use the PKCS11 library via the security provider list.

  1. Configure tomcat via the server.xml file. Note that the 'ciphers' element may not be necessary or may be too loose or too restrictive.
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
    <Connector 
      protocol="org.apache.coyote.http11.Http11NioProtocol"
      sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation"
      port="8443" 
      SSLEnabled="true" 
      maxThreads="150" 
      scheme="https" 
      secure="true" 
      clientAuth="false"
      >
        <SSLHostConfig
          protocols="TLSv1.2"
          ciphers="TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
            TLS_RSA_WITH_AES_128_CBC_SHA,
            TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
            TLS_RSA_WITH_AES_128_CBC_SHA256,
            TLS_RSA_WITH_AES_128_GCM_SHA256,
            TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
            TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
            TLS_RSA_WITH_AES_256_CBC_SHA,
            TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
            TLS_RSA_WITH_AES_256_CBC_SHA256,
            TLS_RSA_WITH_AES_256_GCM_SHA384,
            TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
            TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
          >
          <Certificate 
            type="RSA" 
            certificateKeystoreType="PKCS11" 
            certificateKeystoreFile="" 
            certificateKeystoreProvider="SunPKCS11-CryptoServerR2"
            certificateKeystorePassword="{passphrase}" 
            certificateKeyAlias="tomcat_demo"
          />
        </SSLHostConfig>
    </Connector>

Use the startup.bat script to launch Tomcat and watch for exceptions in the output.

Assuming it launched, you can test the access using curl. The -k indicates "--insecure" but ignore the self-signed certificate created above.

curl -k -v --tlsv1.2 https://localhost:8443
rip...
  • 996
  • 5
  • 20
  • Note: Attempts to use a key smaller than 4096 fail with "RSA key must be at least 512 bytes". I'll let others worry about that. – rip... Oct 29 '21 at 16:13