skip to content

Your REST Assured suite pins a CA with RestAssured.trustStore(...) but accepts a certificate issued for another host. Why?

level: seniorimportance: should knowfreq 34%

answer

  1. the shortcut is not neutral
  2. backward-compatibility clause in the setter
  3. allowAllHostnames applied after the store
  4. strictHostnames restores the default verifier

basics

~20 s

The path-and-password trustStore and keyStore shortcuts finish by applying allowAllHostnames() for backward compatibility, replacing the strict verifier. Your chain is checked against the pinned authority, but the certificate's subject is never compared with the host you called.

solid answer

~40 s

`RestAssured.trustStore(path, password)` does not only set trust material. It routes through a private helper that stores the updated `SSLConfig` with `allowAllHostnames()` chained on, and the source comments that this is for backward compatibility. `RestAssured.keyStore(path, password)`, `given().trustStore(path, password)` and `given().keyStore(path, password)` all do the same. The result is that chain validation against your authority still runs, but `ALLOW_ALL_HOSTNAME_VERIFIER` has replaced the default `STRICT_HOSTNAME_VERIFIER`, so a certificate your CA issued for `internal-batch.bakery.example` is accepted on a call to `https://orders.bakery.example/preorders`. The fix is to build the config yourself: `sslConfig().trustStore("bakery-ca.jks", "changeit").strictHostnames()`. The `KeyStore` overloads and `SSLConfig` itself leave the verifier alone, and a fresh `SSLConfig` starts strict. Prove it either way with one negative test against a host whose certificate names something else, because a passing suite gives you no signal that the check was skipped.

code

java · 10 lines
java
import io.restassured.RestAssured;

import static io.restassured.config.SSLConfig.sslConfig;

RestAssured.baseURI = "https://orders.bakery.example";

RestAssured.config = RestAssured.config()
    .sslConfig(sslConfig()
        .trustStore("bakery-ca.jks", "changeit")
        .strictHostnames());

go deeper

for a junior

Know that REST Assured can be pointed at a private trust store, and that a JKS path given as a string is looked up on the classpath before the file system.

for a middle

Explain the two halves of server identity — chain validation and hostname matching — and name which REST Assured calls set each. Be able to spell out what strictHostnames() and allowAllHostnames() change.

for a senior

Diagnose the silent case: a suite that should have failed and did not. Show how you read the configured verifier back, how you prove trust with a negative test, and how call ordering on an immutable SSLConfig decides the outcome.

for a principal

Decide how trust material is provisioned and proven across environments, and set the convention that any relaxation is explicit in code rather than inherited from a convenience method's compatibility clause.

