Request Construction and Signing Guide
This section explains how to construct HTTP requests when integrating with management APIs to ensure successful verification.
1. Basic Request Elements
-
HTTP Method: Select
GET,POST,PUT, etc. based on the operation. Use uppercase method names when signing. -
Endpoint Address: Use the complete URL for your environment (Sandbox/Production), including query parameters.
-
Key Headers:
Content-Digest: SHA-256 digest of the request body. For requests without a body, compute the digest of an empty string.Signature-InputandSignature: Signature metadata and result. Label is alwaysjalapeno.
-
Request Body: JSON payload must strictly follow the API definition and must not be tampered with before or after signing.
2. Signing Process
-
Prepare Signature Components Select and sort the components to be signed:
"content-digest","@path","@method","@query". If there are no query parameters,@querymust be set to?. -
Construct Signature-Input
Signature-Input: jalapeno=("content-digest" "@path" "@method" "@query");keyid="<access-key-id>";created=<timestamp>;nonce=<random-uint32>;alg="hmac-sha256"
keyid: AccessKeyId from the JalapenoCloud-issued access key pair.created: Unix timestamp in seconds. Requests are considered expired if received more than 5 minutes later.nonce: UINT32 random number.alg: Fixed ashmac-sha256.
-
Concatenate Signature Base String Format each component as
"key": value, one per line, and append"@signature-params"pointing to the parameter list from the previous step. -
Generate Digital Signature Apply
hmac-sha256algorithm to the base string using theSecretAccessKeycorresponding toAccessKeyId, then write the signature value:
Signature: jalapeno=:<Base64Signature>:
- Send Request and Handle Errors
If signature or certificate verification fails, JalapenoCloud returns a
4xxerror. Thecodeandmessagefields in the response body indicate missing headers, digest errors, invalid signatures, etc. Use this information for troubleshooting.
3. Code Example
package httpsign
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"time"
)
func SignRequest(req *http.Request, accessKeyId string, secretAccessKey []byte) error {
if req == nil {
return fmt.Errorf("request is nil")
}
if req.Body != nil {
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("failed to read request body: %w", err)
}
_ = req.Body.Close()
sum := sha256.Sum256(bodyBytes)
digestBase64 := base64.URLEncoding.EncodeToString(sum[:])
req.Header.Set("Content-Digest", fmt.Sprintf("%s=%s", "sha-256", digestBase64))
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
nonceBytes := make([]byte, 32)
_, _ = rand.Read(nonceBytes)
created := time.Now()
nonce := base64.RawURLEncoding.EncodeToString(nonceBytes)
alg := "hmac-sha256"
if req.Method == "" {
return fmt.Errorf("method is required")
}
query := "?"
if req.URL != nil {
if raw := req.URL.RawQuery; raw != "" {
query += raw
}
}
cd := req.Header.Get("Content-Digest")
if cd == "" {
return fmt.Errorf("Content-Digest header is required")
}
path := "/"
if req.URL != nil && req.URL.Path != "" {
path = req.URL.Path
}
escape := func(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
return s
}
pairs := []string{
fmt.Sprintf("created=%d", created.Unix()),
fmt.Sprintf(`nonce="%s"`, escape(nonce)),
fmt.Sprintf(`keyid="%s"`, escape(accessKeyId)),
fmt.Sprintf(`alg="%s"`, escape(alg)),
}
sigParams := `("@method" "@query" "@path" "content-digest")`
if len(pairs) > 0 {
sigParams += ";" + strings.Join(pairs, ";")
}
parts := []string{
fmt.Sprintf(`"@method": %s`, req.Method),
fmt.Sprintf(`"@query": %s`, query),
fmt.Sprintf(`"@path": %s`, path),
fmt.Sprintf(`"content-digest": %s`, cd),
fmt.Sprintf(`"@signature-params": %s`, sigParams),
}
signatureBase := strings.Join(parts, "\n")
for _, r := range signatureBase {
if r > 127 {
return fmt.Errorf("signature base contains non-ASCII character")
}
}
mac := hmac.New(sha256.New, secretAccessKey)
if _, err := mac.Write([]byte(signatureBase)); err != nil {
return fmt.Errorf("failed to compute hmac: %w", err)
}
sigBytes := mac.Sum(nil)
sigInput := sigParams
label := "jalapeno"
signatureInputValue := fmt.Sprintf(`%s=%s`, label, sigInput)
sigBase64 := base64.RawURLEncoding.EncodeToString(sigBytes)
signatureValueValue := fmt.Sprintf(`%s=:%s:`, label, sigBase64)
req.Header.Set("Signature-Input", signatureInputValue)
req.Header.Set("Signature", signatureValueValue)
return nil
}