You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/provider/release.go

209 lines
8.0 KiB

package provider
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/codec"
"g.activething.com/go/DateKeys/profile"
)
// Schema constants of the release object, the content of a .dkr file (spec
// v0.15, §47.1).
const (
ReleaseTypeTag = "datekeys-release"
ReleaseSchemaVersion = 1
)
// Limits of the release object (spec v0.15, §47.1). The object has no frame:
// a file or an input of more than MaxReleaseObjectSize bytes is rejected
// before it is decoded, with ErrNonCanonicalCBOR, and no valid encoding comes
// close to it. MaxSignatureLen is the longest compressed point of
// BLS12-381, one of G2; Quicknet signs with 48 bytes, a point of G1.
const (
MaxReleaseObjectSize = 1024
MaxSignatureLen = 96
)
// MaxReleaseJSONSize bounds drand's JSON, which a reader accepts too as the
// input of the caller (spec v0.15, §47.1). It is the bound of a relay
// response in provider/drand.
const MaxReleaseJSONSize = 8 << 10
// releaseKeys is the number of keys of the release object, all required.
const releaseKeys = 5
// releaseWire is the CBOR map of the release object, keys 2 to 4; keys 0 and
// 1 are the constants ReleaseTypeTag and ReleaseSchemaVersion.
type releaseWire struct {
ChainHash []byte // key 2, 32 bytes
Round uint64 // key 3, 1..2^53-1
Signature []byte // key 4, 1..MaxSignatureLen bytes
}
func (w *releaseWire) encode(e *codec.Encoder) {
e.Map(releaseKeys)
e.Uint(0)
e.Text(ReleaseTypeTag)
e.Uint(1)
e.Uint(ReleaseSchemaVersion)
e.Uint(2)
e.Bstr(w.ChainHash)
e.Uint(3)
e.Uint(w.Round)
e.Uint(4)
e.Bstr(w.Signature)
}
// decode reads the map with every CDDL rule of the release object, all of
// them ErrNonCanonicalCBOR: what each field means against the pinned profile
// and the DateKey is checked by Verify, at step 10.
func (w *releaseWire) decode(d *codec.Decoder) error {
pairs, err := d.Map(releaseKeys)
if err != nil {
return err
}
if pairs != releaseKeys {
return fmt.Errorf("%d keys, want all %d: %w", pairs, releaseKeys, datekeys.ErrNonCanonicalCBOR)
}
for want := range uint64(releaseKeys) {
k, err := d.Key()
if err != nil {
return err
}
if k != want {
return fmt.Errorf("key %d where key %d was expected: %w", k, want, datekeys.ErrNonCanonicalCBOR)
}
switch k {
case 0:
_, err = d.Text(len(ReleaseTypeTag))
case 1:
_, err = d.Uint(ReleaseSchemaVersion)
case 2:
w.ChainHash, err = d.Bstr(32, 32)
case 3:
if w.Round, err = d.Uint(codec.MaxSafeUint); err == nil && w.Round == 0 {
err = fmt.Errorf("round 0: %w", datekeys.ErrNonCanonicalCBOR)
}
case 4:
w.Signature, err = d.Bstr(1, MaxSignatureLen)
}
if err != nil {
return fmt.Errorf("key %d: %w", k, err)
}
}
return d.EndMap()
}
// EncodeRelease returns the release object of r, the content of a .dkr file
// (spec v0.15, §47.1): its chain hash, its round and its signature. It does
// not verify the release: Verify does, against the pinned profile.
func EncodeRelease(r Release) ([]byte, error) {
switch {
case len(r.ChainHash) != 32:
return nil, fmt.Errorf("provider: release object: chain hash of %d bytes, want 32: %w", len(r.ChainHash), datekeys.ErrNonCanonicalCBOR)
case r.Round == 0 || r.Round > codec.MaxSafeUint:
return nil, fmt.Errorf("provider: release object: round %d outside 1..%d: %w", r.Round, uint64(codec.MaxSafeUint), datekeys.ErrNonCanonicalCBOR)
case len(r.Signature) == 0 || len(r.Signature) > MaxSignatureLen:
return nil, fmt.Errorf("provider: release object: signature of %d bytes outside 1..%d: %w", len(r.Signature), MaxSignatureLen, datekeys.ErrNonCanonicalCBOR)
}
w := releaseWire{ChainHash: r.ChainHash, Round: r.Round, Signature: r.Signature}
var e codec.Encoder
w.encode(&e)
return e.Out()
}
// DecodeRelease decodes a release object (spec v0.15, §47.1) with the layers
// of spec §69.1 that it has: its size, at most MaxReleaseObjectSize bytes;
// its type and schema version (ErrNonCanonicalCBOR, then
// ErrUnsupportedVersion); its encoding and schema (ErrNonCanonicalCBOR). The
// release it returns names its chain, and Verify checks it against the pinned
// profile at step 10 of spec §63: the chain hash, the round, the signature.
func DecodeRelease(b []byte) (Release, error) {
if len(b) == 0 || len(b) > MaxReleaseObjectSize {
return Release{}, fmt.Errorf("provider: release object of %d bytes, outside 1..%d: %w", len(b), MaxReleaseObjectSize, datekeys.ErrNonCanonicalCBOR)
}
if err := codec.CheckSchema(b, ReleaseTypeTag, ReleaseSchemaVersion); err != nil {
return Release{}, fmt.Errorf("provider: release object: %w", err)
}
var w releaseWire
if err := codec.Unmarshal(b, w.decode, w.encode); err != nil {
return Release{}, fmt.Errorf("provider: release object: %w", err)
}
return Release{Round: w.Round, Signature: w.Signature, ChainHash: w.ChainHash}, nil
}
// ParseRelease reads a release that the caller supplies: drand's JSON, when
// its first byte other than a JSON space is "{", or else a release object
// (spec v0.15, §47.1), with DecodeRelease. drand's JSON is the answer of a
// relay, {"round": …, "signature": "…"}, with an optional "randomness" that
// must be SHA-256 of the signature; it does not name its chain, so the
// release has no chain hash, and any failure to read it is
// ErrReleaseInvalid. It is accepted as input, never written.
func ParseRelease(b []byte) (Release, error) {
if t := bytes.TrimLeft(b, " \t\r\n"); len(t) > 0 && t[0] == '{' {
return parseDrandJSON(b)
}
return DecodeRelease(b)
}
// parseDrandJSON reads the JSON of a drand relay.
func parseDrandJSON(b []byte) (Release, error) {
if len(b) > MaxReleaseJSONSize {
return Release{}, fmt.Errorf("provider: drand JSON of %d bytes, larger than %d: %w", len(b), MaxReleaseJSONSize, datekeys.ErrReleaseInvalid)
}
var wire struct {
Round *uint64 `json:"round"`
Signature *string `json:"signature"`
Randomness string `json:"randomness"`
}
if err := json.Unmarshal(b, &wire); err != nil || wire.Round == nil || wire.Signature == nil {
return Release{}, fmt.Errorf("provider: drand JSON: malformed, or without round or signature: %w", datekeys.ErrReleaseInvalid)
}
sig, err := hex.DecodeString(*wire.Signature)
if err != nil {
return Release{}, fmt.Errorf("provider: drand JSON: signature is not hex: %w", datekeys.ErrReleaseInvalid)
}
if wire.Randomness != "" && !randomnessMatches(wire.Randomness, sig) {
return Release{}, fmt.Errorf("provider: drand JSON: randomness does not match the signature: %w", datekeys.ErrReleaseInvalid)
}
return Release{Round: *wire.Round, Signature: sig}, nil
}
// Supplier hands over a release that the caller has in hand (spec v0.15,
// §49, §63 step 9.c): a release object read from a .dkr file, drand's JSON
// that the person saved, or an entry of a local archive. It makes no network
// request, so capsule.Open asks it for the release without comparing its
// clock with the round time: a valid signature proves that the round was
// published.
//
// Supply returns the encoding of the release of c, as it is: a release
// object or drand's JSON, which capsule.Open decodes and verifies at step 10
// with the codes of that step. Without a release for c it returns an error
// that wraps datekeys.ErrReleaseUnavailable, the code of step 9.
type Supplier interface {
Supply(p *profile.Profile, c Condition) ([]byte, error)
}
// Encoded is a release in hand, already read: the bytes of a .dkr file or of
// drand's JSON. It supplies itself whatever the condition; step 10 compares
// its round with the DateKey.
type Encoded []byte
// Supply implements Supplier.
func (e Encoded) Supply(*profile.Profile, Condition) ([]byte, error) { return e, nil }
// NewReleaseObject returns the release object of a release of the profile p,
// with the chain hash of p: what a reader saves as a .dkr after verifying the
// release (spec v0.15, §62.1).
func NewReleaseObject(p *profile.Profile, r Release) ([]byte, error) {
r.ChainHash = p.ChainHash[:]
return EncodeRelease(r)
}
// chainHashHex is the chain hash of a release in the text of an error.
func chainHashHex(b []byte) string { return hex.EncodeToString(b) }

Powered by TurnKey Linux.