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

292 lines
10 KiB

package capsule
import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"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
)
// 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)
}
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)
}
// 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
}
// 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
}
// 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.
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}
}
v := Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal}
if w.signature != nil {
v.Signature = VerdictSignatureUnchecked
if c != nil && !recovered(func() { evaluateSignature(&v, w, c) }) {
v = Verdicts{Signature: VerdictSignatureUnchecked, Seal: VerdictNoSeal}
}
}
if w.seal != nil {
signature := v
if !recovered(func() { setSeal(&v, w, c) }) {
v = signature
v.Seal = VerdictSealUnreadable
if v.Detail != nil {
d := *v.Detail
d.SealHolder, d.SealTime = "", time.Time{}
v.Detail = &d
}
}
}
return v
}
// 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
}
// 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 {
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
}
}
}

Powered by TurnKey Linux.