Skip to main content

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-Input and Signature: Signature metadata and result. Label is always jalapeno.
  • Request Body: JSON payload must strictly follow the API definition and must not be tampered with before or after signing.

2. Signing Process​

  1. Prepare Signature Components Select and sort the components to be signed: "content-digest", "@path", "@method", "@query". If there are no query parameters, @query must be set to ?.

  2. 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 as hmac-sha256.
  1. 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.

  2. Generate Digital Signature Apply hmac-sha256 algorithm to the base string using the SecretAccessKey corresponding to AccessKeyId, then write the signature value:

Signature: jalapeno=:<Base64Signature>:
  1. Send Request and Handle Errors If signature or certificate verification fails, JalapenoCloud returns a 4xx error. The code and message fields 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
}