Backend API. Server-side code: this runs in a Codename One backend, not in the app on the device.

public final class Aws

  1. Object
  2. Aws

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.

Fields

public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"The unsigned-payload marker, for a body the caller does not want hashed.

Methods

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 IOExceptionA signed, sent request.
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 IOExceptionAs above, over plain HTTP when secure is false.
public static String authorization(Credentials credentials, String region, String service, String method, String path, Map query, Map headers, String payloadHash, String timestamp) throws IOExceptionThe Authorization header value for one request.
public static String presign(Credentials credentials, String region, String service, String method, String host, String path, Map query, int expiresSeconds, String timestamp) throws IOExceptionA presigned URL: the signature travels in the query string, so anyone holding the URL can make that one request until it expires.
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 IOExceptionAs above, producing an http:// URL when secure is false.
public static byte[] signingKey(String secretKey, String date, String region, String service) throws IOExceptionThe four-step key derivation.
public static String canonicalQuery(Map query)Query parameters sorted by name, each name and value percent-encoded.
public static String encodePath(String path)The path, percent-encoded segment by segment.
public static String encode(String value)RFC 3986 unreserved characters pass; everything else becomes %XX with UPPER case hex.
public static String collapse(String value)Leading and trailing space removed, internal runs collapsed to one space.
public static String sha256Hex(byte[] data)
public static String hex(byte[] data)

Inherited methods

Field details

UNSIGNED_PAYLOAD

public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"
The unsigned-payload marker, for a body the caller does not want hashed.

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.

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.

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.

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.

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.

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.

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 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)