Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
package capsule
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"encoding/hex"
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
"errors"
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"g.activething.com/go/DateKeys/codec/bech32"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/ed25519strict"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The domain prefixes of what an author signs and a seal seals (spec v0.11,
|
|
|
|
|
// §29.8, §29.11). Each is followed by a byte 0x00, except in AUTHOR_MESSAGE,
|
|
|
|
|
// where a line feed follows it.
|
|
|
|
|
const (
|
|
|
|
|
payloadCommitPrefix = "datekeys:dkc3:payload:v1"
|
|
|
|
|
controlCommitPrefix = "datekeys:dkc3:control:v1"
|
|
|
|
|
headDigestPrefix = "datekeys:dkc3:head:v1"
|
|
|
|
|
signersDigestPrefix = "datekeys:dkc3:signers:v1"
|
|
|
|
|
sigPartPrefix = "datekeys:dkc3:sig-part:v1"
|
|
|
|
|
sealSubjectPrefix = "datekeys:dkc3:seal-subject:v1"
|
|
|
|
|
// AuthorMessagePrefix is the first line of AUTHOR_MESSAGE.
|
|
|
|
|
AuthorMessagePrefix = "datekeys:dkc3:author-signature:v1"
|
|
|
|
|
// AuthorMessageSize is the length of AUTHOR_MESSAGE: the prefix, a line
|
|
|
|
|
// feed, the 64 hexadecimal digits of its digest and a line feed.
|
|
|
|
|
AuthorMessageSize = len(AuthorMessagePrefix) + 1 + 64 + 1
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The values of alg that this version defines (spec v0.11, §29.3).
|
|
|
|
|
const (
|
|
|
|
|
// AlgEd25519 is a strict Ed25519 signature with a key of one's own (§29.9).
|
|
|
|
|
AlgEd25519 = 1
|
|
|
|
|
// AlgCMS is a CMS signature with X.509 certificates (§29.10).
|
|
|
|
|
AlgCMS = 2
|
|
|
|
|
// AlgTest and SealTypeTest are reserved for tests: no version defines
|
|
|
|
|
// them, so they are F1 and S1 in every version (§29.3).
|
|
|
|
|
AlgTest = 4294967295
|
|
|
|
|
SealTypeTest = 4294967295
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// AuthorKey signs AUTHOR_MESSAGE with alg 1, as *authorkey.Key does.
|
|
|
|
|
type AuthorKey interface {
|
|
|
|
|
// Public returns the public key A, 32 bytes.
|
|
|
|
|
Public() []byte
|
|
|
|
|
// Sign returns the Ed25519 signature of message, 64 bytes.
|
|
|
|
|
Sign(message []byte) []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CMSSigner makes the signature of alg 2 (spec v0.11, §29.10). EncryptFiles
|
|
|
|
|
// calls Sign with AUTHOR_MESSAGE once the capsule is prepared, and waits: the
|
|
|
|
|
// person signs it outside, with AutoFirma or another application, and Sign
|
|
|
|
|
// returns the DER of the CMS signature that she got, with its seals.
|
|
|
|
|
type CMSSigner interface {
|
|
|
|
|
// Signers returns the SHA-256 of the certificate of each required
|
|
|
|
|
// signer, from 1 to 16.
|
|
|
|
|
Signers() [][32]byte
|
|
|
|
|
// Sign returns the detached CMS signature of message, in DER.
|
|
|
|
|
Sign(message []byte) ([]byte, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sealer asks an authority for an RFC 3161 time-stamp token (spec §29.11).
|
|
|
|
|
type Sealer interface {
|
|
|
|
|
// Seal returns the DER of the token over SHA-256(subject), where subject
|
|
|
|
|
// is SEAL_SUBJECT; the imprint SHA-256 of the 32 bytes is the one that
|
|
|
|
|
// the token must hold.
|
|
|
|
|
Seal(subject [32]byte) ([]byte, error)
|
|
|
|
|
}
|
|
|
|
|
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
func domainHash(prefix string, parts ...[]byte) [32]byte {
|
|
|
|
|
h := sha256.New()
|
|
|
|
|
h.Write([]byte(prefix))
|
|
|
|
|
h.Write([]byte{0})
|
|
|
|
|
for _, p := range parts {
|
|
|
|
|
h.Write(p)
|
|
|
|
|
}
|
|
|
|
|
var out [32]byte
|
|
|
|
|
h.Sum(out[:0])
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PayloadCommit is the commitment to I_PAYLOAD that replaces it in what is
|
|
|
|
|
// signed (spec §29.8).
|
|
|
|
|
func PayloadCommit(identity [32]byte) [32]byte {
|
|
|
|
|
return domainHash(payloadCommitPrefix, identity[:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ControlCommit is control_commit: the hash of CONTROL_SIG, the control of a
|
|
|
|
|
// capsule of format f with payload_commit in place of I_PAYLOAD and the eight
|
|
|
|
|
// bytes of L at zero (spec §29.8). It does not depend on L, so the area can
|
|
|
|
|
// grow after signing.
|
|
|
|
|
func ControlCommit(c *Control, f Format) ([32]byte, error) {
|
|
|
|
|
sig := *c
|
|
|
|
|
sig.PayloadIdentity = PayloadCommit(c.PayloadIdentity)
|
|
|
|
|
sig.PayloadLength = 0
|
|
|
|
|
b, err := EncodeControl(&sig, f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return [32]byte{}, err
|
|
|
|
|
}
|
|
|
|
|
return domainHash(controlCommitPrefix, b), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HeadDigest is head_digest, the hash of HEAD_CBOR (spec §29.8). The salt of
|
|
|
|
|
// the head makes it a commitment that hides the files.
|
|
|
|
|
func HeadDigest(head []byte) [32]byte { return domainHash(headDigestPrefix, head) }
|
|
|
|
|
|
|
|
|
|
// SignersDigest is signers_digest for alg and the exact content of key 1 of a
|
|
|
|
|
// signature of alg 2, signers; nil with alg 1 (spec §29.8).
|
|
|
|
|
func SignersDigest(alg uint32, signers []byte) [32]byte {
|
|
|
|
|
return domainHash(signersDigestPrefix, binary.BigEndian.AppendUint32(nil, alg), signers)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AuthorMessage is AUTHOR_MESSAGE, the ASCII text that an author signs:
|
|
|
|
|
// AuthorMessagePrefix, a line feed, the hexadecimal digest D of the three
|
|
|
|
|
// commitments and a line feed (spec §29.8).
|
|
|
|
|
func AuthorMessage(controlCommit, headDigest, signersDigest [32]byte) []byte {
|
|
|
|
|
d := sha256.Sum256(append(append(controlCommit[:], headDigest[:]...), signersDigest[:]...))
|
|
|
|
|
m := make([]byte, 0, AuthorMessageSize)
|
|
|
|
|
m = append(m, AuthorMessagePrefix...)
|
|
|
|
|
m = append(m, '\n')
|
|
|
|
|
m = hex.AppendEncode(m, d[:])
|
|
|
|
|
return append(m, '\n')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AuthorCode is the code of AUTHOR_MESSAGE that a person compares before
|
|
|
|
|
// signing: the first 8 hexadecimal digits of its digest, in two groups of 4.
|
|
|
|
|
func AuthorCode(message []byte) string {
|
|
|
|
|
if len(message) != AuthorMessageSize {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
d := message[len(AuthorMessagePrefix)+1:]
|
|
|
|
|
return string(d[:4]) + "-" + string(d[4:8])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SigPart is SIG_PART: 0x00 without key 2, and 0x01 and the hash of the exact
|
|
|
|
|
// content of key 2 otherwise, whatever its alg and its verdict (spec §29.11).
|
|
|
|
|
func SigPart(signature []byte) []byte {
|
|
|
|
|
if signature == nil {
|
|
|
|
|
return []byte{0}
|
|
|
|
|
}
|
|
|
|
|
h := domainHash(sigPartPrefix, signature)
|
|
|
|
|
return append([]byte{1}, h[:]...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SealSubject is SEAL_SUBJECT, what a seal of seal_type 2 seals (spec §29.11).
|
|
|
|
|
func SealSubject(controlCommit, headDigest [32]byte, sigPart []byte) [32]byte {
|
|
|
|
|
return domainHash(sealSubjectPrefix, controlCommit[:], headDigest[:], sigPart)
|
|
|
|
|
}
|
|
|
|
|
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// SecurityKey2 returns the exact content of key 2 of SECURITY_CBOR and, when
|
|
|
|
|
// it is an author-signature that decodes, the signature value it holds: what
|
|
|
|
|
// a generator of test vectors records about a signature. It fails when
|
|
|
|
|
// security has no key 2 or its content does not decode.
|
|
|
|
|
func SecurityKey2(security []byte) (content, value []byte, err error) {
|
|
|
|
|
w, ok := decodeSecurity(security)
|
|
|
|
|
if !ok || w.signature == nil {
|
|
|
|
|
return nil, nil, errors.New("capsule: SECURITY_CBOR holds no author-signature")
|
|
|
|
|
}
|
|
|
|
|
a, err := decodeAuthorSignature(w.signature)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
return w.signature, a.value, nil
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// SecurityKey3 returns the seal_type and the token of key 3 of SECURITY_CBOR:
|
|
|
|
|
// what a generator of test vectors records about a seal. It fails when
|
|
|
|
|
// security has no key 3 or its content does not decode.
|
|
|
|
|
func SecurityKey3(security []byte) (sealType uint64, token []byte, err error) {
|
|
|
|
|
w, ok := decodeSecurity(security)
|
|
|
|
|
if !ok || w.seal == nil {
|
|
|
|
|
return 0, nil, errors.New("capsule: SECURITY_CBOR holds no seal")
|
|
|
|
|
}
|
|
|
|
|
s, err := decodeSeal(w.seal)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, nil, err
|
|
|
|
|
}
|
|
|
|
|
return s.sealType, s.token, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DecodeAuthorSignature reads the content of key 2 of SECURITY_CBOR: the alg,
|
|
|
|
|
// key 1 (the public key of alg 1, SIGNERS of alg 2) and the signature value.
|
|
|
|
|
func DecodeAuthorSignature(content []byte) (alg uint64, key, value []byte, err error) {
|
|
|
|
|
a, err := decodeAuthorSignature(content)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
return a.alg, a.key, a.value, nil
|
|
|
|
|
}
|
|
|
|
|
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// SecurityContext is what the verdicts of a signature or a seal need besides
|
|
|
|
|
// SECURITY_CBOR: the commitments of the capsule, the time of its round, and
|
|
|
|
|
// the author keys that the person saved, by their dkauthor1… string, with the
|
|
|
|
|
// label she gave them (F3). A reader builds it at step 17.6.
|
|
|
|
|
type SecurityContext struct {
|
|
|
|
|
ControlCommit, HeadDigest [32]byte
|
|
|
|
|
RoundTime time.Time
|
|
|
|
|
AuthorKeys map[string]string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EvaluateSecurityIn returns the verdicts of SECURITY_CBOR in the capsule
|
|
|
|
|
// that c describes (spec §29.7). Without a context, as EvaluateSecurity, it
|
|
|
|
|
// checks only the structure: any signature is F1, as in v0.10. It never
|
|
|
|
|
// fails: security never decides the opening.
|
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
|
|
|
func EvaluateSecurityIn(b []byte, c *SecurityContext) Verdicts {
|
|
|
|
|
// Security never decides the opening (spec §29.3): whatever a parser does
|
|
|
|
|
// with hostile input, the capsule opens. A panic is a failure of its own
|
|
|
|
|
// part only, as a failure of its form would be: X for the outer map, F1
|
|
|
|
|
// for the signature and S2 for the seal, each apart from the other.
|
|
|
|
|
var w *securityWire
|
|
|
|
|
if !recovered(func() { w, _ = decodeSecurity(b) }) || w == nil {
|
|
|
|
|
return Verdicts{Signature: VerdictUnreadable, Seal: VerdictUnreadable}
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
}
|
|
|
|
|
v := Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal}
|
|
|
|
|
if w.signature != nil {
|
|
|
|
|
v.Signature = VerdictSignatureUnchecked
|
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 c != nil && !recovered(func() { evaluateSignature(&v, w, c) }) {
|
|
|
|
|
v = Verdicts{Signature: VerdictSignatureUnchecked, Seal: VerdictNoSeal}
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if w.seal != 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
|
|
|
signature := v
|
|
|
|
|
if !recovered(func() { setSeal(&v, w, c) }) {
|
|
|
|
|
v = signature
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
v.Seal = VerdictSealUnreadable
|
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 v.Detail != nil {
|
|
|
|
|
d := *v.Detail
|
|
|
|
|
d.SealHolder, d.SealTime = "", time.Time{}
|
|
|
|
|
v.Detail = &d
|
|
|
|
|
}
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// setSeal sets the verdict of the seal of w (spec §29.7): S2 for content that
|
|
|
|
|
// breaks the schema of seal, S1 for a seal_type this reader does not
|
|
|
|
|
// implement, and the verdicts of §29.11 for seal_type 2.
|
|
|
|
|
func setSeal(v *Verdicts, w *securityWire, c *SecurityContext) {
|
|
|
|
|
s, err := decodeSeal(w.seal)
|
|
|
|
|
switch {
|
|
|
|
|
case err != nil:
|
|
|
|
|
v.Seal = VerdictSealUnreadable
|
|
|
|
|
case s.sealType == SealTypeRFC3161 && c != nil:
|
|
|
|
|
evaluateSeal(v, s, w.signature, c)
|
|
|
|
|
default:
|
|
|
|
|
v.Seal = VerdictSealUnsupported
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// recovered runs f and reports whether it returned without a panic.
|
|
|
|
|
func recovered(f func()) (ok bool) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
ok = false
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
f()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// evaluateSignature sets the verdict of the content of key 2: F1 for content
|
|
|
|
|
// that does not decode, an alg this reader does not implement or a key or a
|
|
|
|
|
// signature of another length; F2 when the signature does not verify; F3 or
|
|
|
|
|
// F4 when it does (spec §29.7, §29.9).
|
|
|
|
|
func evaluateSignature(v *Verdicts, w *securityWire, c *SecurityContext) {
|
|
|
|
|
a, err := decodeAuthorSignature(w.signature)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if a.alg == AlgCMS {
|
|
|
|
|
evaluateCMS(v, a, w.seal != nil, c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if a.alg != AlgEd25519 || len(a.key) != 32 || len(a.value) != 64 {
|
Signature plan, steps 3 and 4 begun: what is signed and the verdicts of alg 1
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
msg := AuthorMessage(c.ControlCommit, c.HeadDigest, SignersDigest(AlgEd25519, nil))
|
|
|
|
|
if !ed25519strict.Verify(a.key, msg, a.value) {
|
|
|
|
|
v.Signature = VerdictSignatureInvalid
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
v.Signature = VerdictSignedOther
|
|
|
|
|
copy(v.AuthorKey[:], a.key)
|
|
|
|
|
if s, err := bech32.Encode("dkauthor", a.key); err == nil {
|
|
|
|
|
if label, ok := c.AuthorKeys[s]; ok {
|
|
|
|
|
v.Signature, v.AuthorLabel = VerdictSignedSaved, label
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|