Skip to content

Signing

The SNAP standard defines two signing modes, agreed per partner at registration time: symmetric (HMAC-SHA512) and asymmetric (SHA256withRSA). This package implements both as plain functions — HeaderBuilder.Build() and TokenManager call into them, but you can use them directly for testing or for implementing a KeyStore-backed verifier.

Symmetric (HMAC-SHA512)

func SignSymmetric(clientSecret, stringToSign string) string
func VerifySymmetric(clientSecret, stringToSign, signature string) bool

SignSymmetric computes the HMAC-SHA512 signature of stringToSign using clientSecret as the key, returning lowercase hex. VerifySymmetric checks a signature the same way, using a constant-time comparison.

Asymmetric (SHA256withRSA)

func SignAsymmetric(signer crypto.Signer, stringToSign string) (string, error)
func VerifyAsymmetric(pub crypto.PublicKey, stringToSign, signature string) error

SignAsymmetric signs with SHA256withRSA (PKCS#1 v1.5), returning lowercase hex. It takes a crypto.Signer rather than a concrete *rsa.PrivateKey so an HSM/KMS-backed key can be plugged in without an API change. VerifyAsymmetric returns a non-nil error if pub isn’t an RSA key, signature isn’t valid hex, or the signature doesn’t verify.

Both functions reject RSA keys smaller than 2048 bits (the standard’s own floor) since a weaker key can be forged in practice — this matters most on the verify side, where the public key comes from a partner-registered certificate you don’t control.

func ParseRSAPrivateKeyPEM(pemBytes []byte) (crypto.Signer, error)

Parses a PKCS#1 or PKCS#8 PEM-encoded RSA private key. Skip it if you already hold a crypto.Signer from elsewhere (an HSM/KMS client, for example).

The string-to-sign formulas

func BuildStringToSignAccessToken(clientID, timestamp string) string
func BuildStringToSignTransaction(method, endpointURL, accessToken string, body []byte, timestamp string, symmetric bool) string

Access-token requests always use:

clientID + "|" + timestamp

Transaction requests use one of two formulas depending on symmetric:

symmetric:  HTTPMethod:EndpointUrl:AccessToken:HexSHA256(body):TimeStamp
asymmetric: HTTPMethod:EndpointUrl:HexSHA256(body):TimeStamp

body must be the exact bytes already sent on the wireBuildStringToSignTransaction does not marshal JSON itself. An empty body hashes to the SHA256 digest of an empty byte slice, not an empty string.

Access-token requests are always asymmetric-signed — both B2B and B2B2C — regardless of the signing mode agreed for transaction requests. Don’t reuse ClientSecret/SignSymmetric for the token call even on a symmetric integration; TokenManager already gets this right for you.

Sentinel errors

A sentinel error is a fixed, exported error value (like snap.ErrWeakRSAKey) meant to be checked with errors.Is, not by comparing error text — the functions above return these so you can branch on the failure reason:

SentinelReturned byMeaning
ErrNoPEMBlockParseRSAPrivateKeyPEMinput has no PEM block
ErrNotRSAKeyParseRSAPrivateKeyPEMPEM block isn’t an RSA key
ErrNotRSASignerSignAsymmetric, VerifyAsymmetrickey isn’t RSA
ErrWeakRSAKeySignAsymmetric, VerifyAsymmetrickey smaller than 2048 bits

All are wrapped with %w, so match them with errors.Is.

Next