Class MultiRecipientDataSourceBuilder

java.lang.Object
zeroecho.sdk.guard.MultiRecipientDataSourceBuilder
All Implemented Interfaces:
AutoCloseable, Destroyable, DataContentBuilder<MultiRecipientContent>

public final class MultiRecipientDataSourceBuilder extends Object implements DataContentBuilder<MultiRecipientContent>, Destroyable, AutoCloseable
Builds a data content pipeline that supports multiple recipients and delegates payload encryption to AES or ChaCha builders.

This builder is responsible only for the multi-recipient envelope: generating a content-encryption key (CEK), constructing recipient entries that can recover the same CEK, and scanning those entries on decrypt to recover it. Once the CEK is ready, the builder delegates the actual payload encryption or decryption to an injected symmetric data-content builder (AES or ChaCha), which is responsible for its own header and ciphertext framing.

Envelope layout. The stream begins with a recipient table owned by this builder:

  • pack7 recipientCount
  • for each recipient: UTF-8 entryId, then length-prefixed entryBlob
  • then follows the symmetric builder's header and payload stream
The symmetric stage parses its own header just as in the KEM-oriented pipeline.
  • Method Details

    • builder

      public static MultiRecipientDataSourceBuilder builder(ZeroEchoSession session)
      Creates a new builder for constructing a multi-recipient data source.

      The returned builder is initialized with safe defaults:

      • Payload key length: 32 bytes (suitable for AES-256).
      • Maximum recipients: 64.

      Callers can customize these parameters before building the final data source.

      Parameters:
      session - active session used to create recipient cryptographic contexts
      Returns:
      a fresh MultiRecipientDataSourceBuilder instance with default settings
    • withAes

      Selects AES payload processing by installing an AES data content builder.

      The AES builder owns its parameters and header. This method clears any previously installed ChaCha builder.

      Parameters:
      builder - configured AES data content builder
      Returns:
      this builder
      Throws:
      NullPointerException - if builder is null
    • shuffle

      public void shuffle()
      Randomly permutes the list of recipients using a default source of randomness. All permutations occur with approximately equal likelihood.
    • withChaCha

      Selects ChaCha20-Poly1305 payload processing by installing a ChaCha data content builder.

      The ChaCha builder owns its parameters and header. This method clears any previously installed AES builder.

      Parameters:
      builder - configured ChaCha data content builder
      Returns:
      this builder
      Throws:
      NullPointerException - if builder is null
    • payloadKeyBytes

      public MultiRecipientDataSourceBuilder payloadKeyBytes(int keyBytes)
      Sets the CEK length in bytes that will be generated on encrypt and expected on decrypt.

      Typical values: 16 or 32 for AES, 32 for ChaCha20-Poly1305.

      Parameters:
      keyBytes - CEK length in bytes
      Returns:
      this builder
      Throws:
      IllegalArgumentException - if keyBytes is not positive
    • addPasswordRecipient

      public MultiRecipientDataSourceBuilder addPasswordRecipient(char[] password, int iterations, int saltLen, int kekBytes)
      Adds a password-based recipient that derives a KEK via PBKDF2(HMAC-SHA-256) and AES-GCM-wraps the CEK.
      Parameters:
      password - the password; the caller should clear the array after use
      iterations - PBKDF2 iteration count; must be at least 10000
      saltLen - salt length in bytes
      kekBytes - derived KEK length; exactly 16 or 32 bytes
      Returns:
      this builder
      Throws:
      NullPointerException - if password is null
      IllegalArgumentException - if iterations is below 10000 or kekBytes is not exactly 16 or 32
    • addRecipient

      public MultiRecipientDataSourceBuilder addRecipient(KemContext kem, int kekBytes, int saltLen)
      Adds a universal recipient backed by a caller-supplied KemContext.

      The context is used once to KemContext.encapsulate() and then closed. The entry format and key wrapping are: kemCt || hkdfSalt || wrapIv || gcmWrappedCEK. The entry id is "KEM:<kemAlgorithmId>:GCM-WRAP" so that default openers still match.

      Parameters:
      kem - configured context for KeyUsage.ENCAPSULATE
      kekBytes - derived KEK length; exactly 16 or 32 bytes
      saltLen - HKDF salt length in bytes
      Returns:
      this builder
      Throws:
      NullPointerException - if kem is null
      IllegalArgumentException - if kekBytes is not exactly 16 or 32
    • addRecipient

      Adds a universal recipient backed by a caller-supplied EncryptionContext.

      The context must be configured for KeyUsage.ENCRYPT with the appropriate asymmetric algorithm (e.g., RSA-OAEP, ElGamal, ECIES). The CEK is encrypted by streaming it through EncryptionContext.attach(java.io.InputStream). The entry id is "ENC:" + ctx.algorithm().id().

      The context is consumed once and then closed by the builder.

      Parameters:
      ctx - the encryption context configured for ENCRYPT
      Returns:
      this builder
      Throws:
      NullPointerException - if ctx is null
    • addPasswordRecipientDecoy

      public MultiRecipientDataSourceBuilder addPasswordRecipientDecoy(char[] password, int iterations, int saltLen, int kekBytes)
      Adds a password-based decoy recipient that derives a KEK via PBKDF2(HMAC-SHA-256) and AES-GCM-wraps the CEK.
      Parameters:
      password - the password; the caller should clear the array after use
      iterations - PBKDF2 iteration count; must be at least 10000
      saltLen - salt length in bytes
      kekBytes - derived KEK length; exactly 16 or 32 bytes
      Returns:
      this builder
      Throws:
      NullPointerException - if password is null
      IllegalArgumentException - if iterations is below 10000 or kekBytes is not exactly 16 or 32
    • addRecipientDecoy

      public MultiRecipientDataSourceBuilder addRecipientDecoy(KemContext kem, int kekBytes, int saltLen)
      Adds a universal decoy recipient backed by a caller-supplied KemContext.

      The context is used once to KemContext.encapsulate() and then closed. The entry format and key wrapping are: kemCt || hkdfSalt || wrapIv || gcmWrappedCEK. The entry id is "KEM:<kemAlgorithmId>:GCM-WRAP" so that default openers still match.

      Parameters:
      kem - configured context for KeyUsage.ENCAPSULATE
      kekBytes - derived KEK length; exactly 16 or 32 bytes
      saltLen - HKDF salt length in bytes
      Returns:
      this builder
      Throws:
      NullPointerException - if kem is null
      IllegalArgumentException - if kekBytes is not exactly 16 or 32
    • addRecipientDecoy

      public MultiRecipientDataSourceBuilder addRecipientDecoy(EncryptionContext ctx)
      Adds a universal decoy recipient backed by a caller-supplied EncryptionContext.

      The context must be configured for KeyUsage.ENCRYPT with the appropriate asymmetric algorithm (e.g., RSA-OAEP, ElGamal, ECIES). The CEK is encrypted by streaming it through EncryptionContext.attach(java.io.InputStream). The entry id is "ENC:" + ctx.algorithm().id().

      The context is consumed once and then closed by the builder.

      Parameters:
      ctx - the encryption context configured for ENCRYPT
      Returns:
      this builder
      Throws:
      NullPointerException - if ctx is null
    • unlockWith

      public MultiRecipientDataSourceBuilder unlockWith(UnlockMaterial material)
      Supplies the unlocking material for decryption, either a private key or a password.

      Only a single unlocking material is used. During decryption each opener attempts to recover the CEK from the recipient entries using this material. The builder borrows the object for the lifetime of the built decrypting content and never destroys it. The caller retains ownership and must destroy password material after the returned content and stream are no longer used.

      Parameters:
      material - caller-owned unlocking material borrowed until decryption completes
      Returns:
      this builder
      Throws:
      NullPointerException - if material is null
    • addOpener

      Adds a custom recipient opener used during decryption when scanning recipient entries.

      If no openers are added explicitly, the default set is installed during build(boolean) and covers KEM, RSA-OAEP, ElGamal, and PBKDF2 password recipients.

      The builder takes ownership of the opener. The opener must be reusable across all recipient entries and is closed after scanning or when the built content is abandoned.

      Parameters:
      opener - reusable opener to add
      Returns:
      this builder
      Throws:
      NullPointerException - if opener is null
    • headerLimits

      public MultiRecipientDataSourceBuilder headerLimits(int maxRecipients, int maxEntryLen)
      Applies defensive limits for parsing the recipient table.

      All values are clamped to at least 1. These limits guard against resource exhaustion from malformed input.

      Parameters:
      maxRecipients - maximum number of recipient entries allowed
      maxEntryLen - maximum size in bytes for a single recipient entry blob
      Returns:
      this builder
    • build

      public MultiRecipientContent build(boolean encrypt)
      Builds either an encrypting or a decrypting content wrapper depending on the encrypt flag.

      When encrypt is true, the wrapper emits the recipient table followed by the symmetric builder's header and ciphertext. When encrypt is false, the wrapper unlocks the CEK from the first matching recipient entry and streams the symmetric plaintext.

      Default openers are installed automatically if none were added explicitly. They support KEM, RSA-OAEP, ElGamal with PKCS1 padding, and, when password unlock material is selected, PBKDF2 password recipients. Password decryption requires explicit session PBKDF2 limits.

      Specified by:
      build in interface DataContentBuilder<MultiRecipientContent>
      Parameters:
      encrypt - true to build an encrypting content source, false to build a decrypting one
      Returns:
      an encrypting or decrypting one-shot content wrapper; callers must close an abandoned result
      Throws:
      IllegalStateException - if no symmetric builder is selected, if encryption is requested with no recipients, or if decryption is requested without unlock material
    • destroy

      public void destroy() throws DestroyFailedException
      Destroys recipient secrets still owned by this builder.

      Recipients transferred to a successfully built encrypting content object are owned and destroyed by that object instead.

      Specified by:
      destroy in interface Destroyable
      Throws:
      DestroyFailedException - if recipient cleanup fails
    • isDestroyed

      public boolean isDestroyed()
      Reports whether this builder has released its owned recipient secrets.
      Specified by:
      isDestroyed in interface Destroyable
      Returns:
      true after close() or destroy()
    • close

      public void close() throws IOException
      Releases recipient and opener resources not transferred by build(boolean).
      Specified by:
      close in interface AutoCloseable
      Throws:
      IOException - if recipient cleanup fails