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) boolSignSymmetric 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) errorSignAsymmetric 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) stringAccess-token requests always use:
clientID + "|" + timestampTransaction requests use one of two formulas depending on symmetric:
symmetric: HTTPMethod:EndpointUrl:AccessToken:HexSHA256(body):TimeStamp
asymmetric: HTTPMethod:EndpointUrl:HexSHA256(body):TimeStampbody must be the exact bytes already sent on the wire —
BuildStringToSignTransaction does not marshal JSON itself. An empty body
hashes to the SHA256 digest of an empty byte slice, not an empty string.
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:
| Sentinel | Returned by | Meaning |
|---|---|---|
ErrNoPEMBlock | ParseRSAPrivateKeyPEM | input has no PEM block |
ErrNotRSAKey | ParseRSAPrivateKeyPEM | PEM block isn’t an RSA key |
ErrNotRSASigner | SignAsymmetric, VerifyAsymmetric | key isn’t RSA |
ErrWeakRSAKey | SignAsymmetric, VerifyAsymmetric | key smaller than 2048 bits |
All are wrapped with %w, so match them with errors.Is.