|
|
|
|
// Package locator implements the extension datekeys.capsule of a .dkk and
|
|
|
|
|
// what it points to (spec v0.11, §44.1): the data of the extension, which
|
|
|
|
|
// says what a key's capsule is and when it opens; the locator, an age file
|
|
|
|
|
// sealed with tlock for that date, which says where the capsule is; and the
|
|
|
|
|
// envelope, the .dkc encrypted with age and split into a header, which the
|
|
|
|
|
// locator carries, and a rest, the only thing that is kept outside, alone or
|
|
|
|
|
// inside another file.
|
|
|
|
|
//
|
|
|
|
|
// It does not download anything: a reader fetches the rest only when the
|
|
|
|
|
// person asks, after showing her the host or the CID (§44.1), and gives it
|
|
|
|
|
// to OpenEnvelope.
|
|
|
|
|
package locator
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
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
|
|
|
"net/netip"
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"filippo.io/age"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/agewrap"
|
|
|
|
|
"g.activething.com/go/DateKeys/codec"
|
|
|
|
|
"g.activething.com/go/DateKeys/datekey"
|
|
|
|
|
"g.activething.com/go/DateKeys/extension"
|
|
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
|
|
|
"g.activething.com/go/DateKeys/provider"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Limits of §44.1.
|
|
|
|
|
const (
|
|
|
|
|
// MaxAddresses is the most addresses of a locator.
|
|
|
|
|
MaxAddresses = 8
|
|
|
|
|
// MaxURILen is the longest address, in bytes.
|
|
|
|
|
MaxURILen = 1024
|
|
|
|
|
// MaxHeaderLen is the longest header of an envelope.
|
|
|
|
|
MaxHeaderLen = 1024
|
|
|
|
|
// Block is the unit of the plaintext of a locator: it measures exactly
|
|
|
|
|
// 4096 bytes, or the least multiple of 4096 that holds it.
|
|
|
|
|
Block = 4096
|
|
|
|
|
// maxSealed bounds a sealed locator that a reader decrypts.
|
|
|
|
|
maxSealed = 1 << 20
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Info is the data of the extension datekeys.capsule (spec §44.1).
|
|
|
|
|
type Info struct {
|
|
|
|
|
// Note is the copy of the public note of the capsule, "" for none.
|
|
|
|
|
Note string
|
|
|
|
|
// DateKey is the DateKey of the capsule: it says when it opens.
|
|
|
|
|
DateKey datekey.DateKey
|
|
|
|
|
// Sealed is the age file of the locator, sealed with tlock for the round
|
|
|
|
|
// of DateKey, nil for none.
|
|
|
|
|
Sealed []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extension returns the extension for the noncritical array of a .dkk.
|
|
|
|
|
func (i *Info) Extension() (extension.Extension, error) {
|
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 d, err := datekey.Parse(i.DateKey.Compact()); err != nil || d != i.DateKey {
|
|
|
|
|
return extension.Extension{}, errors.New("locator: Info.DateKey is not a canonical DateKey")
|
|
|
|
|
}
|
|
|
|
|
// A writer writes the DateKey of a capsule it wrote: of a profile that
|
|
|
|
|
// this module pins, whose chain the check of the locator then compares.
|
|
|
|
|
if reg, err := profile.Default(); err != nil {
|
|
|
|
|
return extension.Extension{}, err
|
|
|
|
|
} else if _, ok := reg.Lookup(i.DateKey.ProfileID); !ok {
|
|
|
|
|
return extension.Extension{}, fmt.Errorf("locator: Info.DateKey is of the profile %q, which this module does not pin", i.DateKey.ProfileID)
|
|
|
|
|
}
|
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 i.Sealed != nil && (len(i.Sealed) < 1 || len(i.Sealed) > maxSealed) {
|
|
|
|
|
return extension.Extension{}, fmt.Errorf("locator: a sealed locator of %d bytes, not 1 to %d", len(i.Sealed), maxSealed)
|
|
|
|
|
}
|
|
|
|
|
if i.Note != "" {
|
|
|
|
|
if err := extension.CheckNote(i.Note); err != nil {
|
|
|
|
|
return extension.Extension{}, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var e codec.Encoder
|
|
|
|
|
pairs := 1
|
|
|
|
|
if i.Note != "" {
|
|
|
|
|
pairs++
|
|
|
|
|
}
|
|
|
|
|
if i.Sealed != nil {
|
|
|
|
|
pairs++
|
|
|
|
|
}
|
|
|
|
|
e.Map(pairs)
|
|
|
|
|
if i.Note != "" {
|
|
|
|
|
e.Uint(0)
|
|
|
|
|
e.Text(i.Note)
|
|
|
|
|
}
|
|
|
|
|
e.Uint(1)
|
|
|
|
|
e.Text(i.DateKey.Compact())
|
|
|
|
|
if i.Sealed != nil {
|
|
|
|
|
e.Uint(2)
|
|
|
|
|
e.Bstr(i.Sealed)
|
|
|
|
|
}
|
|
|
|
|
data, err := e.Out()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return extension.Extension{}, err
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
x, err := extension.New(extension.CapsuleID, 1, data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return extension.Extension{}, err
|
|
|
|
|
}
|
|
|
|
|
// Spec §72: the encoder reads what it writes with the rules of a reader,
|
|
|
|
|
// which also ties the locator to the round of DateKey.
|
|
|
|
|
if _, err := ParseInfo(x); err != nil {
|
|
|
|
|
return extension.Extension{}, fmt.Errorf("locator: self-check: a reader rejects this extension: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return x, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParseInfo reads the data of a datekeys.capsule extension. A failure makes
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// the extension unusable, not the .dkk (spec §54): its only normative code is
|
|
|
|
|
// ErrExtensionDataInvalid. A locator must be an age file with one tlock
|
|
|
|
|
// stanza, for the round of the DateKey; its chain is checked when it opens.
|
|
|
|
|
func ParseInfo(x extension.Extension) (*Info, error) {
|
|
|
|
|
if x.ID != extension.CapsuleID || x.Version != 1 || x.Data == nil {
|
|
|
|
|
return nil, fmt.Errorf("locator: not datekeys.capsule version 1 with data: %w", datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
|
|
|
|
var i Info
|
|
|
|
|
var dk string
|
|
|
|
|
decode := func(d *codec.Decoder) error {
|
|
|
|
|
pairs, err := d.Map(3)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var seen uint
|
|
|
|
|
for range pairs {
|
|
|
|
|
k, err := d.Key()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
switch k {
|
|
|
|
|
case 0:
|
|
|
|
|
i.Note, err = d.Text(extension.MaxNoteLen)
|
|
|
|
|
if err == nil {
|
|
|
|
|
err = extension.CheckNote(i.Note)
|
|
|
|
|
}
|
|
|
|
|
case 1:
|
|
|
|
|
dk, err = d.Text(1024)
|
|
|
|
|
case 2:
|
|
|
|
|
i.Sealed, err = d.Bstr(1, maxSealed)
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("key %d: %w", k, err)
|
|
|
|
|
}
|
|
|
|
|
seen |= 1 << k
|
|
|
|
|
}
|
|
|
|
|
if seen&2 == 0 {
|
|
|
|
|
return fmt.Errorf("key 1 is missing: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
return d.EndMap()
|
|
|
|
|
}
|
|
|
|
|
encode := func(e *codec.Encoder) {
|
|
|
|
|
pairs := 1
|
|
|
|
|
if i.Note != "" {
|
|
|
|
|
pairs++
|
|
|
|
|
}
|
|
|
|
|
if i.Sealed != nil {
|
|
|
|
|
pairs++
|
|
|
|
|
}
|
|
|
|
|
e.Map(pairs)
|
|
|
|
|
if i.Note != "" {
|
|
|
|
|
e.Uint(0)
|
|
|
|
|
e.Text(i.Note)
|
|
|
|
|
}
|
|
|
|
|
e.Uint(1)
|
|
|
|
|
e.Text(dk)
|
|
|
|
|
if i.Sealed != nil {
|
|
|
|
|
e.Uint(2)
|
|
|
|
|
e.Bstr(i.Sealed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := codec.Unmarshal(x.Data, decode, encode); err != nil {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil, fmt.Errorf("locator: datekeys.capsule: %v: %w", err, datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
|
|
|
|
d, err := datekey.Parse(dk)
|
|
|
|
|
if err != nil || d.Compact() != dk {
|
|
|
|
|
return nil, fmt.Errorf("locator: compact_datekey is not a canonical DateKey: %w", datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
|
|
|
|
i.DateKey = d
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if i.Sealed != nil {
|
|
|
|
|
if err := checkSealed(i.Sealed, d); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("locator: %v: %w", err, datekeys.ErrExtensionDataInvalid)
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &i, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// checkSealed checks the form of a sealed locator against the DateKey d of
|
|
|
|
|
// its extension, as far as it can be checked before the date (spec §44.1):
|
|
|
|
|
// an age file with one tlock stanza, whose arguments are the round of d in
|
|
|
|
|
// decimal and a chain hash in lower-case hexadecimal (§28.1), the chain of
|
|
|
|
|
// the profile of d when this module pins it; and a body that holds a
|
|
|
|
|
// plaintext of 4096 bytes or a multiple, as every locator has. A locator of
|
|
|
|
|
// another round or chain never opens, and one of another length is not a
|
|
|
|
|
// locator.
|
|
|
|
|
func checkSealed(sealed []byte, d datekey.DateKey) error {
|
|
|
|
|
st, err := agewrap.Stanzas(bytes.NewReader(sealed))
|
|
|
|
|
if err != nil || len(st) != 1 || st[0].Type != agewrap.StanzaTLock || len(st[0].Args) != 2 || st[0].Args[0] != strconv.FormatUint(d.Round, 10) {
|
|
|
|
|
return fmt.Errorf("the locator is not an age file with one tlock stanza for round %d, the one of its DateKey", d.Round)
|
|
|
|
|
}
|
|
|
|
|
chain := st[0].Args[1]
|
|
|
|
|
if len(chain) != 64 || strings.Trim(chain, "0123456789abcdef") != "" {
|
|
|
|
|
return errors.New("the tlock stanza of the locator has no chain hash in lower-case hexadecimal")
|
|
|
|
|
}
|
|
|
|
|
if reg, err := profile.Default(); err == nil {
|
|
|
|
|
if p, ok := reg.Lookup(d.ProfileID); ok && chain != hex.EncodeToString(p.ChainHash[:]) {
|
|
|
|
|
return fmt.Errorf("the locator is sealed for the chain %s, not for the one of the profile %s of its DateKey", chain, d.ProfileID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// agewrap.Stanzas read the header up to its MAC line, so it ends.
|
|
|
|
|
end, err := headerEnd(sealed)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if end == len(sealed) {
|
|
|
|
|
return errors.New("the locator is an age header without a body")
|
|
|
|
|
}
|
|
|
|
|
if !sealedBodyLength(uint64(len(sealed) - end)) {
|
|
|
|
|
return fmt.Errorf("the body of the locator is %d bytes, which no plaintext of 4096 bytes or a multiple gives", len(sealed)-end)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sealedBodyLength reports whether n bytes after the age header are the
|
|
|
|
|
// body of a plaintext of 4096·k bytes, k from 1: the nonce of 16 bytes and
|
|
|
|
|
// the plaintext in chunks of 64 KiB, each with its tag of 16 bytes.
|
|
|
|
|
func sealedBodyLength(n uint64) bool {
|
|
|
|
|
for p := uint64(Block); p <= maxSealed; p += Block {
|
|
|
|
|
if n == 16+p+16*((p+64<<10-1)/(64<<10)) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Address says where the rest of the envelope is.
|
|
|
|
|
type Address struct {
|
|
|
|
|
// URI is ASCII, RFC 3986, with the scheme https or ipfs (a CID v1), and
|
|
|
|
|
// no userinfo.
|
|
|
|
|
URI string
|
|
|
|
|
// Offset is the byte of the resource where the rest starts, 0 when the
|
|
|
|
|
// rest is the whole resource.
|
|
|
|
|
Offset uint64
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// CheckURI checks an address with the rules of spec §44.1: ASCII of RFC 3986,
|
|
|
|
|
// with its percent signs followed by two hexadecimal digits, the scheme
|
Spec v0.12 draft: the rules of an address that the reference applied
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// https or ipfs in lower case, no "." or ".." segment in its path, and the
|
|
|
|
|
// host or the CID that checkHost and isCIDv1 accept.
|
|
|
|
|
func CheckURI(uri string) error {
|
|
|
|
|
if uri == "" || len(uri) > MaxURILen {
|
|
|
|
|
return fmt.Errorf("locator: an address of %d bytes, not 1 to %d", len(uri), MaxURILen)
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < len(uri); i++ {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
c := uri[i]
|
|
|
|
|
switch {
|
|
|
|
|
case c == '%':
|
|
|
|
|
if i+2 >= len(uri) || !isHex(uri[i+1]) || !isHex(uri[i+2]) {
|
|
|
|
|
return errors.New("locator: an address with a percent sign not followed by two hexadecimal digits")
|
|
|
|
|
}
|
|
|
|
|
case !uriChar(c):
|
|
|
|
|
return fmt.Errorf("locator: an address with the character %q, which RFC 3986 does not allow", c)
|
|
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
scheme, host, err := splitAuthority(uri)
|
|
|
|
|
if err != nil {
|
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
|
|
|
return err
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if dotSegment(uri) {
|
|
|
|
|
return errors.New("locator: an address with a \".\" or \"..\" segment in its path")
|
|
|
|
|
}
|
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
|
|
|
switch scheme {
|
|
|
|
|
case "https":
|
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
|
|
|
return checkHost(host)
|
|
|
|
|
case "ipfs":
|
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 !isCIDv1(host) {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return errors.New("locator: an ipfs address without a CID v1 in base32")
|
|
|
|
|
}
|
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
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("locator: the scheme %q: only https and ipfs", scheme)
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// uriChar reports whether c may appear in a URI of RFC 3986, a percent sign
|
|
|
|
|
// apart: the unreserved characters, gen-delims and sub-delims.
|
|
|
|
|
func uriChar(c byte) bool {
|
|
|
|
|
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || strings.IndexByte("-._~:/?#[]@!$&'()*+,;=", c) >= 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isHex(c byte) bool {
|
|
|
|
|
return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dotSegment reports whether the path of uri has a segment "." or "..",
|
|
|
|
|
// written or percent-encoded: a client or a gateway that resolves it would
|
|
|
|
|
// ask for something other than what the address shows, as another CID behind
|
|
|
|
|
// an ipfs address.
|
|
|
|
|
func dotSegment(uri string) bool {
|
|
|
|
|
_, rest, _ := strings.Cut(uri, "://")
|
|
|
|
|
i := strings.IndexByte(rest, '/')
|
|
|
|
|
if i < 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
path := rest[i:]
|
|
|
|
|
if j := strings.IndexAny(path, "?#"); j >= 0 {
|
|
|
|
|
path = path[:j]
|
|
|
|
|
}
|
|
|
|
|
for _, s := range strings.Split(path, "/") {
|
|
|
|
|
s = strings.ReplaceAll(strings.ReplaceAll(s, "%2e", "."), "%2E", ".")
|
|
|
|
|
if s == "." || s == ".." {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// splitAuthority returns the scheme and the raw host of an address, without
|
|
|
|
|
// decoding anything: a percent sign in the authority, userinfo and a
|
|
|
|
|
// malformed port are refused, so that the host a reader shows is the host an
|
|
|
|
|
// HTTP client would use (spec v0.11, §44.1).
|
|
|
|
|
func splitAuthority(uri string) (scheme, host string, err error) {
|
|
|
|
|
scheme, rest, ok := strings.Cut(uri, "://")
|
|
|
|
|
if !ok || scheme == "" {
|
|
|
|
|
return "", "", errors.New("locator: an address without a scheme and ://")
|
|
|
|
|
}
|
|
|
|
|
authority := rest
|
|
|
|
|
if i := strings.IndexAny(rest, "/?#"); i >= 0 {
|
|
|
|
|
authority = rest[:i]
|
|
|
|
|
}
|
|
|
|
|
if strings.ContainsAny(authority, "%@\\") {
|
|
|
|
|
return "", "", errors.New("locator: an address with a percent sign, userinfo or a backslash in its authority")
|
|
|
|
|
}
|
|
|
|
|
host = authority
|
|
|
|
|
if scheme == "https" {
|
|
|
|
|
if strings.HasPrefix(authority, "[") {
|
|
|
|
|
end := strings.Index(authority, "]")
|
|
|
|
|
if end < 0 {
|
|
|
|
|
return "", "", errors.New("locator: an address with an unclosed IPv6 literal")
|
|
|
|
|
}
|
|
|
|
|
host = authority[:end+1]
|
|
|
|
|
if tail := authority[end+1:]; tail != "" {
|
|
|
|
|
if err := checkPort(tail); err != nil {
|
|
|
|
|
return "", "", err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if h, port, found := strings.Cut(authority, ":"); found {
|
|
|
|
|
host = h
|
|
|
|
|
if err := checkPort(":" + port); err != nil {
|
|
|
|
|
return "", "", err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return scheme, host, nil
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.12 draft: the rules of an address that the reference applied
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// checkPort checks the port of an authority, its ':' included: a number from 1
|
|
|
|
|
// to 65535 without leading zeros (spec v0.12, §44.1), so that a port is
|
|
|
|
|
// written in one way only.
|
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
|
|
|
func checkPort(s string) error {
|
|
|
|
|
if len(s) < 2 || s[0] != ':' || len(s) > 6 {
|
|
|
|
|
return errors.New("locator: an address with a malformed port")
|
|
|
|
|
}
|
|
|
|
|
n := 0
|
|
|
|
|
for _, c := range s[1:] {
|
|
|
|
|
if c < '0' || c > '9' {
|
|
|
|
|
return errors.New("locator: an address with a malformed port")
|
|
|
|
|
}
|
|
|
|
|
n = n*10 + int(c-'0')
|
|
|
|
|
}
|
|
|
|
|
if n < 1 || n > 65535 {
|
|
|
|
|
return errors.New("locator: an address with a port outside 1 to 65535")
|
|
|
|
|
}
|
Spec v0.12 draft: the rules of an address that the reference applied
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
if s[1] == '0' {
|
|
|
|
|
return errors.New("locator: an address with a port written with a leading zero")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.12 draft: the rules of an address that the reference applied
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// checkHost accepts a name of labels of 1 to 63 letters, digits and hyphens,
|
|
|
|
|
// separated by dots, none of which starts or ends with a hyphen, or an IP
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// literal that is public: the spec forbids following a redirect to the
|
|
|
|
|
// others, and an address that starts there would defeat the same rule
|
Spec v0.12 draft: the rules of an address that the reference applied
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// (§44.1). A name whose last label is numeric, or starts with 0x in either
|
|
|
|
|
// case, is refused unless it is a public IPv4 address in dotted decimal
|
|
|
|
|
// without leading zeros, the only form netip reads: some clients read the
|
|
|
|
|
// others, 0x7f000001 or 0177.0.0.1, as an IPv4 address too. So are the names
|
|
|
|
|
// that only resolve inside a machine or a local network, in either case:
|
|
|
|
|
// localhost, a name of one label, and the special-use names of localName.
|
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
|
|
|
func checkHost(host string) error {
|
|
|
|
|
if host == "" {
|
|
|
|
|
return errors.New("locator: an https address without a host")
|
|
|
|
|
}
|
|
|
|
|
if strings.HasPrefix(host, "[") {
|
|
|
|
|
// One pair of brackets: splitAuthority ends the literal at the
|
|
|
|
|
// first ']', and "[[2000::]" is not an IPv6 literal.
|
|
|
|
|
a, err := netip.ParseAddr(strings.TrimSuffix(strings.TrimPrefix(host, "["), "]"))
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if err != nil || !a.Is6() || a.Zone() != "" || !publicIP(a) {
|
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
|
|
|
return errors.New("locator: an https address with an IPv6 literal that is not public")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
labels := strings.Split(host, ".")
|
|
|
|
|
for _, l := range labels {
|
|
|
|
|
if l == "" || len(l) > 63 || l[0] == '-' || l[len(l)-1] == '-' {
|
|
|
|
|
return errors.New("locator: an https address with a malformed host")
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < len(l); i++ {
|
|
|
|
|
c := l[i]
|
|
|
|
|
if !(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-') {
|
|
|
|
|
return errors.New("locator: an https address whose host is not letters, digits and hyphens: write its punycode form")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
last := labels[len(labels)-1]
|
|
|
|
|
if allDigits(last) || strings.HasPrefix(strings.ToLower(last), "0x") {
|
|
|
|
|
a, err := netip.ParseAddr(host)
|
|
|
|
|
if err != nil || !a.Is4() || !publicIP(a) {
|
|
|
|
|
return errors.New("locator: an https address with a numeric host that is not a public IPv4 address")
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if len(labels) == 1 || localName(strings.ToLower(host)) {
|
|
|
|
|
return errors.New("locator: an https address with a name that only a machine or a local network resolves")
|
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
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// localName reports whether the name, in lower case, is one of the
|
|
|
|
|
// special-use names that never resolve on the public Internet: localhost
|
|
|
|
|
// (RFC 6761), .local (RFC 6762), .home.arpa (RFC 8375), .internal, .invalid,
|
|
|
|
|
// .test, .example and .onion (RFC 7686), or below one of them.
|
|
|
|
|
func localName(name string) bool {
|
|
|
|
|
for _, s := range []string{"localhost", "local", "home.arpa", "internal", "invalid", "test", "example", "onion"} {
|
|
|
|
|
if name == s || strings.HasSuffix(name, "."+s) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
func allDigits(s string) bool {
|
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
|
if s[i] < '0' || s[i] > '9' {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return s != ""
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// The blocks of the IANA registries of special-purpose addresses that an
|
|
|
|
|
// address of a locator may not use (spec §44.1). An IPv6 address must also
|
|
|
|
|
// be a global unicast one, of 2000::/3.
|
|
|
|
|
var (
|
|
|
|
|
notPublic4 = prefixes("0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
|
|
|
|
|
"192.0.0.0/24", "192.0.2.0/24", "192.88.99.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24",
|
|
|
|
|
"203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4")
|
|
|
|
|
global6 = prefixes("2000::/3")
|
|
|
|
|
notPublic6 = prefixes("2001::/23", "2001:db8::/32", "2002::/16", "3fff::/20")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func prefixes(s ...string) []netip.Prefix {
|
|
|
|
|
out := make([]netip.Prefix, len(s))
|
|
|
|
|
for i, p := range s {
|
|
|
|
|
out[i] = netip.MustParsePrefix(p)
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func inAny(a netip.Addr, ps []netip.Prefix) bool {
|
|
|
|
|
for _, p := range ps {
|
|
|
|
|
if p.Contains(a) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// publicIP reports whether a is an address of the public Internet: an IPv4
|
|
|
|
|
// address outside the blocks of notPublic4, or an IPv6 address of 2000::/3
|
|
|
|
|
// outside those of notPublic6. An IPv6 address that holds an IPv4 one, mapped,
|
|
|
|
|
// compatible, of NAT64, 6to4 or Teredo, is not: it would reach the IPv4
|
|
|
|
|
// address without the check of an IPv4 address.
|
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
|
|
|
func publicIP(a netip.Addr) bool {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if a.Is4() {
|
|
|
|
|
return !inAny(a, notPublic4)
|
|
|
|
|
}
|
|
|
|
|
return inAny(a, global6) && !inAny(a, notPublic6)
|
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
|
|
|
}
|
|
|
|
|
|
Spec v0.12 draft: the rules of an address that the reference applied
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// isCIDv1 reports whether s is a CID v1 of at most 128 characters in
|
|
|
|
|
// canonical base32, which starts with 'b' (spec v0.12, §44.1): the alphabet
|
|
|
|
|
// in lower case, without padding, the bits left over set to zero, and
|
|
|
|
|
// decoded, minimal varints of at most 9 bytes, the version 1, a content codec
|
|
|
|
|
// and a multihash with a digest of at least one byte and of the length it
|
|
|
|
|
// gives, with nothing after it.
|
|
|
|
|
func isCIDv1(s string) bool {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if len(s) < 2 || len(s) > 128 || s[0] != 'b' {
|
|
|
|
|
return false
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
var out []byte
|
|
|
|
|
var acc, bits uint
|
|
|
|
|
for i := 1; i < len(s); i++ {
|
|
|
|
|
c := s[i]
|
|
|
|
|
var v byte
|
|
|
|
|
switch {
|
|
|
|
|
case c >= 'a' && c <= 'z':
|
|
|
|
|
v = c - 'a'
|
|
|
|
|
case c >= '2' && c <= '7':
|
|
|
|
|
v = c - '2' + 26
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
acc, bits = acc<<5|uint(v), bits+5
|
|
|
|
|
if bits >= 8 {
|
|
|
|
|
bits -= 8
|
|
|
|
|
out = append(out, byte(acc>>bits))
|
|
|
|
|
acc &= 1<<bits - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Canonical base32 without padding: the last character carries fewer
|
|
|
|
|
// than 5 bits beyond the last byte, all zero. A character more, even
|
|
|
|
|
// one of zero bits, is another encoding of the same bytes.
|
|
|
|
|
if bits >= 5 || acc != 0 {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
version, out, ok := uvarint(out)
|
|
|
|
|
if !ok || version != 1 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if _, out, ok = uvarint(out); !ok { // the content codec
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if _, out, ok = uvarint(out); !ok { // the hash function
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
n, out, ok := uvarint(out)
|
|
|
|
|
return ok && n > 0 && uint64(len(out)) == n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// uvarint reads an unsigned varint of multiformats, minimal and of at most 9
|
|
|
|
|
// bytes, from the start of b.
|
|
|
|
|
func uvarint(b []byte) (uint64, []byte, bool) {
|
|
|
|
|
var v uint64
|
|
|
|
|
for i := 0; i < len(b) && i < 9; i++ {
|
|
|
|
|
v |= uint64(b[i]&0x7f) << (7 * i)
|
|
|
|
|
if b[i]&0x80 == 0 {
|
|
|
|
|
if i > 0 && b[i] == 0 {
|
|
|
|
|
return 0, nil, false
|
|
|
|
|
}
|
|
|
|
|
return v, b[i+1:], true
|
|
|
|
|
}
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return 0, nil, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Host returns what a reader shows before it downloads: the host of an https
|
|
|
|
|
// address, or the CID of an ipfs one (spec §44.1).
|
|
|
|
|
func (a Address) Host() string {
|
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 CheckURI(a.URI) != nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
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
|
|
|
_, host, _ := splitAuthority(a.URI)
|
|
|
|
|
return strings.Trim(host, "[]")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Locator is the plaintext of the sealed locator (spec §44.1).
|
|
|
|
|
type Locator struct {
|
|
|
|
|
Addresses []Address
|
|
|
|
|
// EnvelopeKey is I_SOBRE, the raw X25519 identity of the envelope. SECRET.
|
|
|
|
|
EnvelopeKey [32]byte
|
|
|
|
|
// EnvelopeHeader is the age header of the envelope, MAC line included.
|
|
|
|
|
EnvelopeHeader []byte
|
|
|
|
|
// RestDigest is the SHA-256 of the rest, and RestSize its length.
|
|
|
|
|
RestDigest [32]byte
|
|
|
|
|
RestSize uint64
|
|
|
|
|
// CapsuleDigest is the SHA-256 of the .dkc (spec §43).
|
|
|
|
|
CapsuleDigest [32]byte
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// Usable returns the addresses that meet the rules of spec §44.1, in their
|
|
|
|
|
// order. A reader rejects each address that breaks them, and uses the others:
|
|
|
|
|
// a locator whose addresses are all rejected has nothing to download.
|
|
|
|
|
func (l *Locator) Usable() []Address {
|
|
|
|
|
var out []Address
|
|
|
|
|
for _, a := range l.Addresses {
|
|
|
|
|
if CheckURI(a.URI) == nil {
|
|
|
|
|
out = append(out, a)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validate checks what Marshal writes: the form, and each address, since a
|
|
|
|
|
// writer never writes one that a reader would reject.
|
|
|
|
|
func (l *Locator) validate() error {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if err := l.validateForm(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, a := range l.Addresses {
|
|
|
|
|
if err := CheckURI(a.URI); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validateForm checks the form that a reader requires of the whole locator:
|
|
|
|
|
// a broken address makes only that address unusable (Usable).
|
|
|
|
|
func (l *Locator) validateForm() error {
|
|
|
|
|
if len(l.Addresses) < 1 || len(l.Addresses) > MaxAddresses {
|
|
|
|
|
return fmt.Errorf("locator: %d addresses, not 1 to %d", len(l.Addresses), MaxAddresses)
|
|
|
|
|
}
|
|
|
|
|
for _, a := range l.Addresses {
|
|
|
|
|
if a.URI == "" || len(a.URI) > MaxURILen {
|
|
|
|
|
return fmt.Errorf("locator: an address of %d bytes, not 1 to %d", len(a.URI), MaxURILen)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if n := len(l.EnvelopeHeader); n < 1 || n > MaxHeaderLen {
|
|
|
|
|
return fmt.Errorf("locator: an envelope header of %d bytes, not 1 to %d", n, MaxHeaderLen)
|
|
|
|
|
}
|
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 l.RestSize > codec.MaxSafeUint {
|
|
|
|
|
return errors.New("locator: a rest larger than 2^53 - 1 bytes")
|
|
|
|
|
}
|
|
|
|
|
for _, a := range l.Addresses {
|
|
|
|
|
if a.Offset > codec.MaxSafeUint {
|
|
|
|
|
return errors.New("locator: an offset larger than 2^53 - 1")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// encode writes the map; pad < 0 leaves key 6 out.
|
|
|
|
|
func (l *Locator) encode(e *codec.Encoder, pad int) {
|
|
|
|
|
n := 6
|
|
|
|
|
if pad >= 0 {
|
|
|
|
|
n = 7
|
|
|
|
|
}
|
|
|
|
|
e.Map(n)
|
|
|
|
|
e.Uint(0)
|
|
|
|
|
e.Array(len(l.Addresses))
|
|
|
|
|
for _, a := range l.Addresses {
|
|
|
|
|
if a.Offset == 0 {
|
|
|
|
|
e.Map(1)
|
|
|
|
|
} else {
|
|
|
|
|
e.Map(2)
|
|
|
|
|
}
|
|
|
|
|
e.Uint(0)
|
|
|
|
|
e.Text(a.URI)
|
|
|
|
|
if a.Offset != 0 {
|
|
|
|
|
e.Uint(1)
|
|
|
|
|
e.Uint(a.Offset)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
e.Uint(1)
|
|
|
|
|
e.Bstr(l.EnvelopeKey[:])
|
|
|
|
|
e.Uint(2)
|
|
|
|
|
e.Bstr(l.EnvelopeHeader)
|
|
|
|
|
e.Uint(3)
|
|
|
|
|
e.Bstr(l.RestDigest[:])
|
|
|
|
|
e.Uint(4)
|
|
|
|
|
e.Uint(l.RestSize)
|
|
|
|
|
e.Uint(5)
|
|
|
|
|
e.Bstr(l.CapsuleDigest[:])
|
|
|
|
|
if pad >= 0 {
|
|
|
|
|
e.Uint(6)
|
|
|
|
|
e.Bstr(make([]byte, pad))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func bstrHeadLen(n int) int {
|
|
|
|
|
switch {
|
|
|
|
|
case n < 24:
|
|
|
|
|
return 1
|
|
|
|
|
case n < 256:
|
|
|
|
|
return 2
|
|
|
|
|
case n < 65536:
|
|
|
|
|
return 3
|
|
|
|
|
}
|
|
|
|
|
return 5
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// padFor returns the length of key 6 that makes the plaintext measure the
|
|
|
|
|
// least multiple of Block that holds it, or -1 when n0, the length without
|
|
|
|
|
// key 6, already is one. When no length of key 6 gives a given multiple, as
|
|
|
|
|
// happens at the boundaries of the CBOR length, it takes the next one.
|
|
|
|
|
func padFor(n0 int) int {
|
|
|
|
|
if n0%Block == 0 {
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
for total := (n0/Block + 1) * Block; ; total += Block {
|
|
|
|
|
for pad := 1; pad <= total-n0; pad++ {
|
|
|
|
|
if n0+1+bstrHeadLen(pad)+pad == total {
|
|
|
|
|
return pad
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// PlaintextLength returns the length of the plaintext of a locator whose CBOR
|
|
|
|
|
// without key 6 measures base bytes: base when it already is a multiple of
|
|
|
|
|
// Block, and otherwise the least multiple that key 6 can fill exactly.
|
|
|
|
|
func PlaintextLength(base int) int {
|
|
|
|
|
pad := padFor(base)
|
|
|
|
|
if pad < 0 {
|
|
|
|
|
return base
|
|
|
|
|
}
|
|
|
|
|
return base + 1 + bstrHeadLen(pad) + pad
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Marshal returns the plaintext of the locator: CBOR with the profile of
|
|
|
|
|
// spec §58, completed with zeros in key 6 up to the least multiple of 4096
|
|
|
|
|
// bytes that holds it, so that its length does not tell how many addresses
|
|
|
|
|
// there are.
|
|
|
|
|
func (l *Locator) Marshal() ([]byte, error) {
|
|
|
|
|
if err := l.validate(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return l.marshal()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// marshal is Marshal once the locator is checked.
|
|
|
|
|
func (l *Locator) marshal() ([]byte, error) {
|
|
|
|
|
var e codec.Encoder
|
|
|
|
|
l.encode(&e, -1)
|
|
|
|
|
base, err := e.Out()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
pad := padFor(len(base))
|
|
|
|
|
if pad < 0 {
|
|
|
|
|
return base, nil
|
|
|
|
|
}
|
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
|
|
|
defer clear(base) // it holds I_SOBRE
|
|
|
|
|
var p codec.Encoder
|
|
|
|
|
l.encode(&p, pad)
|
|
|
|
|
out, err := p.Out()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if len(out)%Block != 0 {
|
|
|
|
|
return nil, fmt.Errorf("locator: internal error: %d bytes of plaintext", len(out))
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// Unmarshal reads the plaintext of a locator, checking its profile and the
|
|
|
|
|
// length that Marshal gives. Its errors carry no normative code: a locator
|
|
|
|
|
// that does not read is unusable (spec §44.1, §57). An address that breaks
|
|
|
|
|
// the rules of §44.1 is kept, and Usable leaves it out.
|
|
|
|
|
func Unmarshal(b []byte) (*Locator, error) {
|
|
|
|
|
var l Locator
|
|
|
|
|
pad := -1
|
|
|
|
|
decode := func(d *codec.Decoder) error {
|
|
|
|
|
pairs, err := d.Map(7)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var seen uint
|
|
|
|
|
for range pairs {
|
|
|
|
|
k, err := d.Key()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
switch k {
|
|
|
|
|
case 0:
|
|
|
|
|
err = decodeAddresses(d, &l)
|
|
|
|
|
case 1:
|
|
|
|
|
err = copyBstr(d, l.EnvelopeKey[:])
|
|
|
|
|
case 2:
|
|
|
|
|
l.EnvelopeHeader, err = d.Bstr(1, MaxHeaderLen)
|
|
|
|
|
case 3:
|
|
|
|
|
err = copyBstr(d, l.RestDigest[:])
|
|
|
|
|
case 4:
|
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
|
|
|
l.RestSize, err = d.Uint(codec.MaxSafeUint)
|
|
|
|
|
case 5:
|
|
|
|
|
err = copyBstr(d, l.CapsuleDigest[:])
|
|
|
|
|
case 6:
|
|
|
|
|
var z []byte
|
|
|
|
|
if z, err = d.Bstr(1, 1<<20); err == nil {
|
|
|
|
|
if len(bytes.Trim(z, "\x00")) != 0 {
|
|
|
|
|
return errors.New("the padding is not zeros")
|
|
|
|
|
}
|
|
|
|
|
pad = len(z)
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("key %d: %w", k, err)
|
|
|
|
|
}
|
|
|
|
|
seen |= 1 << k
|
|
|
|
|
}
|
|
|
|
|
if seen&0x3f != 0x3f {
|
|
|
|
|
return fmt.Errorf("a key from 0 to 5 is missing: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
return d.EndMap()
|
|
|
|
|
}
|
|
|
|
|
encode := func(e *codec.Encoder) { l.encode(e, pad) }
|
|
|
|
|
if err := codec.Unmarshal(b, decode, encode); err != nil {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil, fmt.Errorf("locator: %v", err)
|
|
|
|
|
}
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if err := l.validateForm(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
// The length is the one Marshal gives: nothing else is canonical.
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
want, err := l.marshal()
|
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
|
|
|
defer clear(want)
|
|
|
|
|
if err != nil || !bytes.Equal(want, b) {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil, fmt.Errorf("locator: the plaintext is not %d or the least multiple of %d that holds it", Block, Block)
|
|
|
|
|
}
|
|
|
|
|
return &l, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func copyBstr(d *codec.Decoder, dst []byte) error {
|
|
|
|
|
b, err := d.Bstr(len(dst), len(dst))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
copy(dst, b)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func decodeAddresses(d *codec.Decoder, l *Locator) error {
|
|
|
|
|
n, err := d.Array(MaxAddresses)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for range n {
|
|
|
|
|
pairs, err := d.Map(2)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var a Address
|
|
|
|
|
var seen uint
|
|
|
|
|
for range pairs {
|
|
|
|
|
k, err := d.Key()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
switch k {
|
|
|
|
|
case 0:
|
|
|
|
|
a.URI, err = d.Text(MaxURILen)
|
|
|
|
|
case 1:
|
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 a.Offset, err = d.Uint(codec.MaxSafeUint); err == nil && a.Offset == 0 {
|
|
|
|
|
err = fmt.Errorf("an offset of 0 is written by leaving it out: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("address key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
seen |= 1 << k
|
|
|
|
|
}
|
|
|
|
|
if seen&1 == 0 {
|
|
|
|
|
return fmt.Errorf("an address without URI: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err := d.EndMap(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
l.Addresses = append(l.Addresses, a)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Seal returns the locator as an age file with a single tlock stanza for the
|
|
|
|
|
// round of the DateKey (spec §44.1): nobody reads it before the date, the
|
|
|
|
|
// holder of the key included.
|
|
|
|
|
func Seal(p *profile.Profile, round uint64, l *Locator) ([]byte, error) {
|
|
|
|
|
plain, err := l.Marshal()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer clear(plain)
|
|
|
|
|
r, err := agewrap.NewTimeRecipient(p, round)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
w, err := age.Encrypt(&buf, r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if _, err := w.Write(plain); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := w.Close(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open opens a sealed locator with the release of its round, and reads its
|
|
|
|
|
// plaintext. A locator for another round or another chain does not open: it
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// is unusable (spec §44.1), and its errors carry no normative code.
|
|
|
|
|
func Open(p *profile.Profile, round uint64, release provider.Release, sealed []byte) (*Locator, error) {
|
|
|
|
|
id, err := agewrap.NewTimeIdentity(p, round, release)
|
|
|
|
|
if err != nil {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil, fmt.Errorf("locator: %v", err)
|
|
|
|
|
}
|
|
|
|
|
r, err := age.Decrypt(bytes.NewReader(sealed), id)
|
|
|
|
|
if err != nil {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil, fmt.Errorf("locator: %v", err)
|
|
|
|
|
}
|
|
|
|
|
plain, err := io.ReadAll(io.LimitReader(r, maxSealed))
|
|
|
|
|
if err != nil {
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return nil, fmt.Errorf("locator: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer clear(plain)
|
|
|
|
|
return Unmarshal(plain)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewEnvelope encrypts the .dkc dkc with age for a new identity, I_SOBRE, and
|
|
|
|
|
// splits the age file: the locator it returns has the key, the header, the
|
|
|
|
|
// digests and the size of the rest, and no address yet; rest, with no mark,
|
|
|
|
|
// is what the person keeps outside. A caller adds the addresses where it
|
|
|
|
|
// stored rest, alone or inside another file, and then seals the locator.
|
|
|
|
|
func NewEnvelope(dkc []byte) (loc *Locator, rest []byte, err error) {
|
|
|
|
|
id, err := age.GenerateX25519Identity()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
raw, err := agewrap.RawX25519Identity(id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
defer clear(raw)
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
w, err := age.Encrypt(&buf, id.Recipient())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
if _, err := w.Write(dkc); err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := w.Close(); err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
file := buf.Bytes()
|
|
|
|
|
end, err := headerEnd(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
loc = &Locator{EnvelopeHeader: bytes.Clone(file[:end]), RestDigest: sha256.Sum256(file[end:]), RestSize: uint64(len(file) - end), CapsuleDigest: sha256.Sum256(dkc)}
|
|
|
|
|
copy(loc.EnvelopeKey[:], raw)
|
|
|
|
|
return loc, bytes.Clone(file[end:]), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// headerEnd returns the length of the age header of file, up to and
|
|
|
|
|
// including the line feed after the MAC line: the line that starts with
|
|
|
|
|
// "--- ". No line of the header before it starts so, and the lines of the
|
|
|
|
|
// body of a stanza are base64, which has no '-'.
|
|
|
|
|
func headerEnd(file []byte) (int, error) {
|
|
|
|
|
i := bytes.Index(file, []byte("\n--- "))
|
|
|
|
|
if i < 0 {
|
|
|
|
|
return 0, errors.New("locator: the age file has no MAC line")
|
|
|
|
|
}
|
|
|
|
|
j := bytes.IndexByte(file[i+1:], '\n')
|
|
|
|
|
if j < 0 {
|
|
|
|
|
return 0, errors.New("locator: the MAC line of the age file does not end")
|
|
|
|
|
}
|
|
|
|
|
return i + 1 + j + 1, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OpenEnvelope joins the header of the locator and rest, which a reader got
|
|
|
|
|
// from an address, and decrypts the .dkc. It checks the size and the SHA-256
|
|
|
|
|
// of rest, and the SHA-256 of the .dkc, before the caller uses it (spec
|
|
|
|
|
// §44.1): they protect against whoever stores the rest, not against whoever
|
|
|
|
|
// wrote the .dkk.
|
|
|
|
|
func (l *Locator) OpenEnvelope(rest []byte) ([]byte, error) {
|
|
|
|
|
if uint64(len(rest)) != l.RestSize {
|
|
|
|
|
return nil, fmt.Errorf("locator: the rest is %d bytes, not %d", len(rest), l.RestSize)
|
|
|
|
|
}
|
|
|
|
|
if sha256.Sum256(rest) != l.RestDigest {
|
|
|
|
|
return nil, errors.New("locator: the SHA-256 of the rest is not the one of the locator")
|
|
|
|
|
}
|
|
|
|
|
id, err := agewrap.X25519IdentityFromRaw(l.EnvelopeKey[:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
r, err := age.Decrypt(io.MultiReader(bytes.NewReader(l.EnvelopeHeader), bytes.NewReader(rest)), id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("locator: the envelope: %w", err)
|
|
|
|
|
}
|
|
|
|
|
dkc, err := io.ReadAll(r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("locator: the envelope: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if sha256.Sum256(dkc) != l.CapsuleDigest {
|
|
|
|
|
return nil, errors.New("locator: the SHA-256 of the .dkc is not the capsule_digest of the locator")
|
|
|
|
|
}
|
|
|
|
|
return dkc, nil
|
|
|
|
|
}
|