-
Notifications
You must be signed in to change notification settings - Fork 2
SNS authentication scheme
Some SolarNetwork integrations use a custom authentication scheme adapted from the SNWS2 scheme. For example, the SolarNode STOMP server uses this scheme. Clients of the API must authenticate using a HMAC+SHA256 digest of specific request data.
💡 Note that within this document the terms request and header are abstract, because the underlying communication protocol is not protocol-specific. A request refers to a message sent by the client wishing to authenticate with a server, and header refers to named metadata included with that request.
The following table describes some terms used throughout this document.
| Term | Description |
|---|---|
| principal | An identifier for the actor making the request, such as a username, email address, or token ID. |
| secret | A secret value known only to the actor making the request. |
| header | A name/value pair of metadata included in the request. |
newline character \n
|
The newline character is the ASCII character 0x0A, commonly \n in programming languages. |
UriEncode() |
An encoding function that accepts a UTF-8 string argument and returns a copy where all characters other than A-Z, a-z, 0-9, _, -, ~, and . are replaced by %X, where X is the upper case hexidecimal number of the character's code point (see RFC 3986). For example, UriEncode("Hello, world.") results in Hello%2C%20world. Note these rules are stricter than what JavaScript's encodeURIComponent() method performs. See the _encodeURIComponent() example JavaScript function that performs the required encoding. |
Trim() |
A function that accepts a string argument and returns a copy where all leading and trailing whitespace characters have been removed. |
Hex() |
A function that accepts an arbitrary array of bytes and returns an ASCII-encoded string where all bytes have been treated as unsigned values (0-255) and converted to lower case hexidecimal string values (00 - ff). |
SHA256() |
A SHA256 encoding function that accepts a string argument and produces a 32-byte SHA256 digest. |
HMAC_SHA256() |
A HMAC+SHA256 encoding function that accepts a secret key and message data as arguments, e.g. HMAC(key, message) and produces a 32-byte HMAC signed SHA256 digest. |
The request must include a date header. This date is used in the key used to sign the message and the message to sign, and so its value must be known by the client and communicated to the server.
The value of the request date must match the current date on the server at the time the request is made, within a small tolerance value. If the difference between the header date and the server's system date is too large, an error will be generated and authentication will fail with a message along the lines of date skew too large.
The request must include an authorization header using this syntax:
SNS Credential=principal,SignedHeaders=headerList,Signature=signature
where principal, headerList, and signature are placeholders for the authentication
information, described in the following sections. The order of these comma-delimited elements does
not matter. For example these two header values are effectively equivalent:
SNS Credential=bob@example.com,SignedHeaders=date;host,Signature=8064422140...
SNS Signature=8064422140...,Credential=bob@example.com,SignedHeaders=date;host
The principal in the Credential=principal part of the authorization header is
an identifier for the actor making the request, such as a username, email address, or
token ID. For example:
Credential=bob@example.com
The headerList in the SignedHeaders=headerList part of the authorization header is a semicolon-delimited list of header names included in the canonical request message part of the signature. For example:
SignedHeaders=date;host
The signature in the Signature=signature part of the authorization header is an HMAC+SHA256 digest using a signing key derived from the secret and signing message derived from the request details, encoded as a hexidecimal string.
The signature value is calculated using this general algorithm:
Hex(HMAC_SHA256(signingKey,signingMessage))
where signingKey is the signing key derived from the secret and signingMessage is the signing message as described in this document.
Creating the signature in the Signature=signature part of the authorization header follows
this general algorithm:
Hex(HMAC_SHA256(signingKey,signingMessage))
The signingKey and signingMessage values are discussed in the following sections.
The signingKey used in the HMAC_SHA256(signingKey,signingMessage) part of the signature algorithm
is derived from the secret value known only to the actor making the request. It is a 32-byte key derived using this general algorithm:
HMAC_SHA256(HMAC_SHA256("SNS"+secret, utcDate), "sns_request")
The utcDate value is the signing date formatted as YYYYMMDD. A signing key is valid for up to
7 days from the date it is signed. For some security-sensitive applications this can be useful
so the actual secret does not have to be held in memory, just the derived signing key does.
For example, if the secret is ABC123 and the UTC date is January 1, 2017, the result is derived
like:
HMAC_SHA256(HMAC_SHA256("SNSABC123", "20170101"), "sns_request")
which results in a hex-encoded key of:
0bd3a3bfa9bc1694bc471ab775f8511e2a55d393f3c80333c0fecc2a74c8858b
☝️ Note that when signing the message, use the actual output bytes of the
HMAC_SHA256()function, not a hex encoded version of it.
The signingMessage used in the HMAC_SHA256(signingKey,signingMessage) part of the signature algorithm is 3 lines of text delimited by a newline character:
- The literal string
SNS-HMAC-SHA256 - The request date, formatted as an UTC ISO8601 timestamp like
YYYYMMDD'T'HHmmss'Z' - The output of
Hex(SHA256(CanonicalRequestMessage))whereCanonicalRequestMessageis the canonical request message string as described the following section.
For example, the signing message for a request might look like:
SNS-HMAC-SHA256
20170303T043628Z
8f732085380ed6dc18d8556a96c58c820b0148852a61b3c828cb9cfd233ae05f
The CanonicalRequestMessage used in the Hex(SHA256(CanonicalRequestMessage)) part
of the signing message is several lines of text derived from 5 request items. The string is formed by concatenating the following items with the \n newline character:
Verb
Path
CanonicalHeaderList
SignedHeaderNames
BodyContentSHA256
Each of these items are discussed in detail next.
| Component | Description |
|---|---|
| Verb | The uppercase action verb used in the request, for example GET or SEND. |
| Path | A URL path, starting at /, for example /services/setup. |
| Canonical headers | This is a newline-delimited string of all headers and associated values used to sign the request. The header names that appear here must match the authorization signed header list. See Canonical headers for more information. |
| Signed header names | This is a semicolon-delimited string of all header names used to sign the request, and the same value included in the authorization signed header list. See Signed header names for more information. |
| Body content SHA256 digest | This is a Hex(SHA256()) digest of the request body content. If no body content is included then the Hex(SHA256()) digest of an empty string must be used. For example a JSON body of {"m":{"foo":"BAR"}} would result in 3fb055786e256de47c267183d53d67337afe7aed40e200a7ad798a256688782b. |
For example, a GET request might result in a canonical request message like this:
GET
/some/service
host:example.com
date:Fri, 03 Mar 2017 04:36:28 GMT
host;date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
☝️ The
e3b0c442...7852b855value is the SHA256 digest value for "no" body content, that is the result of passing an empty string toHex(SHA256('')).
Here's an example of a SEND request:
SEND
/some/service
content-type:application/json; charset=UTF-8
digest:SHA-256=P7BVeG4lbeR8JnGD1T1nM3r+eu1A4gCnrXmKJWaIeCs=
host:example.com
date:Fri, 03 Mar 2017 04:29:07 GMT
content-type;digest;host;x-sn-date
3fb055786e256de47c267183d53d67337afe7aed40e200a7ad798a256688782b
The CanonicalHeaderList part of the canonical request message is a
list of of request header name/value pairs formed using this algorithm:
- Lowercase all header names that will be included in the signature data
- Sort the lowercase header names
- For each sorted, lowercase header
nameandvaluepair:- If this is not the first name, append
\nto the result - Append
Trim(name),:, andTrim(value)to the result
- If this is not the first name, append
For example, if a request includes a Host header value example.com and a Date header value Fri, 03 Mar 2017 04:00:23 GMT then the canonical headers string is:
host:example.com
date:Fri, 03 Mar 2017 04:00:23 GMT
The SignedHeaderNames part of the canonical request message is a
string formed using this algorithm:
- Lowercase all header names that will be included in the signature data
- Sort the lowercase header names
- For each sorted, lowercase header
name:- If this is not the first name, append
;to the result - Append
Trim(name)to the result
- If this is not the first name, append
The request must at a minimum sign the date header name. For example, if the request includes the Host and Date headers, the SignedHeaderNames string is:
host;date
If the request includes body content then a Digest or Content-MD5 header should be included. The RFC 5843 Digest header using the SHA-256 algorithm is preferred. Examples headers are Digest: SHA-256=P7BVeG4lbeR8JnGD1T1nM3r+eu1A4gCnrXmKJWaIeCs= and Content-MD5: /o1mwr8CitmYCfPTCeZp4A==.
Another example, for a request including body content and the additional Digest header, would result in a signed header names string of:
content-type;digest;host;date
- SolarNetwork API access
- SolarNetwork API authentication
- SolarNetwork API rate limiting
- SolarNetwork global objects
- SolarNetwork aggregation
- SolarFlux API
- SolarIn API
- SolarQuery API
-
SolarUser API
- SolarUser enumerated types
- SolarUser datum expire API
- SolarUser datum export API
- SolarUser datum import API
- SolarUser datum stream alias API
- SolarUser event hook API
- SolarUser location request API
- SolarUser Cloud Integrations API
- SolarUser DIN API
- SolarUser DNP3 API
- SolarUser ININ API
- SolarUser OCPP API
- SolarUser OSCP API
- SolarUser Secrets API
- SolarUser SolarFlux API