jws

package
v0.0.0-...-3c258e1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2025 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package jws implements JSON Web Signature (JWS) as defined in RFC 7515.

Overview

JWS provides integrity protection for arbitrary data through digital signatures or message authentication codes. A JWS message consists of a payload, a protected header (containing the algorithm), and a signature.

Core Interfaces

The package defines two interfaces for cryptographic operations:

Primary Functions

  • Sign: Creates a signed Message from a payload and one or more signers.
  • Verify: Parses and verifies a JWS, returning the original Message.

Message Serialization

A Message can be serialized in three formats defined by RFC 7515:

Supported Algorithms

  • HMAC: HS256, HS384, HS512 via HMAC
  • RSA PKCS#1 v1.5: RS256, RS384, RS512 via RSA
  • RSA-PSS: PS256, PS384, PS512 via RSAPSS
  • ECDSA: ES256, ES384, ES512 via ECDSA
  • EdDSA: Ed25519 via EdDSA

Example

signer, verifier, err := jws.HMAC(crypto.SHA256, "key-id", secret)
if err != nil {
    return err
}
msg, err := jws.Sign([]byte("data"), signer)
if err != nil {
    return err
}
token, err := msg.Compact()
if err != nil {
    return err
}
verified, err := jws.Verify(token, verifier)
if err != nil {
    return err
}
Example (Ecdsa)
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	// ECDSA uses public-key cryptography: sign with private key,
	// verify with public key. The curve determines the algorithm:
	// P-256 → ES256, P-384 → ES384, P-521 → ES512

	// Generate a key pair
	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		log.Fatal(err)
	}

	// Create a signer from the private key
	signer, err := jws.NewSigner(privateKey, "my-ec-key")
	if err != nil {
		log.Fatal(err)
	}

	// Sign a message
	msg, err := jws.Sign([]byte("sensitive data"), signer)
	if err != nil {
		log.Fatal(err)
	}

	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}

	// Create a verifier from the public key (this could be shared with others)
	verifier, err := jws.NewVerifier(&privateKey.PublicKey, "my-ec-key")
	if err != nil {
		log.Fatal(err)
	}

	// Verify
	verified, err := jws.Verify(token, verifier)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Verified:", string(verified.Payload))

}
Output:

Verified: sensitive data
Example (EdDSA)
package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	// Ed25519 signatures - modern, fast, and secure

	_, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		log.Fatal(err)
	}

	signer, verifier, err := jws.EdDSA(privateKey, "my-ed-key")
	if err != nil {
		log.Fatal(err)
	}

	msg, err := jws.Sign([]byte("hello Ed25519"), signer)
	if err != nil {
		log.Fatal(err)
	}

	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}

	verified, err := jws.Verify(token, verifier)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Verified:", string(verified.Payload))

}
Output:

Verified: hello Ed25519
Example (Hmac)
package main

import (
	"crypto"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	// HMAC uses a shared secret for both signing and verification.
	secret := []byte("my-secret-key-at-least-32-bytes!")
	signer, verifier, err := jws.HMAC(crypto.SHA256, "my-key-id", secret)
	if err != nil {
		log.Fatal(err)
	}

	// Sign a message
	msg, err := jws.Sign([]byte("hello, world!"), signer)
	if err != nil {
		log.Fatal(err)
	}

	// Get the compact token (most common format)
	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Token created successfully")

	// Verify the token
	verified, err := jws.Verify(token, verifier)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Payload:", string(verified.Payload))

}
Output:

Token created successfully
Payload: hello, world!
Example (MultipleVerifiers)
package main

import (
	"crypto"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	// When you have multiple valid keys (e.g., during key rotation),
	// you can pass multiple verifiers to Verify.

	// Create two different keys
	secret1 := []byte("old-key-being-rotated-out-12345!")
	secret2 := []byte("new-key-being-rotated-in-123456!")

	signer1, verifier1, err := jws.HMAC(crypto.SHA256, "old-key", secret1)
	if err != nil {
		log.Fatal(err)
	}

	_, verifier2, err := jws.HMAC(crypto.SHA256, "new-key", secret2)
	if err != nil {
		log.Fatal(err)
	}

	// Sign with the old key
	msg, err := jws.Sign([]byte("data"), signer1)
	if err != nil {
		log.Fatal(err)
	}

	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}

	// Verify with either key - Verify tries each verifier until one succeeds
	verified, err := jws.Verify(token, verifier1, verifier2)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Verified with one of the keys:", string(verified.Payload))

}
Output:

Verified with one of the keys: data
Example (Rsa)
package main

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	// RSA PKCS#1 v1.5 signatures (RS256, RS384, RS512)

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal(err)
	}

	signer, verifier, err := jws.RSA(crypto.SHA256, privateKey, "my-rsa-key")
	if err != nil {
		log.Fatal(err)
	}

	msg, err := jws.Sign([]byte("hello RSA"), signer)
	if err != nil {
		log.Fatal(err)
	}

	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}

	verified, err := jws.Verify(token, verifier)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Verified:", string(verified.Payload))

}
Output:

