diff --git a/README.md b/README.md index b46cb10..be20e91 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ For information on the classes and types, see the [REFERENCE.md](https://github. The java_ks module uses the `keytool` and `openssl` commands. It should work on all systems with these commands. +On a host running in FIPS mode, the module creates the temporary PKCS12 file it hands to `keytool` with PBES2 and AES-256 and without an integrity MAC, because the PKCS12 defaults in OpenSSL 1.x (RC2 and 3DES) and the key derivation for the PKCS12 MAC are not FIPS approved. Without this, importing a private key fails with `encrypt error` (OpenSSL 1.1.1) or `mac generation error` (OpenSSL 3 whose FIPS provider has no PKCS12 KDF, such as the one bundled with the FIPS builds of the OpenVox agent). `keytool` reads PBES2 since Java 8u301 and 11.0.12. Outside FIPS mode the file is created as before. + Java 7 is supported as of 1.0.0. Developed against IBM Java 6 on AIX. Other versions may be unsupported. diff --git a/lib/puppet/provider/java_ks/keytool.rb b/lib/puppet/provider/java_ks/keytool.rb index 59832a0..36405d3 100644 --- a/lib/puppet/provider/java_ks/keytool.rb +++ b/lib/puppet/provider/java_ks/keytool.rb @@ -30,10 +30,30 @@ def to_pkcs12(path) chain_certs = get_chain(certificate) x509_cert = chain_certs.shift end - pkcs12 = OpenSSL::PKCS12.create(password, @resource[:name], pkey, x509_cert, chain_certs) + pkcs12 = pkcs12_create(password, @resource[:name], pkey, x509_cert, chain_certs) File.open(path, 'wb') { |f| f.print pkcs12.to_der } end + # OpenSSL's PKCS12 defaults do not work on a host running in FIPS mode. + # OpenSSL 1.x encrypts the bags with RC2-40 and 3DES, which FIPS mode refuses, + # and the PKCS12 integrity MAC is keyed with the PKCS12 KDF, which the OpenSSL 3 + # FIPS provider does not implement. In FIPS mode, ask for PBES2 with AES-256 + # explicitly (the OpenSSL 3 default, and readable by keytool since Java 8u301 + # and 11.0.12) and skip the MAC; the file only exists long enough for keytool + # to read it once, so the MAC adds nothing. Outside FIPS mode the call is + # unchanged, so the file keeps whatever format the host's OpenSSL and Java + # already agreed on. + def pkcs12_create(pass, name, pkey, cert, chain) + return OpenSSL::PKCS12.create(pass, name, pkey, cert, chain) unless fips_mode? + + Puppet.debug("java_ks: OpenSSL is in FIPS mode, creating the PKCS12 for #{name} with AES-256 and without a MAC") + OpenSSL::PKCS12.create(pass, name, pkey, cert, chain, 'AES-256-CBC', 'AES-256-CBC', nil, -1) + end + + def fips_mode? + OpenSSL.respond_to?(:fips_mode) && OpenSSL.fips_mode + end + # Keytool can only import a jceks keystore if the format is der. Generating and # importing a keystore is used to add private_key and certificate pairs. def to_der(path) diff --git a/spec/unit/puppet/provider/java_ks/keytool_spec.rb b/spec/unit/puppet/provider/java_ks/keytool_spec.rb index 47ba664..7c6d50d 100755 --- a/spec/unit/puppet/provider/java_ks/keytool_spec.rb +++ b/spec/unit/puppet/provider/java_ks/keytool_spec.rb @@ -167,6 +167,20 @@ end end + describe '#pkcs12_create' do + it 'keeps the default algorithms outside FIPS mode' do + allow(provider).to receive(:fips_mode?).and_return(false) + expect(OpenSSL::PKCS12).to receive(:create).with('puppet', 'app', 'priv_obj', 'cert_obj', []) + provider.pkcs12_create('puppet', 'app', 'priv_obj', 'cert_obj', []) + end + + it 'uses AES-256 and skips the MAC in FIPS mode' do + allow(provider).to receive(:fips_mode?).and_return(true) + expect(OpenSSL::PKCS12).to receive(:create).with('puppet', 'app', 'priv_obj', 'cert_obj', [], 'AES-256-CBC', 'AES-256-CBC', nil, -1) + provider.pkcs12_create('puppet', 'app', 'priv_obj', 'cert_obj', []) + end + end + describe '#import_ks' do it 'executes openssl and keytool with specific options' do expect(provider).to receive(:to_pkcs12).with("#{temp_dir}testing.stuff")