## What the shortcut really does `RestAssured.trustStore(String pathToJks, String password)` looks like a narrow setter, and it is not. It validates the password and then calls a private helper, `applyTrustStore`, which takes the current `SSLConfig`, applies the trust store to it, and assigns the result **with `allowAllHostnames()` chained on the end**. The source carries the reason in a one-line comment: allow all host names to be backward-compatible. `applyKeyStore` does the same thing, and the request-level `given().trustStore(path, password)`, `given().keyStore(path, password)` and their `File` overloads all repeat it. So the call you reached for to make trust *stricter* — pin the bakery pre-order API to your own certificate authority — quietly replaced the default `STRICT_HOSTNAME_VERIFIER` with `ALLOW_ALL_HOSTNAME_VERIFIER`. Half of the server-identity check is gone. The chain is still verified against your CA, so a certificate signed by anyone else is rejected; but a certificate that your CA issued for `internal-batch.bakery.example` is now accepted on a call to `https://orders.bakery.example/preorders`. ## Which calls relax hostnames and which do not | Call | Trust or key material set | Hostname verifier afterwards | |---|---|---| | `RestAssured.trustStore(path, password)` | trust store | `ALLOW_ALL_HOSTNAME_VERIFIER` | | `given().trustStore(path, password)` | trust store | `ALLOW_ALL_HOSTNAME_VERIFIER` | | `RestAssured.keyStore(path, password)` | key store | `ALLOW_ALL_HOSTNAME_VERIFIER` | | `given().trustStore(KeyStore)` | trust store | unchanged — strict unless you changed it | | `sslConfig().trustStore(path, password)` | trust store | unchanged — strict unless you changed it | The pattern is that the **path-and-password convenience methods** carry the compatibility clause, while the `KeyStore` overloads and `SSLConfig` itself do not. `SSLConfig`'s no-argument constructor starts from `STRICT_HOSTNAME_VERIFIER`, so building the config yourself gives you the strict behaviour by default. ## How to confirm the diagnosis The symptom is a test that should have failed and did not, which is the hardest kind to notice. Confirm it rather than guess: - Read back `RestAssured.config().getSSLConfig().getX509HostnameVerifier()` after your setup runs and check which verifier instance you actually have. - Point one deliberate test at a host whose certificate names something else and assert that the call fails. A trust configuration you never proved is a trust configuration you do not have. - Check the order of your setup code: a later `trustStore(path, password)` re-applies `allowAllHostnames()` even if an earlier line called `strictHostnames()`. - Look for the same shortcut in a `RequestSpecBuilder`; `setTrustStore(String, String)` routes through the same request-level method. - Remember that `SSLConfig` is immutable and every setter returns a new instance, so the value that survives is whatever the last assignment to the config held. - Search the suite for every `trustStore` and `keyStore` call rather than only the one in the class you are debugging; a single stray shortcut in shared setup re-relaxes everything. ## The fix Build the `SSLConfig` explicitly and finish with `strictHostnames()`: ```java RestAssured.config = RestAssured.config() .sslConfig(sslConfig().trustStore("bakery-ca.jks", "changeit").strictHostnames()); ``` `strictHostnames()` returns a new `SSLConfig` carrying `STRICT_HOSTNAME_VERIFIER`, and because `SSLConfig` is immutable the ordering is explicit: whatever you call last wins. The alternative is to load the store yourself and pass the `KeyStore` overload, `RestAssured.trustStore(keyStore)` or `given().trustStore(keyStore)`, neither of which touches the verifier — though note the static `KeyStore` form builds a fresh `SSLConfig` and so discards a key store you had set earlier. Two further points are worth having ready: 1. `SSLConfig` is only consulted when the request URI's scheme is `https` **and** the config reports `isUserConfigured()`. Setting nothing leaves the platform default trust in place; setting anything at all flips that flag. 2. A store given as a `String` path is resolved from the classpath first and only then from the file system, while a `File` is always the file system. A stale copy of `bakery-ca.jks` on the test classpath will win over the one you edited on disk, which produces its own confusing failures. ## Why this matters beyond the one bug Hostname verification is the check that ties a valid certificate to the host you meant to reach. Losing it turns a pinned CA into a much weaker control: any certificate that authority ever issued — for a different service, a different tenant, an internal tool — is now accepted for the bakery pre-order API. Nothing in the response, the log or the assertion output records that the check was skipped, so it will not appear in a failing build. The habit worth carrying out of this question is to prove a trust setting with a negative test rather than to trust the name of the method that set it.

  • Which trust-store call does not relax hostname verification?
    The `KeyStore` overloads — `RestAssured.trustStore(keyStore)` and `given().trustStore(keyStore)` — and anything you set on `SSLConfig` directly. Only the path-and-password convenience methods carry the `allowAllHostnames()` clause. Note that the static `KeyStore` form rebuilds the `SSLConfig` from scratch, so a key store set earlier is discarded.
  • How would you prove a trust setting is really in force before shipping the suite?
    Write one deliberate negative test against a host whose certificate names something else and assert the call fails. Reading `config().getSSLConfig().getX509HostnameVerifier()` back confirms which verifier is installed, but only a failing request proves the client rejects the wrong identity.

saying these in an interview costs you the question

  • Assumes a setter named trustStore only sets trust material
  • Thinks pinning a CA implies the hostname is checked too
  • Calls strictHostnames first and a trustStore shortcut afterwards
  • Never writes a negative test to prove the trust setting works
  • Confuses chain validation with certificate subject matching