Class WebPushEncryptor

java.lang.Object
com.iizix.server.push.engine.transport.WebPushEncryptor

public final class WebPushEncryptor extends Object
Message encryption for Web Push, RFC 8291 with the aes128gcm content coding of RFC 8188: ECDH on P-256 against the subscription's p256dh, HKDF-SHA-256 with the subscription's auth secret, one AES-128-GCM record.

Rebuilt, not inherited. The severed HttpEce/AbstractPushService pair carried recorded defects; this class fixes them rather than copying them:

  • IIZI-PUSH-001: a payload-less send dereferenced a null payload. Here an empty plaintext is a valid input and encrypts to a header plus one padded record.
  • IIZI-PUSH-007: the salt was accepted unvalidated. Here every input length is checked before any key is derived: the receiver's public key is the 65-byte uncompressed point, the authentication secret 16 bytes, the salt 16 bytes; and the public entry point never accepts a salt or a key pair at all - it draws a fresh 16-byte salt and a fresh ephemeral key pair from SecureRandom per message.
  • IIZI-PUSH-008: the padding delimiter and the record size were not related. Here the record size is fixed at 4096 and the plaintext is limited so that plaintext, the 0x02 delimiter and the 16-byte tag fit one record; a larger payload is refused before encryption with the limit in the message, because the push services reject bodies over 4096 bytes anyway.
Only aes128gcm is produced. The legacy aesgcm coding, which the old code kept for backward compatibility, has not been needed by any browser since Chrome 60 and Firefox 55 and is not emitted.

HKDF is the RFC 5869 extract-and-expand over HmacSHA256 from the JDK; AES-GCM is the JDK cipher; only the curve arithmetic and key loading use BouncyCastle, the same routines VAPIDKeys and PushProps.verify already rely on.

Known-answer coverage: the test fragment reproduces RFC 8291 Appendix A byte for byte through the package-private encrypt(byte[],byte[],byte[],KeyPair,byte[]) entry.

Author:
Christopher Mindus
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final int
    The authentication secret length in bytes.
    static final int
    The length of the content coding header: salt (16) + rs (4) + idlen (1) + keyid (65).
    static final int
    The largest plaintext that fits one record beside the delimiter and the tag, and keeps the whole body within the 4096 bytes the push services accept: 4096 - HEADER_LENGTH - 1 - 16.
    static final int
    The record size written to the content coding header: 4096 bytes.
    static final int
    The salt length in bytes.
  • Method Summary

    Modifier and Type
    Method
    Description
    static byte[]
    encrypt(byte[] plaintext, byte[] receiverPublicKey, byte[] authSecret)
    Encrypts a message for a subscription with a fresh salt and a fresh ephemeral key pair.
    static void
    Makes sure the BouncyCastle provider is registered: the key loaders in com.iizix.push.vapid.Utils name it, and the server registers it during its own start-up but a test fork does not.
  • Field Details

    • RECORD_SIZE

      public static final int RECORD_SIZE
      The record size written to the content coding header: 4096 bytes.
      See Also:
    • HEADER_LENGTH

      public static final int HEADER_LENGTH
      The length of the content coding header: salt (16) + rs (4) + idlen (1) + keyid (65).
      See Also:
    • MAX_PLAINTEXT

      public static final int MAX_PLAINTEXT
      The largest plaintext that fits one record beside the delimiter and the tag, and keeps the whole body within the 4096 bytes the push services accept: 4096 - HEADER_LENGTH - 1 - 16.
      See Also:
    • SALT_LENGTH

      public static final int SALT_LENGTH
      The salt length in bytes.
      See Also:
    • AUTH_LENGTH

      public static final int AUTH_LENGTH
      The authentication secret length in bytes.
      See Also:
  • Method Details

    • ensureProvider

      public static void ensureProvider()
      Makes sure the BouncyCastle provider is registered: the key loaders in com.iizix.push.vapid.Utils name it, and the server registers it during its own start-up but a test fork does not.
    • encrypt

      public static byte[] encrypt(byte[] plaintext, byte[] receiverPublicKey, byte[] authSecret) throws GeneralSecurityException
      Encrypts a message for a subscription with a fresh salt and a fresh ephemeral key pair.
      Parameters:
      plaintext - The message, at most MAX_PLAINTEXT bytes; may be empty.
      receiverPublicKey - The subscription's p256dh: the 65-byte uncompressed point.
      authSecret - The subscription's auth: 16 bytes.
      Returns:
      The body to POST: the content coding header followed by the single record.
      Throws:
      IllegalArgumentException - If an input has the wrong length.
      GeneralSecurityException - For a cryptographic failure.