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
}