Class Aws

java.lang.Object
com.codename1.backend.aws.Aws

public final class Aws extends Object

AWS Signature Version 4, and the request plumbing every AWS service shares.

This is the whole of what an AWS client needs that is not service specific: canonicalise a request, derive a signing key, and send it. S3 is one service built on it; SQS, DynamoDB and Secrets Manager are the same three steps with a different host and payload, which is why the signer is a separate class rather than something private to S3.

Written rather than pulled in because the AWS SDK is not an option here: it wants reflection, a class loader and a threading model a translated server binary does not have. SigV4 itself is a hash chain -- five HMACs and a SHA-256 -- over a canonical form of the request, and the specification is public and stable.

The part that is easy to get wrong, and the reason for the length of this file, is the CANONICAL form: the signature covers a normalised URI, a sorted query string, sorted lower-cased headers and a hash of the body, and a single difference from what the service computes produces a 403 with no indication of which field disagreed.

  • Field Details

    • UNSIGNED_PAYLOAD

      public static final String UNSIGNED_PAYLOAD
      The unsigned-payload marker, for a body the caller does not want hashed.
      See Also:
  • Method Details

    • send

      public static Web.Result send(Credentials credentials, String region, String service, String method, String host, String path, Map query, Map headers, byte[] body, String timestamp) throws IOException

      A signed, sent request.

      headers are extra "Name: value" strings; Host, x-amz-date and x-amz-content-sha256 are added here because they are part of the signature.

      Throws:
      IOException
    • send

      public static Web.Result send(Credentials credentials, String region, String service, String method, String host, String path, Map query, Map headers, byte[] body, String timestamp, boolean secure) throws IOException

      As above, over plain HTTP when secure is false.

      The signature covers the host and not the scheme, so this changes only how the request travels. It exists for a local MinIO or a test double on loopback; nothing reachable off the machine should use it, and AWS itself does not accept it.

      Throws:
      IOException
    • authorization

      public static String authorization(Credentials credentials, String region, String service, String method, String path, Map query, Map headers, String payloadHash, String timestamp) throws IOException
      The Authorization header value for one request.
      Throws:
      IOException
    • presign

      public static String presign(Credentials credentials, String region, String service, String method, String host, String path, Map query, int expiresSeconds, String timestamp) throws IOException

      A presigned URL: the signature travels in the query string, so anyone holding the URL can make that one request until it expires.

      This is what hands a mobile client a direct download or upload without proxying the bytes through the server, which is most of the reason to use object storage from an app at all.

      Throws:
      IOException
    • presign

      public static String presign(Credentials credentials, String region, String service, String method, String host, String path, Map query, int expiresSeconds, String timestamp, boolean secure) throws IOException
      As above, producing an http:// URL when secure is false. See send(Credentials, String, String, String, String, String, Map, Map, byte[], String).
      Throws:
      IOException
    • signingKey

      public static byte[] signingKey(String secretKey, String date, String region, String service) throws IOException

      The four-step key derivation. The signing key is scoped to a date, a region and a service, which is what keeps a leaked signature from being reusable anywhere else.

      Public, along with the four canonicalisation helpers below, because a service this class does not wrap -- SQS, DynamoDB, Secrets Manager -- is the same signature over a different payload, and because these are the pieces a known-answer test can pin. A signature implementation that can only be tested end to end is one whose failures all look like 403.

      Throws:
      IOException
    • canonicalQuery

      public static String canonicalQuery(Map query)
      Query parameters sorted by name, each name and value percent-encoded. Sorting is by the ENCODED name, which matters for names that differ only in a character the encoding changes.
    • encodePath

      public static String encodePath(String path)
      The path, percent-encoded segment by segment. The slashes between segments are NOT encoded; everything else that is not unreserved is -- which is why this cannot just call encode(String) on the whole path.
    • encode

      public static String encode(String value)
      RFC 3986 unreserved characters pass; everything else becomes %XX with UPPER case hex. Note this is not URLEncoder: a space is %20 here, never '+', and '~' is not encoded. Both differences produce a signature mismatch.
    • collapse

      public static String collapse(String value)
      Leading and trailing space removed, internal runs collapsed to one space.
    • sha256Hex

      public static String sha256Hex(byte[] data)
    • hex

      public static String hex(byte[] data)