Class AppShield

java.lang.Object
com.codename1.security.shield.AppShield

public final class AppShield extends Object

API shielding: proves to your own backend that a request came from a genuine, unmodified build of your app running on a device that has not been tampered with.

How it differs from DeviceIntegrity

DeviceIntegrity is the raw platform primitive -- it hands you a Play Integrity or App Attest blob and leaves verification, policy and enforcement to you. AppShield is the managed layer on top: the attestation is verified server side against Apple and Google, evaluated against a policy you control, and turned into a short-lived signed token your backend can check with a few lines of middleware. Both remain available and DeviceIntegrity keeps working unchanged; an app that only needs the raw blob should keep using it.

The shape of the thing
AppShield.init(new ShieldConfig()
    .protect("api.mybank.example", HostPolicy.PROTECTED));

// ...then just make requests. Protected hosts get the header and the pin check.
ConnectionRequest r = new ConnectionRequest("https://api.mybank.example/transfer", true);
NetworkManager.getInstance().addToQueueAndWait(r);

Your backend rejects any request whose token is missing, expired or unsigned. That check is where the security actually lives -- not in this class. A device the attacker fully controls can always strip a header; what it cannot do is mint a token, because the token is signed by a service the attacker does not control, on the strength of a statement from Apple or Google.

When the engine is absent

Builds without the enterprise attestation engine -- open-source builds, and any project not entitled to it -- get a working, inert implementation. isProtected() returns false, fetchToken() completes with ShieldStatus.UNPROTECTED rather than hanging, attach(ConnectionRequest) does nothing, and no request is ever blocked. The API is safe to call unconditionally; there is no need to guard call sites.

Threading

