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/signature2.go

230 lines
6.8 KiB

package capsule
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"strings"
"time"
"unicode/utf8"
"g.activething.com/go/DateKeys/codec"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/pathrule"
)
// SealTypeRFC3161 is the seal_type of a seal that is an RFC 3161 time-stamp
// token (spec v0.11, §29.3, §29.11).
const SealTypeRFC3161 = 2
// MaxSigners is the most required signers of an alg 2 signature (§29.10).
const MaxSigners = 16
// EncodeSigners returns SIGNERS, the content of key 1 of an author-signature
// of alg 2: a CBOR array of 1 to 16 strings of 32 bytes, the SHA-256 of the
// certificate of each required signer, in strictly ascending order of bytes
// (spec §29.10). It sorts them, and fails when there are none, too many or
// two are equal.
func EncodeSigners(hashes [][32]byte) ([]byte, error) {
if len(hashes) < 1 || len(hashes) > MaxSigners {
return nil, errors.New("capsule: SIGNERS holds from 1 to 16 certificates")
}
sorted := append([][32]byte(nil), hashes...)
for i := 1; i < len(sorted); i++ { // insertion sort: at most 16
for j := i; j > 0 && bytes.Compare(sorted[j-1][:], sorted[j][:]) > 0; j-- {
sorted[j-1], sorted[j] = sorted[j], sorted[j-1]
}
}
for i := 1; i < len(sorted); i++ {
if sorted[i-1] == sorted[i] {
return nil, errors.New("capsule: SIGNERS names a certificate twice")
}
}
var e codec.Encoder
e.Array(len(sorted))
for _, h := range sorted {
e.Bstr(h[:])
}
return e.Out()
}
// decodeSigners reads SIGNERS and checks the profile of spec §29.10.
func decodeSigners(b []byte) ([][32]byte, error) {
var out [][32]byte
decode := func(d *codec.Decoder) error {
n, err := d.Array(MaxSigners)
if err != nil {
return err
}
if n < 1 {
return errors.New("SIGNERS is empty")
}
for range n {
h, err := d.Bstr(32, 32)
if err != nil {
return err
}
var x [32]byte
copy(x[:], h)
if len(out) > 0 && bytes.Compare(out[len(out)-1][:], x[:]) >= 0 {
return errors.New("SIGNERS is not in strictly ascending order")
}
out = append(out, x)
}
return nil
}
encode := func(e *codec.Encoder) {
e.Array(len(out))
for _, h := range out {
e.Bstr(h[:])
}
}
if err := codec.Unmarshal(b, decode, encode); err != nil {
return nil, err
}
return out, nil
}
// MaxNameLen is the most code points of a name of a certificate that §29.7
// shows, the upper bound of a commonName in X.520.
const MaxNameLen = 64
// holderText is how §29.7 shows a name of a certificate: the name, when it
// meets the rules of the declared author, has at most MaxNameLen code points
// and no two spaces in a row, and the SHA-256 given otherwise. A name cannot
// then line up, with spaces, a text of its own where a terminal breaks the
// line.
func holderText(name string, hash [32]byte) string {
if name != "" && utf8.ValidString(name) && utf8.RuneCountInString(name) <= MaxNameLen && !strings.Contains(name, " ") && pathrule.CheckAuthor(name) == nil {
return name
}
return hex.EncodeToString(hash[:])
}
// evaluateCMS sets the verdict of a signature of alg 2 (spec §29.10): F1 for
// content that breaks its profile, F2 when the signature of a required
// signer is invalid, F5 when something the capsule demands is missing, F6
// when every required signer is valid and sealed. hasSeal is whether key 3
// exists, which an alg 2 signature forbids.
func evaluateCMS(v *Verdicts, a *authorSignature, hasSeal bool, c *SecurityContext) {
required, err := decodeSigners(a.key)
if err != nil {
return
}
sd, err := cms.ParseSignature(a.value)
if err != nil {
return
}
msg := AuthorMessage(c.ControlCommit, c.HeadDigest, SignersDigest(AlgCMS, a.key))
detail := &Detail{}
byHash := map[[32]byte]*cms.SignerInfo{}
for _, s := range sd.Signers {
byHash[s.Cert.Hash] = s
}
invalid, incomplete := false, hasSeal
for _, h := range required {
s := byHash[h]
if s == nil {
detail.Signers = append(detail.Signers, SignerLine{Holder: hex.EncodeToString(h[:]), Result: "absent"})
incomplete = true
continue
}
line := signerLine(s, msg, c.RoundTime)
switch line.Result {
case "invalid":
invalid = true
case "valid":
default:
incomplete = true
}
detail.Signers = append(detail.Signers, line)
}
for _, s := range sd.Signers {
if !isRequired(required, s.Cert.Hash) {
detail.Foreign = append(detail.Foreign, signerLine(s, msg, c.RoundTime))
}
}
v.Detail = detail
switch {
case invalid:
v.Signature = VerdictSignatureInvalid
case incomplete:
v.Signature = VerdictSignedIncomplete
default:
v.Signature = VerdictSignedComplete
}
}
func isRequired(required [][32]byte, h [32]byte) bool {
for _, r := range required {
if r == h {
return true
}
}
return false
}
// signerLine checks one SignerInfo as §29.10 orders: not verifiable, invalid,
// without seal, with an invalid seal, out of validity, or valid.
func signerLine(s *cms.SignerInfo, msg []byte, roundTime time.Time) SignerLine {
// The issuer is text of the certificate, as the holder is: it gets the same
// rules, and the SHA-256 of the issuer when it fails them, so that no
// escape, no control and no bidi character reaches a line of the verdicts.
l := SignerLine{Holder: holderText(s.Cert.Holder(), s.Cert.Hash), Issuer: holderText(s.Cert.IssuerName(), sha256.Sum256(s.Cert.RawIssuer))}
switch s.Check(msg) {
case cms.NotVerifiable:
l.Result = "not verifiable"
return l
case cms.Invalid:
l.Result = "invalid"
return l
}
if s.Token == nil {
l.Result = "without seal"
return l
}
tok, err := cms.ParseToken(s.Token)
if err != nil || !tok.Check(s.Signature) {
l.Result = "invalid seal"
return l
}
if !s.Cert.ValidAt(tok.GenTime) {
l.Result = "out of validity"
return l
}
l.Result, l.SealTime, l.SealHolder = "valid", tok.GenTime, holderText(tok.TSA.Holder(), tok.TSA.Hash)
l.Reason = sealReason(tok, roundTime)
l.Before = l.Reason == ReasonNone
return l
}
// evaluateSeal sets the verdict of a seal of seal_type 2 (spec §29.11): S2 or
// S1 for the form and the algorithms, S3 when it does not verify, and S4 or
// S5 when it does. signature is the content of key 2, nil without it.
func evaluateSeal(v *Verdicts, s *seal, signature []byte, c *SecurityContext) {
tok, err := cms.ParseToken(s.token)
switch {
case errors.Is(err, cms.ErrForm):
v.Seal = VerdictSealUnreadable
return
case err != nil || !tok.ImprintIsSHA256():
v.Seal = VerdictSealUnsupported
return
}
subject := SealSubject(c.ControlCommit, c.HeadDigest, SigPart(signature))
if !tok.Check(subject[:]) {
v.Seal = VerdictSealInvalid
return
}
if v.Detail == nil {
v.Detail = &Detail{}
}
v.Detail.SealHolder, v.Detail.SealTime = holderText(tok.TSA.Holder(), tok.TSA.Hash), tok.GenTime
v.Detail.SealReason = sealReason(tok, c.RoundTime)
v.Seal = VerdictSealedLate
if v.Detail.SealReason == ReasonNone {
v.Seal = VerdictSealed
}
}

Powered by TurnKey Linux.