Signing process
The signing process is identical to SigV4, with the exception of the keys used, the signature algorithm, and the addition of headers related to the X.509 certificate and trust chain. For more information, see Signature Version 4 signing process, which should be treated as authoritative unless specifically addressed in this user guide.
Topics
Task 1: Create a canonical request
To begin the signing process, create a string that includes information from your request in a standardized (canonical) format. This ensures that when the request is received, the string to sign can be calculated in the same way you did. This string will then be used for signature verification by IAM Roles Anywhere.
Follow the steps below to create a canonical version of the request. Otherwise, your version and the version calculated by AWS won't match, and the request will be denied.
The following example shows the pseudocode to create a canonical request.
Example Canonical request pseudocode
CanonicalRequest =
HttpRequestMethod
+ '\n' +CanonicalUri
+ '\n' +CanonicalQueryString
+ '\n' +CanonicalHeaders
+ '\n' +SignedHeaders
+ '\n' + Lowercase(Hex(SHA256(RequestPayload
)))
A canonical request has the above structure, in which specific elements of the request are transformed into a canonical value and concatenated together with other elements, joined with a newline character.
The following examples shows how to construct the canonical form of a request to IAM Roles Anywhere. The original request might look like this as it is sent from the client to AWS.
Example Request
POST /sessions HTTP/1.1 Host: rolesanywhere.us-east-1.amazonaws.com Content-Type: application/json X-Amz-Date: 20211103T120000Z X-Amz-X509: {
base64-encoded DER data
} { "durationSeconds":number
, "profileArn":string
, "roleArn":string
, "trustAnchorArn":string
}
To create a canonical request, concatenate the following components from each step into a single string:
-
The
HttpRequestMethod
is the verb of the HTTP request, in upper case. From the example,POST
. -
The
CanonicalUri
is the path of the request up until the query string delimiter?
. From the example,/sessions
. The path MUST be normalized according to RFC 3986, with redundant and relative path components removed. Path segments MUST be URI-encoded twice. -
The
CanonicalQueryString
is the string following?
in the request URI. If the request does not include a query string, use an empty string (essentially, a blank line). The example request does not include a query string, therefore we will use an empty string. To construct the canonical query string, complete the following steps:-
Sort the parameter names by character code point in ascending order. Parameters with duplicate names should be sorted by value. For example, a parameter name that begins with the uppercase letter F precedes a parameter name that begins with a lowercase letter b.
-
URI-encode each parameter name and value according to the following rules:
-
Do not URI-encode any of the unreserved characters that RFC 3986 defines: A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ).
-
Percent-encode all other characters with %XY, where X and Y are hexadecimal characters (0-9 and uppercase A-F). For example, the space character must be encoded as %20 (not using '+', as some encoding schemes do) and extended UTF-8 characters must be in the form %XY%ZA%BC.
-
Double-encode any equals ( = ) characters in parameter values.
-
-
Build the canonical query string by starting with the first parameter name in the sorted list.
-
For each parameter, append the URI-encoded parameter name, followed by the equals sign character (=), followed by the URI-encoded parameter value. Use an empty string for parameters that have no value.
-
Append the ampersand character (&) after each parameter value, except for the last value in the list.
-
-
The
CanonicalHeaders
is a string capturing the header key and value that are included in the signature. The field is structured as follows:CanonicalHeaders =
CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ... + CanonicalHeadersEntryN
CanonicalHeadersEntry = Lowercase(HeaderName
) + ':' + Trimall(HeaderValue
) + '\n'Lowercase represents a function that converts all characters to lowercase. The
TrimAll
function removes excess white space before and after values, and converts sequential spaces to a single space.Important
The signing certificate MUST be presented in the header
X-Amz-X509
, as base64-encoded Distinguished Encoding Rules (DER), and theX-Amz-X509
header MUST be included inCanonicalHeaders
andSignedHeaders
If the client is providing the chain of intermediate certificates, the
X-Amz-X509-Chain
MUST be added to the request as well.Build the canonical headers list by sorting the (lowercase) headers by character code and then iterating through the header names. Construct each header according to the following rules:
-
Append the lowercase header name followed by a colon.
-
Append a comma-separated list of values for that header. Do not sort the values in headers that have multiple values.
-
Append a new line ('\n').
-
-
The
SignedHeaders
is a list of the header names, sorted by lowercase character code, delimited by semi-colon. For example -content-type;host;x-amz-date;x-amz-x509
-
A hash of the request payload is appended to the canonical request. The bytes of the request are encoded as UTF-8, hashed with SHA-256, the resulting bytes hex encoded, and finally lowercased.
-
To construct the finished canonical request, combine all the components from each step as a single string. As noted, each component ends with a newline character. If you follow the canonical request pseudocode explained earlier, the resulting canonical request is shown in the following example.
Example Canonical request
POST /sessions content-type:application/json host:rolesanywhere.us-east-1.amazonaws.com x-amz-date:20211103T120000Z x-amz-x509:{base64-encoded DER data} content-type;host;x-amz-date;x-amz-x509 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
-
Create a digest (hash) of the canonical request with the same algorithm that you used to hash the payload. The bytes of the request are encoded as UTF-8, hashed with SHA-256, the resulting bytes hex encoded, and finally lowercased.
Task 2: Create a string to sign
The “string to sign” is the actual input to the signing algorithm, and includes meta information about the request, along with the canonical request created in the previous step.
Example StringToSign pseudocode
StringToSign =
Algorithm
+ '\n' +RequestDateTime
+ '\n' +CredentialScope
+ '\n' +HashedCanonicalRequest
The structure is as follows:
-
The
Algorithm
is a string that indicates how the signature is calculated. It is of the formAWS4-X509-[AlgorithmId]-[HASH]
, whereAlgorithmId
is eitherRSA
orECDSA
, and MUST be supported by the key type associated with the signing certificate. For example, if the signing certificate has an RSA key, the full algorithm string will beAWS4-X509-RSA-SHA256
. -
The
RequestDateTime
is a string derived at time of the signing operation, at second granularity, in UTC, formatted as ISO8601 basic, YYYYMMDD’T’HHMMSS’Z’. For example,20211101T121030Z
. -
The CredentialScope is structured field of the form
Date + '/' + Region + '/' + Service + '/aws4_request’
. The Region and service name strings must be UTF-8 encoded. For example,20211101/us-east-1/rolesanywhere/aws4_request
. -
Finally, append a newline followed by the HashedCanonicalRequest computed in the previous step.
Task 3: Calculate the signature
Signature = HexEncode(
SigningAlgorithm(CertPrivateKey,StringToSign)
)
Here, the SigningAlgorithm refers to a supported signing operation, either SHA256WithRSA or SHA256WithECDSA.
Task 4: Add the signature to the HTTP request
The signature derived from the previous step is added to the HTTP request in the Authorization
header field. The Authorization
header is attached to the request and validated for authentication. It is partitioned into multiple fields - signing algorithm, credentials, signed headers, and the actual signature. The header of the authentication mechanism based on X.509 differs from a SigV4 header in two ways:
-
Algorithm. As described above, instead of
AWS4-HMAC-SHA256
, the algorithm field will have the values of the formAWS4-X509-RSA-SHA256
orAWS4-X509-ECDSA-SHA256
, depending on whether an RSA or Elliptic Curve algorithm is used. This, in turn, is determined by the key bound to the signing certificate. -
Scope field/credentials. As specified above, the serial number of the certificate used to sign the request will be in place of the Access Key ID (credential) in the Scope field.
The structure of the field is as follows:
Authorization: {
Algorithm
} Credential={CredentialString
}, SignedHeaders={SignedHeaders
}, Signature={Signature
}
-
The
Algorithm
is of the formAWS4-X509-{RSA|ECDSA}-SHA256
. Examples -AWS4-X509-RSA-SHA256
. -
The
Credential
is constructed via{SerialNumber}/{Scope}
where serial number is the decimal representation of the serial number of the signing certificate, andScope
is the value constructed as input to theStringToSign
. For example:Credential=11111222223333344444/20201105/us-east-1/rolesanywhere/aws4_request
-
The
SignedHeaders
is a comma-delimited list of the headers signed as part of the request. -
The
Signature
is the hex encoded output of the previous step.