Verified: hello RSA
Example (RsaPSS)
package main

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	// RSA-PSS signatures (PS256, PS384, PS512) - more secure than PKCS#1 v1.5

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal(err)
	}

	signer, verifier, err := jws.RSAPSS(crypto.SHA256, privateKey, "my-pss-key")
	if err != nil {
		log.Fatal(err)
	}

	msg, err := jws.Sign([]byte("hello PSS"), signer)
	if err != nil {
		log.Fatal(err)
	}

	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}

	verified, err := jws.Verify(token, verifier)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Verified:", string(verified.Payload))

}
Output:

Verified: hello PSS

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrCriticalHeader = errors.New("jws: unsupported critical header parameter")

ErrCriticalHeader is returned when a message carries critical header parameters that are not understood or not integrity protected.

View Source
var ErrInvalidSignature = errors.New("jws: invalid signature")

ErrInvalidSignature is the error returned when a signature cannot be verified.

View Source
var ErrUnsupportedAlgorithm = errors.New("jws: unsupported algorithm")

ErrUnsupportedAlgorithm is the error returned when attempting to sign a message with an unsupported key type, or verify one signed with an unsupported algorithm.

Functions

func ECDSA

func ECDSA(key *ecdsa.PrivateKey, kid string) (Signer, Verifier, error)

ECDSA returns a Signer and Verifier for ECDSA signatures.

The curve of the private key determines the algorithm:

  • P-256 → ES256
  • P-384 → ES384
  • P-521 → ES512

func EdDSA

func EdDSA(key ed25519.PrivateKey, kid string) (Signer, Verifier, error)

EdDSA returns a Signer and Verifier for Ed25519 signatures.

Ed25519 is defined in RFC 8037. Unlike RSA and ECDSA, Ed25519 uses a fixed curve, so there's no need to specify a hash algorithm.

func HMAC

func HMAC(hasher crypto.Hash, kid string, secret []byte) (Signer, Verifier, error)

HMAC returns a Signer and Verifier for HMAC-based signatures.

HMAC uses a shared secret key for both signing and verification, making it suitable for server-to-server communication where both parties share the secret.

The hasher determines the algorithm:

The kid (key ID) is included in the signature header to help identify which key was used. The secret should be at least as long as the hash output (32 bytes for SHA-256, etc.) for full security.

func RSA

func RSA(hasher crypto.Hash, key *rsa.PrivateKey, kid string) (Signer, Verifier, error)

RSA returns a Signer and Verifier for RSA PKCS#1 v1.5 signatures.

RSA signatures use asymmetric cryptography: sign with a private key, verify with the corresponding public key. This allows anyone to verify a signature without being able to create one.

The hasher determines the algorithm:

For new applications, consider RSAPSS which provides stronger security guarantees.

func RSAPSS

func RSAPSS(hasher crypto.Hash, key *rsa.PrivateKey, kid string) (Signer, Verifier, error)

RSAPSS returns a Signer and Verifier for RSA-PSS signatures.

RSA-PSS (Probabilistic Signature Scheme) is defined in RFC 7518 Section 3.5.

The hasher determines the algorithm:

Types

type Message

type Message struct {
	// Payload contains the signed data. After verification, this is the
	// original message that was signed.
	Payload jose.Binary `json:"payload"`

	// Signatures contains one or more signatures over the payload.
	// Most messages have exactly one signature.
	Signatures []*Signature `json:"signatures"`
}

Message represents a signed JWS message containing a payload and one or more signatures.

After signing with Sign, use one of the serialization methods to get a transmittable format:

After verifying with Verify, access the original data via the Payload field.

func Sign

func Sign(payload []byte, signers ...Signer) (msg *Message, err error)

Sign creates a signed JWS message from the given payload.

The returned Message can be serialized using Message.Compact (most common), Message.Serialized, or Message.Flattened.

Multiple signers can be provided to create a message with multiple signatures, though this is rarely needed in practice.

func Verify

func Verify(data []byte, verifiers ...Verifier) (*Message, error)

Verify parses and verifies a signed JWS message.

The data can be in any JWS format: compact (most common), JSON, or flattened JSON. Returns the verified Message if any of the provided verifiers successfully validates a signature, or ErrInvalidSignature if none do.

When multiple verifiers are provided, Verify tries each one until it finds a match. This is useful when you have multiple valid keys (e.g., during key rotation).

func (*Message) Compact

func (m *Message) Compact() ([]byte, error)

Compact returns the message as a URL-safe compact string.

This is the most common format, used by JWTs and most APIs. The result looks like "xxxxx.yyyyy.zzzzz" where each part is base64url-encoded.

Only works with a single signature. Returns an error if the message has multiple signatures.

Example
package main

import (
	"crypto"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	signer, _, err := jws.HMAC(crypto.SHA256, "key", []byte("secret-key-at-least-32-bytes!!!"))
	if err != nil {
		log.Fatal(err)
	}

	msg, err := jws.Sign([]byte("hello"), signer)
	if err != nil {
		log.Fatal(err)
	}

	// Compact returns a URL-safe string like "header.payload.signature"
	token, err := msg.Compact()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Token created:", len(token) > 0)

}
Output:

