Files
varsig/varsig.go

156 lines
4.4 KiB
Go
Raw Normal View History

// Package varsig implements v1.0.0 of the [Varsig specification] with
// limited support for varsig < v1. This is primarily in support of the
// UCAN v1.0.0 specification and will be deprecated in the future.
//
// # Common algorithm naming
//
// While there is no strict need for compatibility with JWA/JWT/JWE/JWS,
// all attempts are made to keep the algorithm names here consistent with
// list made available at the [IANA Registry] titled "JSON Web Signature
// and Encryption Algorithms" (JOSE.)
//
// It should also be noted that algorithm in this context might in fact be
// a pseudonym - for cryptographical signing algorithms that require the
// signed data to be hashed first, these names commonly refer to the
// combination of that signing algorithm and the hash algorithm.
//
// [IANA Registry]]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
// [Varsig Specification]: https://github.com/ChainAgnostic/varsig
package varsig
import (
"encoding/binary"
2025-07-08 14:13:47 -04:00
"errors"
"io"
)
// Varsig represents types that describe how a signature was generated
// and thus how to interpret the signature and verify the signed data.
type Varsig interface {
2025-07-09 12:55:44 +02:00
// Version returns the varsig's version field.
Version() Version
2025-07-09 12:55:44 +02:00
// Discriminator returns the algorithm used to produce the corresponding signature.
Discriminator() Discriminator
2025-07-09 12:55:44 +02:00
// PayloadEncoding returns the codec that was used to encode the signed data.
PayloadEncoding() PayloadEncoding
2025-07-09 12:55:44 +02:00
// Signature returns the cryptographic signature of the signed data.
// This value is never present in a varsig >= v1 and must either be a valid
// signature with the correct length or empty in varsig < v1.
Signature() []byte
2025-07-09 12:55:44 +02:00
// Encode returns the encoded byte format of the varsig.
Encode() []byte
}
// Decode converts the provided data into one of the Varsig types
// provided by the DefaultRegistry.
func Decode(data []byte) (Varsig, error) {
return DefaultRegistry().Decode(data)
}
// DecodeStream converts data read from the provided io.Reader into one
// of the Varsig types provided by the DefaultRegistry.
func DecodeStream(r BytesReader) (Varsig, error) {
return DefaultRegistry().DecodeStream(r)
}
type varsig struct {
vers Version
disc Discriminator
payEnc PayloadEncoding
sig []byte
}
// Version returns the varsig's version field.
func (v varsig) Version() Version {
return v.vers
}
// Discriminator returns the algorithm used to produce the corresponding
// signature.
func (v varsig) Discriminator() Discriminator {
return v.disc
}
// PayloadEncoding returns the codec that was used to encode the signed
// data.
func (v varsig) PayloadEncoding() PayloadEncoding {
return v.payEnc
}
// Signature returns the cryptographic signature of the signed data. This
// value is never present in a varsig >= v1 and must either be a valid
// signature with the correct length or empty in varsig < v1.
func (v varsig) Signature() []byte {
return v.sig
}
func (v varsig) encode() []byte {
// Pre-allocate to the maximum size to avoid re-allocating.
// I think the maximum is 10 bytes, but it's all the same for go to allocate 16 (due to the small
// size allocation class), so we might as well get some headroom for bigger varints.
buf := make([]byte, 0, 16)
buf = binary.AppendUvarint(buf, Prefix)
if v.Version() == Version1 {
buf = binary.AppendUvarint(buf, uint64(Version1))
}
buf = binary.AppendUvarint(buf, uint64(v.disc))
return buf
}
func (v varsig) decodePayEncAndSig(r BytesReader) (PayloadEncoding, []byte, error) {
payEnc, err := DecodePayloadEncoding(r, v.Version())
if err != nil {
return 0, nil, err
}
var signature []byte
2025-07-08 14:13:47 -04:00
switch v.Version() {
case Version0:
signature, err = io.ReadAll(r)
if err != nil {
return 0, nil, err
}
2025-07-08 14:13:47 -04:00
case Version1:
_, err := r.ReadByte()
if err != nil && !errors.Is(err, io.EOF) {
return 0, nil, err
}
if err == nil {
return 0, nil, ErrUnexpectedSignaturePresent
}
default:
return 0, nil, ErrUnsupportedVersion
}
return payEnc, signature, nil
}
2025-07-08 14:13:47 -04:00
func validateSig[T Varsig](v T, expectedLength uint64) (T, error) {
if v.Version() == Version0 && len(v.Signature()) == 0 {
return v, ErrMissingSignature
}
2025-07-08 14:13:47 -04:00
if v.Version() == Version0 && uint64(len(v.Signature())) != expectedLength {
return *new(T), ErrUnexpectedSignatureSize
}
2025-07-08 14:13:47 -04:00
if v.Version() == Version1 && len(v.Signature()) != 0 {
return *new(T), ErrUnexpectedSignaturePresent
}
2025-07-08 14:13:47 -04:00
return v, nil
}
type BytesReader interface {
io.ByteReader
io.Reader
}