Initial QSfera import
This commit is contained in:
+38
@@ -0,0 +1,38 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "jwsbb",
|
||||
srcs = [
|
||||
"crypto_signer.go",
|
||||
"ecdsa.go",
|
||||
"eddsa.go",
|
||||
"format.go",
|
||||
"hmac.go",
|
||||
"jwsbb.go",
|
||||
"rsa.go",
|
||||
"sign.go",
|
||||
"verify.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jws/jwsbb",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/base64",
|
||||
"//internal/ecutil",
|
||||
"//internal/jwxio",
|
||||
"//internal/keyconv",
|
||||
"//internal/pool",
|
||||
"//internal/tokens",
|
||||
"//jws/internal/keytype",
|
||||
"@com_github_lestrrat_go_dsig//:dsig",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "jwsbb_test",
|
||||
srcs = ["jwsbb_test.go"],
|
||||
embed = [":jwsbb"],
|
||||
deps = [
|
||||
"//internal/base64",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// cryptosign is a low-level function that signs a payload using a crypto.Signer.
|
||||
// If hash is crypto.Hash(0), the payload is signed directly without hashing.
|
||||
// Otherwise, the payload is hashed using the specified hash function before signing.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
func cryptosign(signer crypto.Signer, payload []byte, hash crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) {
|
||||
if rr == nil {
|
||||
rr = rand.Reader
|
||||
}
|
||||
|
||||
var digest []byte
|
||||
if hash == crypto.Hash(0) {
|
||||
digest = payload
|
||||
} else {
|
||||
h := hash.New()
|
||||
if _, err := h.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload to hash: %w`, err)
|
||||
}
|
||||
digest = h.Sum(nil)
|
||||
}
|
||||
return signer.Sign(rr, digest, opts)
|
||||
}
|
||||
|
||||
// SignCryptoSigner generates a signature using a crypto.Signer interface.
|
||||
// This function can be used for hardware security modules, smart cards,
|
||||
// and other implementations of the crypto.Signer interface.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// Returns the signature bytes or an error if signing fails.
|
||||
func SignCryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) {
|
||||
if signer == nil {
|
||||
return nil, fmt.Errorf("jwsbb.SignCryptoSignerRaw: signer is nil")
|
||||
}
|
||||
return cryptosign(signer, raw, h, opts, rr)
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/ecutil"
|
||||
)
|
||||
|
||||
// ecdsaHashToDsigAlgorithm maps ECDSA hash functions to dsig algorithm constants
|
||||
func ecdsaHashToDsigAlgorithm(h crypto.Hash) (string, error) {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return dsig.ECDSAWithP256AndSHA256, nil
|
||||
case crypto.SHA384:
|
||||
return dsig.ECDSAWithP384AndSHA384, nil
|
||||
case crypto.SHA512:
|
||||
return dsig.ECDSAWithP521AndSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported ECDSA hash function: %v", h)
|
||||
}
|
||||
}
|
||||
|
||||
// UnpackASN1ECDSASignature unpacks an ASN.1 encoded ECDSA signature into r and s values.
|
||||
// This is typically used when working with crypto.Signer interfaces that return ASN.1 encoded signatures.
|
||||
func UnpackASN1ECDSASignature(signed []byte, r, s *big.Int) error {
|
||||
// Okay, this is silly, but hear me out. When we use the
|
||||
// crypto.Signer interface, the PrivateKey is hidden.
|
||||
// But we need some information about the key (its bit size).
|
||||
//
|
||||
// So while silly, we're going to have to make another call
|
||||
// here and fetch the Public key.
|
||||
// (This probably means that this information should be cached somewhere)
|
||||
var p struct {
|
||||
R *big.Int // TODO: get this from a pool?
|
||||
S *big.Int
|
||||
}
|
||||
if _, err := asn1.Unmarshal(signed, &p); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal ASN1 encoded signature: %w`, err)
|
||||
}
|
||||
|
||||
r.Set(p.R)
|
||||
s.Set(p.S)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnpackECDSASignature unpacks a JWS-format ECDSA signature into r and s values.
|
||||
// The signature should be in the format specified by RFC 7515 (r||s as fixed-length byte arrays).
|
||||
func UnpackECDSASignature(signature []byte, pubkey *ecdsa.PublicKey, r, s *big.Int) error {
|
||||
keySize := ecutil.CalculateKeySize(pubkey.Curve)
|
||||
if len(signature) != keySize*2 {
|
||||
return fmt.Errorf(`invalid signature length for curve %q`, pubkey.Curve.Params().Name)
|
||||
}
|
||||
|
||||
r.SetBytes(signature[:keySize])
|
||||
s.SetBytes(signature[keySize:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackECDSASignature packs the r and s values from an ECDSA signature into a JWS-format byte slice.
|
||||
// The output format follows RFC 7515: r||s as fixed-length byte arrays.
|
||||
func PackECDSASignature(r *big.Int, sbig *big.Int, curveBits int) ([]byte, error) {
|
||||
keyBytes := curveBits / 8
|
||||
if curveBits%8 > 0 {
|
||||
keyBytes++
|
||||
}
|
||||
|
||||
// Serialize r and s into fixed-length bytes
|
||||
rBytes := r.Bytes()
|
||||
rBytesPadded := make([]byte, keyBytes)
|
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||
|
||||
sBytes := sbig.Bytes()
|
||||
sBytesPadded := make([]byte, keyBytes)
|
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||
|
||||
// Output as r||s
|
||||
return append(rBytesPadded, sBytesPadded...), nil
|
||||
}
|
||||
|
||||
// SignECDSA generates an ECDSA signature for the given payload using the specified private key and hash.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. if rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignECDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignECDSA(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
dsigAlg, err := ecdsaHashToDsigAlgorithm(h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jwsbb.SignECDSA: %w", err)
|
||||
}
|
||||
|
||||
return dsig.Sign(key, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
// SignECDSACryptoSigner generates an ECDSA signature using a crypto.Signer interface.
|
||||
// This function works with hardware security modules and other crypto.Signer implementations.
|
||||
// The signature is converted from ASN.1 format to JWS format (r||s).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
func SignECDSACryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
signed, err := SignCryptoSigner(signer, raw, h, h, rr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to sign payload using crypto.Signer: %w`, err)
|
||||
}
|
||||
|
||||
return signECDSACryptoSigner(signer, signed)
|
||||
}
|
||||
|
||||
func signECDSACryptoSigner(signer crypto.Signer, signed []byte) ([]byte, error) {
|
||||
cpub := signer.Public()
|
||||
pubkey, ok := cpub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`expected *ecdsa.PublicKey, got %T`, pubkey)
|
||||
}
|
||||
curveBits := pubkey.Curve.Params().BitSize
|
||||
|
||||
var r, s big.Int
|
||||
if err := UnpackASN1ECDSASignature(signed, &r, &s); err != nil {
|
||||
return nil, fmt.Errorf(`failed to unpack ASN1 encoded signature: %w`, err)
|
||||
}
|
||||
|
||||
return PackECDSASignature(&r, &s, curveBits)
|
||||
}
|
||||
|
||||
func ecdsaVerify(key *ecdsa.PublicKey, buf []byte, h crypto.Hash, r, s *big.Int) error {
|
||||
hasher := h.New()
|
||||
hasher.Write(buf)
|
||||
digest := hasher.Sum(nil)
|
||||
if !ecdsa.Verify(key, digest, r, s) {
|
||||
return fmt.Errorf("jwsbb.ECDSAVerifier: invalid ECDSA signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyECDSA verifies an ECDSA signature for the given payload.
|
||||
// This function verifies the signature using the specified public key and hash algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyECDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyECDSA(key *ecdsa.PublicKey, payload, signature []byte, h crypto.Hash) error {
|
||||
dsigAlg, err := ecdsaHashToDsigAlgorithm(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("jwsbb.VerifyECDSA: %w", err)
|
||||
}
|
||||
|
||||
return dsig.Verify(key, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
// VerifyECDSACryptoSigner verifies an ECDSA signature for crypto.Signer implementations.
|
||||
// This function is useful for verifying signatures created by hardware security modules
|
||||
// or other implementations of the crypto.Signer interface.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyECDSACryptoSigner(signer crypto.Signer, payload, signature []byte, h crypto.Hash) error {
|
||||
var pubkey *ecdsa.PublicKey
|
||||
switch cpub := signer.Public(); cpub := cpub.(type) {
|
||||
case ecdsa.PublicKey:
|
||||
pubkey = &cpub
|
||||
case *ecdsa.PublicKey:
|
||||
pubkey = cpub
|
||||
default:
|
||||
return fmt.Errorf(`jwsbb.VerifyECDSACryptoSigner: expected *ecdsa.PublicKey, got %T`, cpub)
|
||||
}
|
||||
|
||||
var r, s big.Int
|
||||
if err := UnpackECDSASignature(signature, pubkey, &r, &s); err != nil {
|
||||
return fmt.Errorf("jwsbb.ECDSAVerifier: failed to unpack ASN.1 encoded ECDSA signature: %w", err)
|
||||
}
|
||||
|
||||
return ecdsaVerify(pubkey, payload, h, &r, &s)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
// SignEdDSA generates an EdDSA (Ed25519) signature for the given payload.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and doesn't require additional hashing of the input.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignEdDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignEdDSA(key ed25519.PrivateKey, payload []byte) ([]byte, error) {
|
||||
// Use dsig.Sign with EdDSA algorithm constant
|
||||
return dsig.Sign(key, dsig.EdDSA, payload, nil)
|
||||
}
|
||||
|
||||
// VerifyEdDSA verifies an EdDSA (Ed25519) signature for the given payload.
|
||||
// This function verifies the signature using Ed25519 verification algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and provides strong security guarantees without requiring hash function selection.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyEdDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyEdDSA(key ed25519.PublicKey, payload, signature []byte) error {
|
||||
// Use dsig.Verify with EdDSA algorithm constant
|
||||
return dsig.Verify(key, dsig.EdDSA, payload, signature)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build jwx_es256k
|
||||
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
dsigsecp256k1 "github.com/lestrrat-go/dsig-secp256k1"
|
||||
)
|
||||
|
||||
const es256k = "ES256K"
|
||||
|
||||
func init() {
|
||||
// Add ES256K mapping when this build tag is enabled
|
||||
jwsToDsigAlgorithm[es256k] = dsigsecp256k1.ECDSAWithSecp256k1AndSHA256
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/jwxio"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
// SignBuffer combines the base64-encoded header and payload into a single byte slice
|
||||
// for signing purposes. This creates the signing input according to JWS specification (RFC 7515).
|
||||
// The result should be passed to signature generation functions.
|
||||
//
|
||||
// Parameters:
|
||||
// - buf: Reusable buffer (can be nil for automatic allocation)
|
||||
// - hdr: Raw header bytes (will be base64-encoded)
|
||||
// - payload: Raw payload bytes (encoded based on encodePayload flag)
|
||||
// - encoder: Base64 encoder to use for encoding components
|
||||
// - encodePayload: If true, payload is base64-encoded; if false, payload is used as-is
|
||||
//
|
||||
// Returns the constructed signing input in the format: base64(header).base64(payload) or base64(header).payload
|
||||
func SignBuffer(buf, hdr, payload []byte, encoder base64.Encoder, encodePayload bool) []byte {
|
||||
l := encoder.EncodedLen(len(hdr)+len(payload)) + 1
|
||||
if cap(buf) < l {
|
||||
buf = make([]byte, 0, l)
|
||||
}
|
||||
buf = buf[:0]
|
||||
buf = encoder.AppendEncode(buf, hdr)
|
||||
buf = append(buf, tokens.Period)
|
||||
if encodePayload {
|
||||
buf = encoder.AppendEncode(buf, payload)
|
||||
} else {
|
||||
buf = append(buf, payload...)
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// AppendSignature appends a base64-encoded signature to a JWS signing input buffer.
|
||||
// This completes the compact JWS serialization by adding the final signature component.
|
||||
// The input buffer should contain the signing input (header.payload), and this function
|
||||
// adds the period separator and base64-encoded signature.
|
||||
//
|
||||
// Parameters:
|
||||
// - buf: Buffer containing the signing input (typically from SignBuffer)
|
||||
// - signature: Raw signature bytes (will be base64-encoded)
|
||||
// - encoder: Base64 encoder to use for encoding the signature
|
||||
//
|
||||
// Returns the complete compact JWS in the format: base64(header).base64(payload).base64(signature)
|
||||
func AppendSignature(buf, signature []byte, encoder base64.Encoder) []byte {
|
||||
l := len(buf) + len(signature) + 1
|
||||
if cap(buf) < l {
|
||||
buf = make([]byte, 0, l)
|
||||
}
|
||||
buf = append(buf, tokens.Period)
|
||||
buf = encoder.AppendEncode(buf, signature)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// JoinCompact creates a complete compact JWS serialization from individual components.
|
||||
// This is a one-step function that combines header, payload, and signature into the final JWS format.
|
||||
// It includes safety checks to prevent excessive memory allocation.
|
||||
//
|
||||
// Parameters:
|
||||
// - buf: Reusable buffer (can be nil for automatic allocation)
|
||||
// - hdr: Raw header bytes (will be base64-encoded)
|
||||
// - payload: Raw payload bytes (encoded based on encodePayload flag)
|
||||
// - signature: Raw signature bytes (will be base64-encoded)
|
||||
// - encoder: Base64 encoder to use for encoding all components
|
||||
// - encodePayload: If true, payload is base64-encoded; if false, payload is used as-is
|
||||
//
|
||||
// Returns the complete compact JWS or an error if the total size exceeds safety limits (1GB).
|
||||
func JoinCompact(buf, hdr, payload, signature []byte, encoder base64.Encoder, encodePayload bool) ([]byte, error) {
|
||||
const MaxBufferSize = 1 << 30 // 1 GB
|
||||
totalSize := len(hdr) + len(payload) + len(signature) + 2
|
||||
if totalSize > MaxBufferSize {
|
||||
return nil, errors.New("input sizes exceed maximum allowable buffer size")
|
||||
}
|
||||
if cap(buf) < totalSize {
|
||||
buf = make([]byte, 0, totalSize)
|
||||
}
|
||||
buf = buf[:0]
|
||||
buf = encoder.AppendEncode(buf, hdr)
|
||||
buf = append(buf, tokens.Period)
|
||||
if encodePayload {
|
||||
buf = encoder.AppendEncode(buf, payload)
|
||||
} else {
|
||||
buf = append(buf, payload...)
|
||||
}
|
||||
buf = append(buf, tokens.Period)
|
||||
buf = encoder.AppendEncode(buf, signature)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
var compactDelim = []byte{tokens.Period}
|
||||
|
||||
var errInvalidNumberOfSegments = errors.New(`jwsbb: invalid number of segments`)
|
||||
|
||||
// InvalidNumberOfSegmentsError returns the standard error for invalid JWS segment count.
|
||||
// A valid compact JWS must have exactly 3 segments separated by periods: header.payload.signature
|
||||
func InvalidNumberOfSegmentsError() error {
|
||||
return errInvalidNumberOfSegments
|
||||
}
|
||||
|
||||
// SplitCompact parses a compact JWS serialization into its three components.
|
||||
// This function validates that the input has exactly 3 segments separated by periods
|
||||
// and returns the base64-encoded components without decoding them.
|
||||
//
|
||||
// Parameters:
|
||||
// - src: Complete compact JWS string as bytes
|
||||
//
|
||||
// Returns:
|
||||
// - protected: Base64-encoded protected header
|
||||
// - payload: Base64-encoded payload (or raw payload if b64=false was used)
|
||||
// - signature: Base64-encoded signature
|
||||
// - err: Error if the format is invalid or segment count is wrong
|
||||
func SplitCompact(src []byte) (protected, payload, signature []byte, err error) {
|
||||
var s []byte
|
||||
var ok bool
|
||||
|
||||
protected, s, ok = bytes.Cut(src, compactDelim)
|
||||
if !ok { // no period found
|
||||
return nil, nil, nil, InvalidNumberOfSegmentsError()
|
||||
}
|
||||
payload, s, ok = bytes.Cut(s, compactDelim)
|
||||
if !ok { // only one period found
|
||||
return nil, nil, nil, InvalidNumberOfSegmentsError()
|
||||
}
|
||||
signature, _, ok = bytes.Cut(s, compactDelim)
|
||||
if ok { // three periods found
|
||||
return nil, nil, nil, InvalidNumberOfSegmentsError()
|
||||
}
|
||||
return protected, payload, signature, nil
|
||||
}
|
||||
|
||||
// SplitCompactString is a convenience wrapper around SplitCompact for string inputs.
|
||||
// It converts the string to bytes and parses the compact JWS serialization.
|
||||
//
|
||||
// Parameters:
|
||||
// - src: Complete compact JWS as a string
|
||||
//
|
||||
// Returns the same components as SplitCompact: protected header, payload, signature, and error.
|
||||
func SplitCompactString(src string) (protected, payload, signature []byte, err error) {
|
||||
return SplitCompact([]byte(src))
|
||||
}
|
||||
|
||||
// SplitCompactReader parses a compact JWS serialization from an io.Reader.
|
||||
// This function handles both finite and streaming sources efficiently.
|
||||
// For finite sources, it reads all data at once. For streaming sources,
|
||||
// it uses a buffer-based approach to find segment boundaries.
|
||||
//
|
||||
// Parameters:
|
||||
// - rdr: Reader containing the compact JWS data
|
||||
//
|
||||
// Returns:
|
||||
// - protected: Base64-encoded protected header
|
||||
// - payload: Base64-encoded payload (or raw payload if b64=false was used)
|
||||
// - signature: Base64-encoded signature
|
||||
// - err: Error if reading fails or the format is invalid
|
||||
//
|
||||
// The function validates that exactly 3 segments are present, separated by periods.
|
||||
func SplitCompactReader(rdr io.Reader) (protected, payload, signature []byte, err error) {
|
||||
data, err := jwxio.ReadAllFromFiniteSource(rdr)
|
||||
if err == nil {
|
||||
return SplitCompact(data)
|
||||
}
|
||||
|
||||
if !errors.Is(err, jwxio.NonFiniteSourceError()) {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var periods int
|
||||
var state int
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
var sofar []byte
|
||||
|
||||
for {
|
||||
// read next bytes
|
||||
n, err := rdr.Read(buf)
|
||||
// return on unexpected read error
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, nil, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// append to current buffer
|
||||
sofar = append(sofar, buf[:n]...)
|
||||
// loop to capture multiple tokens.Period in current buffer
|
||||
for loop := true; loop; {
|
||||
var i = bytes.IndexByte(sofar, tokens.Period)
|
||||
if i == -1 && err != io.EOF {
|
||||
// no tokens.Period found -> exit and read next bytes (outer loop)
|
||||
loop = false
|
||||
continue
|
||||
} else if i == -1 && err == io.EOF {
|
||||
// no tokens.Period found -> process rest and exit
|
||||
i = len(sofar)
|
||||
loop = false
|
||||
} else {
|
||||
// tokens.Period found
|
||||
periods++
|
||||
}
|
||||
|
||||
// Reaching this point means we have found a tokens.Period or EOF and process the rest of the buffer
|
||||
switch state {
|
||||
case 0:
|
||||
protected = sofar[:i]
|
||||
state++
|
||||
case 1:
|
||||
payload = sofar[:i]
|
||||
state++
|
||||
case 2:
|
||||
signature = sofar[:i]
|
||||
}
|
||||
// Shorten current buffer
|
||||
if len(sofar) > i {
|
||||
sofar = sofar[i+1:]
|
||||
}
|
||||
}
|
||||
// Exit on EOF
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
if periods != 2 {
|
||||
return nil, nil, nil, InvalidNumberOfSegmentsError()
|
||||
}
|
||||
|
||||
return protected, payload, signature, nil
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
type headerNotFoundError struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (e headerNotFoundError) Error() string {
|
||||
return fmt.Sprintf(`jwsbb: header "%s" not found`, e.key)
|
||||
}
|
||||
|
||||
func (e headerNotFoundError) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
case headerNotFoundError, *headerNotFoundError:
|
||||
// If the target is a headerNotFoundError or a pointer to it, we
|
||||
// consider it a match
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ErrHeaderNotFound returns an error that can be passed to `errors.Is` to check if the error is
|
||||
// the result of the field not being found
|
||||
func ErrHeaderNotFound() error {
|
||||
return headerNotFoundError{}
|
||||
}
|
||||
|
||||
// ErrFieldNotFound is an alias for ErrHeaderNotFound, and is deprecated. It was a misnomer.
|
||||
// It will be removed in a future release.
|
||||
func ErrFieldNotFound() error {
|
||||
return ErrHeaderNotFound()
|
||||
}
|
||||
|
||||
// Header is an object that allows you to access the JWS header in a quick and
|
||||
// dirty way. It does not verify anything, it does not know anything about what
|
||||
// each header field means, and it does not care about the JWS specification.
|
||||
// But when you need to access the JWS header for that one field that you
|
||||
// need, this is the object you want to use.
|
||||
//
|
||||
// As of this writing, HeaderParser cannot be used from concurrent goroutines.
|
||||
// You will need to create a new instance for each goroutine that needs to parse a JWS header.
|
||||
// Also, in general values obtained from this object should only be used
|
||||
// while the Header object is still in scope.
|
||||
//
|
||||
// This type is experimental and may change or be removed in the future.
|
||||
type Header interface {
|
||||
// I'm hiding this behind an interface so that users won't accidentally
|
||||
// rely on the underlying json handler implementation, nor the concrete
|
||||
// type name that jwsbb provides, as we may choose a different one in the future.
|
||||
jwsbbHeader()
|
||||
}
|
||||
|
||||
type header struct {
|
||||
v *fastjson.Value
|
||||
err error
|
||||
}
|
||||
|
||||
func (h *header) jwsbbHeader() {}
|
||||
|
||||
// HeaderParseCompact parses a JWS header from a compact serialization format.
|
||||
// You will need to call HeaderGet* functions to extract the values from the header.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderParseCompact(buf []byte) Header {
|
||||
decoded, err := base64.Decode(buf)
|
||||
if err != nil {
|
||||
return &header{err: err}
|
||||
}
|
||||
return HeaderParse(decoded)
|
||||
}
|
||||
|
||||
// HeaderParse parses a JWS header from a byte slice containing the decoded JSON.
|
||||
// You will need to call HeaderGet* functions to extract the values from the header.
|
||||
//
|
||||
// Unlike HeaderParseCompact, this function does not perform any base64 decoding.
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderParse(decoded []byte) Header {
|
||||
var p fastjson.Parser
|
||||
v, err := p.ParseBytes(decoded)
|
||||
if err != nil {
|
||||
return &header{err: err}
|
||||
}
|
||||
return &header{
|
||||
v: v,
|
||||
}
|
||||
}
|
||||
|
||||
func headerGet(h Header, key string) (*fastjson.Value, error) {
|
||||
//nolint:forcetypeassert
|
||||
hh := h.(*header) // we _know_ this can't be another type
|
||||
if hh.err != nil {
|
||||
return nil, hh.err
|
||||
}
|
||||
|
||||
v := hh.v.Get(key)
|
||||
if v == nil {
|
||||
return nil, headerNotFoundError{key: key}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// HeaderGetString returns the string value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not a string.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetString(h Header, key string) (string, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sb, err := v.StringBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(sb), nil
|
||||
}
|
||||
|
||||
// HeaderGetBool returns the boolean value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not a boolean.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetBool(h Header, key string) (bool, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return v.Bool()
|
||||
}
|
||||
|
||||
// HeaderGetFloat64 returns the float64 value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not a float64.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetFloat64(h Header, key string) (float64, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v.Float64()
|
||||
}
|
||||
|
||||
// HeaderGetInt returns the int value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not an int.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetInt(h Header, key string) (int, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v.Int()
|
||||
}
|
||||
|
||||
// HeaderGetInt64 returns the int64 value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not an int64.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetInt64(h Header, key string) (int64, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v.Int64()
|
||||
}
|
||||
|
||||
// HeaderGetStringBytes returns the byte slice value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not a byte slice.
|
||||
//
|
||||
// Because of limitations of the underlying library, you cannot use the return value
|
||||
// of this function after the parser is garbage collected.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetStringBytes(h Header, key string) ([]byte, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v.StringBytes()
|
||||
}
|
||||
|
||||
// HeaderGetUint returns the uint value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not a uint.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetUint(h Header, key string) (uint, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v.Uint()
|
||||
}
|
||||
|
||||
// HeaderGetUint64 returns the uint64 value for the given key from the JWS header.
|
||||
// An error is returned if the JSON was not valid, if the key does not exist,
|
||||
// or if the value is not a uint64.
|
||||
//
|
||||
// This function is experimental and may change or be removed in the future.
|
||||
func HeaderGetUint64(h Header, key string) (uint64, error) {
|
||||
v, err := headerGet(h, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return v.Uint64()
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
// hmacHashToDsigAlgorithm maps HMAC hash function sizes to dsig algorithm constants
|
||||
func hmacHashToDsigAlgorithm(hfunc func() hash.Hash) (string, error) {
|
||||
h := hfunc()
|
||||
switch h.Size() {
|
||||
case 32: // SHA256
|
||||
return dsig.HMACWithSHA256, nil
|
||||
case 48: // SHA384
|
||||
return dsig.HMACWithSHA384, nil
|
||||
case 64: // SHA512
|
||||
return dsig.HMACWithSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported HMAC hash function: size=%d", h.Size())
|
||||
}
|
||||
}
|
||||
|
||||
// SignHMAC generates an HMAC signature for the given payload using the specified hash function and key.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignHMAC. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignHMAC(key, payload []byte, hfunc func() hash.Hash) ([]byte, error) {
|
||||
dsigAlg, err := hmacHashToDsigAlgorithm(hfunc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jwsbb.SignHMAC: %w", err)
|
||||
}
|
||||
|
||||
return dsig.Sign(key, dsigAlg, payload, nil)
|
||||
}
|
||||
|
||||
// VerifyHMAC verifies an HMAC signature for the given payload.
|
||||
// This function verifies the signature using the specified key and hash function.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyHMAC. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyHMAC(key, payload, signature []byte, hfunc func() hash.Hash) error {
|
||||
dsigAlg, err := hmacHashToDsigAlgorithm(hfunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("jwsbb.VerifyHMAC: %w", err)
|
||||
}
|
||||
|
||||
return dsig.Verify(key, dsigAlg, payload, signature)
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// Package jwsbb provides the building blocks (hence the name "bb") for JWS operations.
|
||||
// It should be thought of as a low-level API, almost akin to internal packages
|
||||
// that should not be used directly by users of the jwx package. However, these exist
|
||||
// to provide a more efficient way to perform JWS operations without the overhead of
|
||||
// the higher-level jws package to power-users who know what they are doing.
|
||||
//
|
||||
// This package is currently considered EXPERIMENTAL, and the API may change
|
||||
// without notice. It is not recommended to use this package unless you are
|
||||
// fully aware of the implications of using it.
|
||||
//
|
||||
// All bb packages in jwx follow the same design principles:
|
||||
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
|
||||
// 2. All exported functions are strongly typed (i.e. they do not take `any` types unless they absolutely have to).
|
||||
// 3. Does not rely on other public jwx packages (they are standalone, except for internal packages).
|
||||
//
|
||||
// This implementation uses github.com/lestrrat-go/dsig as the underlying signature provider.
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
// JWS algorithm name constants
|
||||
const (
|
||||
// HMAC algorithms
|
||||
hs256 = "HS256"
|
||||
hs384 = "HS384"
|
||||
hs512 = "HS512"
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
rs256 = "RS256"
|
||||
rs384 = "RS384"
|
||||
rs512 = "RS512"
|
||||
|
||||
// RSA PSS algorithms
|
||||
ps256 = "PS256"
|
||||
ps384 = "PS384"
|
||||
ps512 = "PS512"
|
||||
|
||||
// ECDSA algorithms
|
||||
es256 = "ES256"
|
||||
es384 = "ES384"
|
||||
es512 = "ES512"
|
||||
|
||||
// EdDSA algorithm
|
||||
edDSA = "EdDSA"
|
||||
)
|
||||
|
||||
// Signer is a generic interface that defines the method for signing payloads.
|
||||
// The type parameter K represents the key type (e.g., []byte for HMAC keys,
|
||||
// *rsa.PrivateKey for RSA keys, *ecdsa.PrivateKey for ECDSA keys).
|
||||
type Signer[K any] interface {
|
||||
Sign(key K, payload []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// Verifier is a generic interface that defines the method for verifying signatures.
|
||||
// The type parameter K represents the key type (e.g., []byte for HMAC keys,
|
||||
// *rsa.PublicKey for RSA keys, *ecdsa.PublicKey for ECDSA keys).
|
||||
type Verifier[K any] interface {
|
||||
Verify(key K, buf []byte, signature []byte) error
|
||||
}
|
||||
|
||||
// JWS to dsig algorithm mapping
|
||||
var jwsToDsigAlgorithm = map[string]string{
|
||||
// HMAC algorithms
|
||||
hs256: dsig.HMACWithSHA256,
|
||||
hs384: dsig.HMACWithSHA384,
|
||||
hs512: dsig.HMACWithSHA512,
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
rs256: dsig.RSAPKCS1v15WithSHA256,
|
||||
rs384: dsig.RSAPKCS1v15WithSHA384,
|
||||
rs512: dsig.RSAPKCS1v15WithSHA512,
|
||||
|
||||
// RSA PSS algorithms
|
||||
ps256: dsig.RSAPSSWithSHA256,
|
||||
ps384: dsig.RSAPSSWithSHA384,
|
||||
ps512: dsig.RSAPSSWithSHA512,
|
||||
|
||||
// ECDSA algorithms
|
||||
es256: dsig.ECDSAWithP256AndSHA256,
|
||||
es384: dsig.ECDSAWithP384AndSHA384,
|
||||
es512: dsig.ECDSAWithP521AndSHA512,
|
||||
// Note: ES256K requires external dependency and is handled separately
|
||||
|
||||
// EdDSA algorithm
|
||||
edDSA: dsig.EdDSA,
|
||||
}
|
||||
|
||||
// getDsigAlgorithm returns the dsig algorithm name for a JWS algorithm
|
||||
func getDsigAlgorithm(jwsAlg string) (string, bool) {
|
||||
dsigAlg, ok := jwsToDsigAlgorithm[jwsAlg]
|
||||
return dsigAlg, ok
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
// rsaHashToDsigAlgorithm maps RSA hash functions to dsig algorithm constants
|
||||
func rsaHashToDsigAlgorithm(h crypto.Hash, pss bool) (string, error) {
|
||||
if pss {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return dsig.RSAPSSWithSHA256, nil
|
||||
case crypto.SHA384:
|
||||
return dsig.RSAPSSWithSHA384, nil
|
||||
case crypto.SHA512:
|
||||
return dsig.RSAPSSWithSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported hash algorithm for RSA-PSS: %v", h)
|
||||
}
|
||||
} else {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return dsig.RSAPKCS1v15WithSHA256, nil
|
||||
case crypto.SHA384:
|
||||
return dsig.RSAPKCS1v15WithSHA384, nil
|
||||
case crypto.SHA512:
|
||||
return dsig.RSAPKCS1v15WithSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported hash algorithm for RSA PKCS#1 v1.5: %v", h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SignRSA generates an RSA signature for the given payload using the specified private key and options.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// If pss is true, RSA-PSS is used; otherwise, PKCS#1 v1.5 is used.
|
||||
//
|
||||
// The rr parameter is an optional io.Reader that can be used to provide randomness for signing.
|
||||
// If rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignRSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignRSA(key *rsa.PrivateKey, payload []byte, h crypto.Hash, pss bool, rr io.Reader) ([]byte, error) {
|
||||
dsigAlg, err := rsaHashToDsigAlgorithm(h, pss)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jwsbb.SignRSA: %w", err)
|
||||
}
|
||||
|
||||
return dsig.Sign(key, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
// VerifyRSA verifies an RSA signature for the given payload and header.
|
||||
// This function constructs the signing input by encoding the header and payload according to JWS specification,
|
||||
// then verifies the signature using the specified public key and hash algorithm.
|
||||
// If pss is true, RSA-PSS verification is used; otherwise, PKCS#1 v1.5 verification is used.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyRSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyRSA(key *rsa.PublicKey, payload, signature []byte, h crypto.Hash, pss bool) error {
|
||||
dsigAlg, err := rsaHashToDsigAlgorithm(h, pss)
|
||||
if err != nil {
|
||||
return fmt.Errorf("jwsbb.VerifyRSA: %w", err)
|
||||
}
|
||||
|
||||
return dsig.Verify(key, dsigAlg, payload, signature)
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
)
|
||||
|
||||
// Sign generates a JWS signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the signer registered in the jwsbb package _ONLY_.
|
||||
// It does not support custom signers that the user might have registered.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
// Not all algorithms require this parameter, but it is included for consistency.
|
||||
// 99% of the time, you can pass nil for rr, and it will work fine.
|
||||
func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
dsigAlg, ok := getDsigAlgorithm(alg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
// Get dsig algorithm info to determine key conversion strategy
|
||||
dsigInfo, ok := dsig.GetAlgorithmInfo(dsigAlg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: dsig algorithm %q not registered`, dsigAlg)
|
||||
}
|
||||
|
||||
switch dsigInfo.Family {
|
||||
case dsig.HMAC:
|
||||
return dispatchHMACSign(key, dsigAlg, payload)
|
||||
case dsig.RSA:
|
||||
return dispatchRSASign(key, dsigAlg, payload, rr)
|
||||
case dsig.ECDSA:
|
||||
return dispatchECDSASign(key, dsigAlg, payload, rr)
|
||||
case dsig.EdDSAFamily:
|
||||
return dispatchEdDSASign(key, dsigAlg, payload, rr)
|
||||
default:
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: unsupported dsig algorithm family %q`, dsigInfo.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACSign(key any, dsigAlg string, payload []byte) ([]byte, error) {
|
||||
var hmackey []byte
|
||||
if err := keyconv.ByteSliceKey(&hmackey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. []byte is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Sign(hmackey, dsigAlg, payload, nil)
|
||||
}
|
||||
|
||||
func dispatchRSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an RSA key
|
||||
if _, ok := signer.Public().(*rsa.PublicKey); ok {
|
||||
return dsig.Sign(signer, dsigAlg, payload, rr)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var privkey *rsa.PrivateKey
|
||||
if err := keyconv.RSAPrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. *rsa.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Sign(privkey, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
func dispatchECDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an ECDSA key
|
||||
if _, ok := signer.Public().(*ecdsa.PublicKey); ok {
|
||||
return dsig.Sign(signer, dsigAlg, payload, rr)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var privkey *ecdsa.PrivateKey
|
||||
if err := keyconv.ECDSAPrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. *ecdsa.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Sign(privkey, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
func dispatchEdDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an EdDSA key
|
||||
if _, ok := signer.Public().(ed25519.PublicKey); ok {
|
||||
return dsig.Sign(signer, dsigAlg, payload, rr)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var privkey ed25519.PrivateKey
|
||||
if err := keyconv.Ed25519PrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. ed25519.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Sign(privkey, dsigAlg, payload, rr)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
)
|
||||
|
||||
// Verify verifies a JWS signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the verifier registered in the jwsbb package _ONLY_.
|
||||
// It does not support custom verifiers that the user might have registered.
|
||||
func Verify(key any, alg string, payload, signature []byte) error {
|
||||
dsigAlg, ok := getDsigAlgorithm(alg)
|
||||
if !ok {
|
||||
return fmt.Errorf(`jwsbb.Verify: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
// Get dsig algorithm info to determine key conversion strategy
|
||||
dsigInfo, ok := dsig.GetAlgorithmInfo(dsigAlg)
|
||||
if !ok {
|
||||
return fmt.Errorf(`jwsbb.Verify: dsig algorithm %q not registered`, dsigAlg)
|
||||
}
|
||||
|
||||
switch dsigInfo.Family {
|
||||
case dsig.HMAC:
|
||||
return dispatchHMACVerify(key, dsigAlg, payload, signature)
|
||||
case dsig.RSA:
|
||||
return dispatchRSAVerify(key, dsigAlg, payload, signature)
|
||||
case dsig.ECDSA:
|
||||
return dispatchECDSAVerify(key, dsigAlg, payload, signature)
|
||||
case dsig.EdDSAFamily:
|
||||
return dispatchEdDSAVerify(key, dsigAlg, payload, signature)
|
||||
default:
|
||||
return fmt.Errorf(`jwsbb.Verify: unsupported dsig algorithm family %q`, dsigInfo.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
var hmackey []byte
|
||||
if err := keyconv.ByteSliceKey(&hmackey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. []byte is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Verify(hmackey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
func dispatchRSAVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an RSA key
|
||||
if _, ok := signer.Public().(*rsa.PublicKey); ok {
|
||||
return dsig.Verify(signer, dsigAlg, payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var pubkey *rsa.PublicKey
|
||||
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. *rsa.PublicKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Verify(pubkey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
func dispatchECDSAVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an ECDSA key
|
||||
if _, ok := signer.Public().(*ecdsa.PublicKey); ok {
|
||||
return dsig.Verify(signer, dsigAlg, payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var pubkey *ecdsa.PublicKey
|
||||
if err := keyconv.ECDSAPublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. *ecdsa.PublicKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Verify(pubkey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
func dispatchEdDSAVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an EdDSA key
|
||||
if _, ok := signer.Public().(ed25519.PublicKey); ok {
|
||||
return dsig.Verify(signer, dsigAlg, payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var pubkey ed25519.PublicKey
|
||||
if err := keyconv.Ed25519PublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. ed25519.PublicKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Verify(pubkey, dsigAlg, payload, signature)
|
||||
}
|
||||
Reference in New Issue
Block a user