fetchToken() is asynchronous. attach(ConnectionRequest) blocks and must not be called on the EDT -- in normal use you never call it yourself, because a protected host is handled automatically on the network thread.

  • Field Details

    • REJECT_HEADER

      public static final String REJECT_HEADER

      Response header a backend sets to say it rejected the attestation token, as opposed to the user's own credentials.

      Without it a 401 or 403 is ambiguous: protected APIs normally carry ordinary user authorization too, and treating every such response as an attestation rejection would make a client re-attest through an entire login failure. Emit it only when the token itself was the problem; the value is ignored.

      See Also:
  • Method Details

    • init

      public static void init(ShieldConfig cfg)

      Initializes the shield. Call once during app startup, after Display.init.

      Safe to call in a build with no attestation engine: it logs one line and leaves the shield inert. Calling it twice is a no-op.

    • getNetworkGuard

      public static NetworkGuard getNetworkGuard()

      The shield's own NetworkGuard, for an app that has to install a guard of its own.

      NetworkManager holds a single guard and seals the slot on first install, so an app with its own guard leaves no room for the shield's. Delegating to this one is the supported way to have both. Delegate every method, not only NetworkGuard.beforeRequest(ConnectionRequest): attaching the token is the visible half of the shield, and the certificate callbacks are the half that enforces HostPolicy.isEnforcePins(). An app that forwards only beforeRequest gets tokens and no pinning, and nothing about its behaviour says so.

      final NetworkGuard shield = AppShield.getNetworkGuard();
      NetworkManager.setNetworkGuard(new NetworkGuard() {
          public void beforeRequest(ConnectionRequest r) throws IOException {
              myOwnHeaders(r);
              shield.beforeRequest(r);
          }
          public boolean isCertificateCheckRequired(String url) {
              return myOwnCheckNeeded(url) || shield.isCertificateCheckRequired(url);
          }
          public void checkCertificates(ConnectionRequest r,
                  ConnectionRequest.SSLCertificate[] c) throws IOException {
              myOwnCheck(r, c);
              shield.checkCertificates(r, c);
          }
          public String[] interestingResponseHeaders() {
              return concat(myOwnHeaderNames(), shield.interestingResponseHeaders());
          }
          public void afterResponse(ConnectionRequest r, int code, String[] headers) {
              shield.afterResponse(r, code, headers);
          }
      });
      AppShield.init(cfg);
      

      Note that interestingResponseHeaders() has to be the union of both guards' names, and that the headers array handed to afterResponse is positional against it -- so a composing guard must pass the shield the slice that corresponds to the shield's own names, in that order. Installing the shield's guard directly, by calling init(ShieldConfig) before installing anything of your own, avoids the bookkeeping entirely and is what most apps should do.

      Safe to call before init(ShieldConfig); the returned guard reads the configuration live rather than capturing it.

    • isProtected

      public static boolean isProtected()
      True when a real attestation engine is present and available. False in an open-source or unentitled build, and in the simulator unless simulation is switched on.
    • getEngineName

      public static String getEngineName()
      The active engine's name, for diagnostics and support logs.
    • getConfig

      public static ShieldConfig getConfig()
      The configuration passed to init(ShieldConfig), or defaults if it has not been called.
    • fetchToken

      public static AsyncResource<ShieldToken> fetchToken()
      Fetches a time-limited token, reusing the cached one when it is still good.
    • fetchToken

      public static AsyncResource<ShieldToken> fetchToken(String bindingData)

      Fetches a token bound to specific request data.

      Binding ties the token to one request, so a token lifted off a captured request cannot be replayed against a different one. Worth the extra round trip on the calls that matter -- a transfer, a password change -- and not worth it on the rest, which should use the plain fetchToken().

      Parameters:
      bindingData - the data to bind to, typically a digest of the request body
    • invalidateToken

      public static void invalidateToken()
      Discards any cached token. Call this when your backend rejects a token, so the next request re-attests rather than replaying the token that was just refused.
    • attach

      public static void attach(ConnectionRequest request) throws ShieldException

      Attaches the attestation header to a request.

      Blocks while a token is fetched, so it must be called on a network thread. Requests to hosts registered via ShieldConfig.protect(String, HostPolicy) are handled automatically and do not need this; use it for a request built outside the normal path.

      Honours the host's FailureMode: under FailureMode.OPEN a token failure leaves the request untouched, under FailureMode.CLOSED it propagates.

      Throws:
      ShieldException
    • headersFor

      public static Hashtable headersFor(String url)

      The headers a protected URL should carry, for network paths that do not go through ConnectionRequest -- notably BrowserComponent.setURL(url, headers).

      Returns an empty table when the host is unprotected or no token is available. Never blocks: it uses the cached token only, because the callers are typically on the EDT.

      Note this covers only the initial navigation. Requests the loaded page makes itself are not visible to the framework and cannot be given a token or pinned.

    • getCachedToken

      public static ShieldToken getCachedToken()
      The cached token without triggering a fetch. May be null or lapsed. Never blocks.
    • addProtectedHost

      public static void addProtectedHost(String host, HostPolicy policy)
      Registers a protected host after init(ShieldConfig), for a backend discovered at runtime.
    • addProtectedHost

      public static void addProtectedHost(String host)
      Registers a host with the default policy, honouring ShieldConfig.defaultFailureMode(FailureMode).
    • policyFor

      public static HostPolicy policyFor(String host)
      The policy in force for a host. Returns HostPolicy.UNPROTECTED for anything not registered, which is the great majority of hosts an app talks to.
    • getPinSet

      public static PinSet getPinSet()
      The pin set currently in force. Never null; may be PinSet.EMPTY.
    • getSignals

      public static ShieldSignal[] getSignals()

      The runtime self-protection observations recorded so far, combining what the engine detected with anything the app or a library reported to ShieldSignals.

      Informational. The attestation service applies the policy, and it may reach a different conclusion than a naive reading of this array -- an emulator signal, for instance, is normal on a developer's machine.

    • getStatus

      public static ShieldStatus getStatus()
      The most recent token status. ShieldStatus.NOT_INITIALIZED before init(ShieldConfig).
    • addListener

      public static void addListener(ShieldListener l)
      Registers a listener for status and signal changes. Callbacks arrive on the EDT.
    • removeListener

      public static void removeListener(ShieldListener l)