skip to content

A SOAP 1.2 claim message crosses two intermediaries before its ultimate receiver; how should WS-Security sign and encrypt it so the Body stays protected end to end?

level: seniorimportance: should knowfreq 15%

answer

  1. TLS ends at every hop
  2. sign Body, Timestamp, relied-on headers
  3. encrypt the Body's children
  4. one Security header per role
  5. intermediaries read only their part

basics

~20 s

TLS ends at each intermediary, so the sender signs the Body, Timestamp and any header the receiver relies on, and encrypts the Body's content for the ultimate receiver's key, leaving intermediaries only the headers aimed at their own roles.

solid answer

~40 s

Each TLS connection ends at the next node, so an intermediary that terminates it sees, and could change, the plaintext. WS-Security moves protection into the message. The originator writes a `wsse:Security` header for the ultimate receiver, signs the Body, the `wsu:Timestamp` and every header the receiver acts on, and encrypts the Body's **child** content (the specification forbids encrypting the `Envelope`, `Header` or `Body` elements themselves) under a key wrapped in an `xenc:EncryptedKey` for the receiver. Anything an intermediary needs goes in a separate Security header whose `role` names that intermediary, because two Security headers may not target the same recipient. The untargeted header MUST NOT be removed before the final destination, and intermediaries must not alter signed parts. TLS can still run underneath, hiding the headers on each hop.

code

xml · 21 lines
xml
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <env:Header>
    <wsse:Security env:role="http://claims.example.org/roles/fraud-screen"
        env:mustUnderstand="true">...</wsse:Security>
    <wsse:Security env:mustUnderstand="true">
      <wsu:Timestamp wsu:Id="TS-1">...</wsu:Timestamp>
      <wsse:BinarySecurityToken wsu:Id="Cert-1" ValueType="...#X509v3">...</wsse:BinarySecurityToken>
      <xenc:EncryptedKey>
        ...
        <xenc:ReferenceList><xenc:DataReference URI="#Claim-1"/></xenc:ReferenceList>
      </xenc:EncryptedKey>
      <ds:Signature>...references #TS-1 and #Body-1...</ds:Signature>
    </wsse:Security>
  </env:Header>
  <env:Body wsu:Id="Body-1">
    <xenc:EncryptedData Id="Claim-1" Type="http://www.w3.org/2001/04/xmlenc#Element">...</xenc:EncryptedData>
  </env:Body>
</env:Envelope>

go deeper

for a junior

Recall that TLS protects one connection at a time, while WS-Security signs and encrypts parts of the message itself so protection survives intermediaries.

for a middle

Explain which parts get signed and which encrypted, why only the Body's children can be encrypted, and how role targeting gives each intermediary its own header.

for a senior

Design the protection plan for a real multi-hop flow, and diagnose signature failures caused by intermediaries that touch signed parts.

for a principal

Weigh message-level protection's cost in size, key management and fragility against terminating trust at each hop, and decide where each is worth it.