Token created: true

func (*Message) Flattened

func (m *Message) Flattened() ([]byte, error)

Flattened returns the message as a simplified JSON object.

This is a more compact JSON format than Message.Serialized, but only works with a single signature.

Example output:

{"payload":"aGVsbG8","protected":{"alg":"HS256"},"signature":"..."}

func (*Message) Serialized

func (m *Message) Serialized() ([]byte, error)

Serialized returns the message as a full JSON object.

This format supports multiple signatures and unprotected headers.

Example output:

{"payload":"aGVsbG8","signatures":[{"protected":{"alg":"HS256"},"signature":"..."}]}
Example
package main

import (
	"crypto"
	"fmt"
	"log"

	"mz.attahri.com/code/jose/jws"
)

func main() {
	signer, _, err := jws.HMAC(crypto.SHA256, "key", []byte("secret-key-at-least-32-bytes!!!"))
	if err != nil {
		log.Fatal(err)
	}

	msg, err := jws.Sign([]byte("hi"), signer)
	if err != nil {
		log.Fatal(err)
	}

	// Serialized returns full JSON format
	jsonData, err := msg.Serialized()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("JSON format available:", len(jsonData) > 0)

}
Output:

JSON format available: true

func (*Message) UnmarshalJSON

func (m *Message) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a message from either its full JSON representation or a flattened one.

type Signature

type Signature struct {
	// Protected contains the integrity-protected header parameters.
	// This always includes "alg" (algorithm) and usually "kid" (key ID).
	Protected jose.Header `json:"protected"`

	// Header contains unprotected header parameters (rarely used).
	Header jose.Header `json:"header,omitempty"`

	// Signature contains the cryptographic signature bytes.
	Signature jose.Binary `json:"signature"`
}

Signature represents a single cryptographic signature within a Message.

Most users won't need to interact with this type directly. It's primarily used when inspecting a verified message's metadata (like the algorithm used).

type Signer

type Signer interface {
	// Protected returns the protected header that will be included in signatures.
	Protected() jose.Header

	// Header returns the unprotected header (rarely used).
	Header() jose.Header

	// Algorithm returns the JWS algorithm name (e.g., "HS256", "RS256").
	Algorithm() string

	// KID returns the key ID, useful for identifying which key to use for verification.
	KID() string

	// Sign creates a signature over the payload.
	Sign(payload []byte) (*Signature, error)
}

Signer creates cryptographic signatures over data.

Create signers using the algorithm-specific constructors:

  • HMAC for HS256, HS384, HS512
  • RSA for RS256, RS384, RS512
  • RSAPSS for PS256, PS384, PS512
  • EdDSA for Ed25519
  • NewSigner for ECDSA keys (ES256, ES384, ES512)

Signers are safe for concurrent use.

func NewSigner

func NewSigner(k crypto.Signer, kid string) (Signer, error)

NewSigner returns a new signer from the given private key.

The key must implement crypto.Signer. If the key also implements crypto.MessageSigner (Go 1.25+), signing operations will use crypto.SignMessage which allows the signer to perform hashing internally. This is useful for hardware security modules (HSMs) and other opaque key implementations.

type Verifier

type Verifier interface {
	// KID returns the key ID this verifier is associated with.
	KID() string

	// Algorithm returns the JWS algorithm name (e.g., "HS256", "RS256").
	Algorithm() string

	// Verify checks if a signature is valid for the given protected header and payload.
	// Returns nil if the signature is valid, or [ErrInvalidSignature] if not.
	Verify(protected jose.Header, payload, signature []byte) error
}

Verifier validates cryptographic signatures.

Create verifiers using the algorithm-specific constructors:

Verifiers are safe for concurrent use.

func ECDSAVerifier

func ECDSAVerifier(key *ecdsa.PublicKey, kid string) (Verifier, error)

ECDSAVerifier returns a Verifier for ECDSA signatures using only a public key.

The curve of the public key determines the algorithm:

  • P-256 → ES256
  • P-384 → ES384
  • P-521 → ES512

func EdDSAVerifier

func EdDSAVerifier(pub ed25519.PublicKey, kid string) (Verifier, error)

EdDSAVerifier returns a Verifier for Ed25519 signatures.

Use this when you only have a public key and need to verify signatures without being able to create them.

func NewVerifier

func NewVerifier(pub crypto.PublicKey, kid string) (Verifier, error)

NewVerifier returns a new Verifier from the given public key.

func RSAPSSVerifier

func RSAPSSVerifier(hasher crypto.Hash, pub *rsa.PublicKey, kid string) (Verifier, error)

RSAPSSVerifier returns a Verifier for RSA-PSS signatures.

Use this when you only have a public key (e.g., received from a JWKS endpoint) and need to verify signatures without being able to create them.

func RSAVerifier

func RSAVerifier(hasher crypto.Hash, pub *rsa.PublicKey, kid string) (Verifier, error)

RSAVerifier returns a Verifier for RSA PKCS#1 v1.5 signatures.

Use this when you only have a public key (e.g., received from a JWKS endpoint) and need to verify signatures without being able to create them.