Skip to content

JWTConfig.Builder hardcodes DefaultPrivateKeyDecryptor causing FIPS compatibility issues #1707

@o-shevchenko

Description

@o-shevchenko

Summary

The JWTConfig.Builder constructor hardcodes the instantiation of DefaultPrivateKeyDecryptor on line 270, which causes issues when testing with custom PrivateKeyDecryptor implementations that use different BouncyCastle library versions.

If we exclude org.bouncycastle:bcprov-jdk18on:1.83 as described in https://github.com/box/box-java-sdk?tab=readme-ov-file#fips-140-2-compliance and add FIPS compatible libs, we can't use JWTConfig.Builder since it hardcodes DefaultPrivateKeyDecryptor in constructor, so it fails even before we set it via .privateKeyDecryptor(fipsPrivateKeyDecryptor)

org.bouncycastle.jce.provider.BouncyCastleProvider
java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
	at com.box.sdkgen.box.jwtauth.JWTConfig$Builder.<init>(JWTConfig.java:270)

Current Behavior

In JWTConfig.java (line 270):

public Builder(
    String clientId,
    String clientSecret,
    String jwtKeyId,
    String privateKey,
    String privateKeyPassphrase) {
  this.clientId = clientId;
  this.clientSecret = clientSecret;
  this.jwtKeyId = jwtKeyId;
  this.privateKey = privateKey;
  this.privateKeyPassphrase = privateKeyPassphrase;
  this.algorithm = new EnumWrapper<JwtAlgorithm>(JwtAlgorithm.RS256);
  this.tokenStorage = new InMemoryTokenStorage();
  this.privateKeyDecryptor = new DefaultPrivateKeyDecryptor(); // <-- Hardcoded instantiation
}

Even when using .privateKeyDecryptor(customDecryptor), the DefaultPrivateKeyDecryptor is instantiated first during Builder construction, which can cause class loading failures if:

  1. The test environment uses a different BouncyCastle version (e.g., bcprov-jdk18on:1.83 for FIPS compliance)
  2. The DefaultPrivateKeyDecryptor depends on incompatible BouncyCastle classes
  3. Tests need to use FIPS-compliant cryptography providers

Expected Behavior

The DefaultPrivateKeyDecryptor should only be instantiated when actually needed (lazy initialization), or the Builder should support passing a custom PrivateKeyDecryptor from the start to avoid instantiating the default one.

Proposed Solutions

Option 1: Lazy initialization in build()

public JWTConfig build() {
  if (this.privateKeyDecryptor == null) {
    this.privateKeyDecryptor = new DefaultPrivateKeyDecryptor();
  }
  return new JWTConfig(this);
}

Option 2: Add constructor overload

public Builder(
    String clientId,
    String clientSecret,
    String jwtKeyId,
    String privateKey,
    String privateKeyPassphrase,
    PrivateKeyDecryptor privateKeyDecryptor) {
  this.clientId = clientId;
  this.clientSecret = clientSecret;
  this.jwtKeyId = jwtKeyId;
  this.privateKey = privateKey;
  this.privateKeyPassphrase = privateKeyPassphrase;
  this.algorithm = new EnumWrapper<JwtAlgorithm>(JwtAlgorithm.RS256);
  this.tokenStorage = new InMemoryTokenStorage();
  this.privateKeyDecryptor = privateKeyDecryptor != null ? privateKeyDecryptor : new DefaultPrivateKeyDecryptor();
}

Use Case

This is needed for FIPS 140-2 compliant environments where:

  • Custom PrivateKeyDecryptor implementations use FIPS-certified BouncyCastle providers
  • Tests require testImplementation("org.bouncycastle:bcprov-jdk18on:1.83")
  • The DefaultPrivateKeyDecryptor class loading conflicts with the required BouncyCastle version

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions