|
|
|
|
package extension
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/pathrule"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The extensions that spec v0.11 registers (§72).
|
|
|
|
|
const (
|
|
|
|
|
// NoteID is the public note of a capsule, in the noncritical array of
|
|
|
|
|
// PUBLIC_HEADER (§24.1), version 1: its data is the text in UTF-8, with
|
|
|
|
|
// no CBOR around it.
|
|
|
|
|
NoteID = "datekeys.note"
|
|
|
|
|
// CapsuleID is the extension of a .dkk that says what its capsule is and
|
|
|
|
|
// where to find it (§44.1), version 1.
|
|
|
|
|
CapsuleID = "datekeys.capsule"
|
|
|
|
|
// MaxNoteLen is the longest public note, in bytes.
|
|
|
|
|
MaxNoteLen = 1024
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// CheckNote checks the text of a public note with the rules of spec §24.1:
|
|
|
|
|
// from 1 to 1024 bytes of valid UTF-8 that meet the rules of the declared
|
|
|
|
|
// author of §29.6, one line without tabs and without spaces at the ends.
|
|
|
|
|
func CheckNote(text string) error {
|
|
|
|
|
switch {
|
|
|
|
|
case text == "" || len(text) > MaxNoteLen:
|
|
|
|
|
return fmt.Errorf("a public note of %d bytes, not 1 to %d: %w", len(text), MaxNoteLen, datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
case !utf8.ValidString(text):
|
|
|
|
|
return fmt.Errorf("a public note that is not valid UTF-8: %w", datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
|
|
|
|
if err := pathrule.CheckAuthor(text); err != nil {
|
|
|
|
|
return fmt.Errorf("a public note that breaks the rules of text: %v: %w", err, datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewNote returns the public note extension for text, which must pass
|
|
|
|
|
// CheckNote. A note is public: whoever has the .dkc reads it before the date,
|
|
|
|
|
// and with the date it can identify someone (spec §24.1).
|
|
|
|
|
func NewNote(text string) (Extension, error) {
|
|
|
|
|
if err := CheckNote(text); err != nil {
|
|
|
|
|
return Extension{}, err
|
|
|
|
|
}
|
|
|
|
|
return New(NoteID, 1, []byte(text))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note returns the text of the public note among the noncritical extensions
|
|
|
|
|
// of a PUBLIC_HEADER, and whether there is one that is usable: "", false when
|
|
|
|
|
// there is none, or when its data breaks the rules of §24.1, in which case
|
|
|
|
|
// a reader treats it as unusable and shows nothing (spec §54).
|
|
|
|
|
func Note(noncritical []Extension) (string, bool) {
|
|
|
|
|
for _, e := range noncritical {
|
|
|
|
|
if e.ID == NoteID && e.Version == 1 {
|
|
|
|
|
if e.Data == nil || CheckNote(string(e.Data)) != nil {
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
return string(e.Data), true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard is the Registry of the extensions that spec v0.11 defines: the
|
|
|
|
|
// public note in PUBLIC_HEADER and datekeys.capsule in a .dkk, both
|
|
|
|
|
// noncritical. It validates their data, as spec §54 asks of a reader that
|
|
|
|
|
// knows an extension, and registers each only where §72 does.
|
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
type Standard struct {
|
|
|
|
|
// ValidateCapsule checks the data of datekeys.capsule, which package
|
|
|
|
|
// locator decodes: it cannot be imported here. locator.Standard sets it.
|
|
|
|
|
// When nil, only the presence of the data is checked.
|
|
|
|
|
ValidateCapsule func(Extension) error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Known reports whether (id, version) is one of the two.
|
|
|
|
|
func (Standard) Known(id string, version uint64) bool {
|
|
|
|
|
return version == 1 && (id == NoteID || id == CapsuleID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegisteredIn reports where the extensions are registered (spec §72).
|
|
|
|
|
func (Standard) RegisteredIn(id string, version uint64, obj Object, arr Array) bool {
|
|
|
|
|
if arr != Noncritical || version != 1 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return id == NoteID && obj == PublicHeader || id == CapsuleID && obj == AccessKey
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// ValidateData checks the data of a note, and the data of datekeys.capsule
|
|
|
|
|
// with ValidateCapsule, when it is set.
|
|
|
|
|
func (s Standard) ValidateData(e Extension) error {
|
|
|
|
|
switch e.ID {
|
|
|
|
|
case NoteID:
|
|
|
|
|
if e.Data == nil {
|
|
|
|
|
return fmt.Errorf("a public note without data: %w", datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
|
|
|
|
return CheckNote(string(e.Data))
|
|
|
|
|
case CapsuleID:
|
|
|
|
|
if e.Data == nil {
|
|
|
|
|
return fmt.Errorf("datekeys.capsule without data: %w", datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if s.ValidateCapsule != nil {
|
|
|
|
|
return s.ValidateCapsule(e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|