## Why TLS alone is not enough here Picture an insurance claim sent as a SOAP 1.2 message from a clinic system, through a **fraud-screening** intermediary and a **routing** intermediary, to the insurer's claims service, the **ultimate receiver**. Each hop can run TLS, but TLS protects a *connection*: it ends at the node that terminates it. Each intermediary therefore sees the whole message in clear and could read or rewrite it without the next hop noticing. WS-Security lists "end-to-end message content security and not just transport-level security" among its requirements, and defines end-to-end message level security as a message staying secure over its full route through one or more SOAP intermediaries. | Property | TLS on each hop | WS-Security in the message | |---|---|---| | Scope | one connection | the message, across every node | | Visible at an intermediary | everything | only what is left unencrypted | | Survives store-and-forward | no | yes, the protection travels with the message | | Granularity | all bytes alike | per element: Body, a header, a token | | Cost | low, widely deployed | larger messages, XML processing, key distribution | ## The protection plan, part by part | Part | Sign? | Encrypt? | For whom | |---|---|---|---| | Body content (the claim) | yes | yes, its child elements | the ultimate receiver | | `wsu:Timestamp` | yes | no | the ultimate receiver checks freshness | | Headers the receiver acts on, such as addressing headers | yes | only if confidential | the ultimate receiver | | A header only the fraud screen reads | as agreed | for the fraud screen's key | that intermediary's role | | The sender's certificate token | sign it with the message | no | anyone verifying | The core specification says producers SHOULD sign all important elements, while warning that careful thought is needed before requiring a signature over parts that might legitimately be altered in transit. ## The originator's steps 1. Create the `wsse:Security` header for the ultimate receiver. In SOAP 1.2, a header with no `role` is aimed at the ultimate receiver; set `mustUnderstand` to true. 2. Add a `wsu:Timestamp` and give the Body and Timestamp `wsu:Id` values. 3. Sign the Body, the Timestamp and the relied-on headers, referencing the signing certificate through a `wsse:SecurityTokenReference`. 4. Encrypt the Body's children: each is **replaced** by an `xenc:EncryptedData`, and an `xenc:EncryptedKey` in the Security header carries the wrapped key plus an `xenc:ReferenceList` naming what it decrypts. The specification forbids encrypting the `Envelope`, `Header` or `Body` elements themselves, so the result is still a valid SOAP envelope. 5. For data meant for the fraud screen, add a second Security header whose `role` is that intermediary's role. A whole header block, attributes included, can be hidden with a `wsse11:EncryptedHeader`, which wraps one `xenc:EncryptedData` and copies the original's `mustUnderstand`, `role` and `relay` values so SOAP processing still works. ## What the intermediaries may and may not do - Process headers aimed at roles they play, including their own Security header, and **MAY** add new Security headers for other targets. - Read anything left unencrypted; they cannot read the Body content without the receiver's key. - **Not** remove the Security header that names no role before the final destination. - Expect no guaranteed order between roles: when parts are encrypted for nodes acting in different roles, the keys and reference lists sit in different Security headers, and because SOAP does not fix the order in which roles process their headers, that order can only come from prior agreement. - **Not** change signed parts: the specification warns that signatures are fragile against modification by intermediaries, so their transformations must not affect a signed component. ## What it costs and what it leaves open - Messages grow, and every node that signs, verifies or decrypts pays XML-processing cost. - Keys must be distributed and trusted per party, not just per connection. - WS-Security leaves replay protection to timestamps and caching, and lists non-repudiation as a non-goal. - TLS remains useful underneath: it hides the headers and tokens that message-level protection leaves in clear on each hop. The specification itself warns that its examples show syntax, not secure combinations: the design above still needs a security review for the specific integration.

  • How does a sender hide an entire header block, attributes included, from intermediaries?
    WS-Security 1.1 defines `wsse11:EncryptedHeader`, which replaces the header block and contains exactly one `xenc:EncryptedData`. The `mustUnderstand`, `role` and `relay` values of the referencing Security header MUST be copied onto it, so SOAP targeting still works. The node it is aimed at decrypts it and processes the original block by the normal SOAP rules.
  • How can the original sender learn that the receiver really processed its signature?
    With signature confirmation. A responder that supports it MUST put a `wsse11:SignatureConfirmation` in the response's Security header for each top-level request signature, with `Value` set to that signature's `SignatureValue`, and include it in the response signature. The initiator SHOULD reject a response whose confirmations are missing or do not match.

saying these in an interview costs you the question

  • TLS on every hop protects the Body as well as an end-to-end signature does.
  • To hide the payload, encrypt the whole env:Body element.
  • Signing only the Body is enough; the headers the receiver acts on need nothing.
  • Every intermediary needs the ultimate receiver's decryption key.
  • Once the Body is signed and encrypted, TLS underneath adds nothing.