90 lines
2.5 KiB
Go
90 lines
2.5 KiB
Go
package catalog
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidDocument = errors.New("invalid catalog document")
|
|
ErrDuplicateField = errors.New("duplicate catalog field")
|
|
ErrUnsupportedNumber = errors.New("unsupported catalog number")
|
|
ErrSignatureMissing = errors.New("catalog signature missing")
|
|
ErrSignatureInvalid = errors.New("catalog signature invalid")
|
|
ErrPublicKeyInvalid = errors.New("catalog public key invalid")
|
|
)
|
|
|
|
// VerifiedDocument contains the original signed document and its signing bytes.
|
|
type VerifiedDocument struct {
|
|
Bytes []byte
|
|
SignedPayload []byte
|
|
}
|
|
|
|
// Verifier validates signed Catalog JSON documents with one Ed25519 public key.
|
|
type Verifier struct {
|
|
publicKey ed25519.PublicKey
|
|
}
|
|
|
|
// NewVerifier copies and validates the public key.
|
|
func NewVerifier(publicKey []byte) (Verifier, error) {
|
|
if len(publicKey) != ed25519.PublicKeySize {
|
|
return Verifier{}, fmt.Errorf(
|
|
"%w: got %d bytes, want %d",
|
|
ErrPublicKeyInvalid,
|
|
len(publicKey),
|
|
ed25519.PublicKeySize,
|
|
)
|
|
}
|
|
keyCopy := append(ed25519.PublicKey(nil), publicKey...)
|
|
return Verifier{publicKey: keyCopy}, nil
|
|
}
|
|
|
|
// Verify rejects ambiguous JSON and validates the top-level signature.
|
|
func (verifier Verifier) Verify(document []byte) (VerifiedDocument, error) {
|
|
rootValue, err := parseRestrictedJSON(document)
|
|
if err != nil {
|
|
return VerifiedDocument{}, err
|
|
}
|
|
root, ok := rootValue.(map[string]any)
|
|
if !ok {
|
|
return VerifiedDocument{}, fmt.Errorf("%w: root must be an object", ErrInvalidDocument)
|
|
}
|
|
|
|
signatureValue, exists := root["signature"]
|
|
if !exists {
|
|
return VerifiedDocument{}, ErrSignatureMissing
|
|
}
|
|
signatureText, ok := signatureValue.(string)
|
|
if !ok {
|
|
return VerifiedDocument{}, fmt.Errorf("%w: signature must be a string", ErrSignatureInvalid)
|
|
}
|
|
delete(root, "signature")
|
|
|
|
signedPayload, err := canonicalJSON(root)
|
|
if err != nil {
|
|
return VerifiedDocument{}, err
|
|
}
|
|
signature, err := base64.StdEncoding.Strict().DecodeString(signatureText)
|
|
if err != nil {
|
|
return VerifiedDocument{}, fmt.Errorf("%w: base64: %v", ErrSignatureInvalid, err)
|
|
}
|
|
if len(signature) != ed25519.SignatureSize {
|
|
return VerifiedDocument{}, fmt.Errorf(
|
|
"%w: got %d signature bytes, want %d",
|
|
ErrSignatureInvalid,
|
|
len(signature),
|
|
ed25519.SignatureSize,
|
|
)
|
|
}
|
|
if !ed25519.Verify(verifier.publicKey, signedPayload, signature) {
|
|
return VerifiedDocument{}, ErrSignatureInvalid
|
|
}
|
|
|
|
return VerifiedDocument{
|
|
Bytes: append([]byte(nil), document...),
|
|
SignedPayload: append([]byte(nil), signedPayload...),
|
|
}, nil
|
|
}
|