|
|
|
|
package capsule
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
Review fixes: the CMS reader, the area after the signature, and the issuer on screen
The TSTInfo is read field by field in DER, with accuracy from zero and
millis and micros from 1 to 999, genTime in UTC with Z, no default written
and nothing after the last field. The ContentInfo and the SignerInfo must be
SEQUENCEs, a SignerInfo version must match its sid, an attribute needs a
value and is counted by attribute and not by value, a signing-certificate
beside the v2 decides nothing, PSS parameters come in order without the
trailer, and der.Check refuses the end of contents and the universal tags
the profile does not use.
The writer signs before L is fixed: write asks prepare for the final L, so
the area grows to 64 KiB only when what was signed does not fit and LargeArea
allows it, and nobody signs twice for it. Typed nils are nil, the exclusions
are checked before a file is read, Encrypt refuses the signing options, and
EvaluateSecurityIn gives X if a parser panics. The issuer of a certificate is
filtered like its holder.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"errors"
|
Spec v0.12 draft and the verdicts of a certificate (not approved)
The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.
The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.12 draft and the verdicts of a certificate (not approved)
The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.
The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// 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 {
|
Spec v0.12 draft and the verdicts of a certificate (not approved)
The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.
The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
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 {
|
Review fixes: the CMS reader, the area after the signature, and the issuer on screen
The TSTInfo is read field by field in DER, with accuracy from zero and
millis and micros from 1 to 999, genTime in UTC with Z, no default written
and nothing after the last field. The ContentInfo and the SignerInfo must be
SEQUENCEs, a SignerInfo version must match its sid, an attribute needs a
value and is counted by attribute and not by value, a signing-certificate
beside the v2 decides nothing, PSS parameters come in order without the
trailer, and der.Check refuses the end of contents and the universal tags
the profile does not use.
The writer signs before L is fixed: write asks prepare for the final L, so
the area grows to 64 KiB only when what was signed does not fit and LargeArea
allows it, and nobody signs twice for it. Typed nils are nil, the exclusions
are checked before a file is read, Encrypt refuses the signing options, and
EvaluateSecurityIn gives X if a parser panics. The issuer of a certificate is
filtered like its holder.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// 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
|
|
|
|
|
}
|
Spec v0.12 draft and the verdicts of a certificate (not approved)
The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.
The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
l.Result, l.SealTime, l.SealHolder = "valid", tok.GenTime, holderText(tok.TSA.Holder(), tok.TSA.Hash)
|
|
|
|
|
l.Before = !roundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(roundTime)
|
|
|
|
|
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.Seal = VerdictSealedLate
|
Spec v0.12 draft and the verdicts of a certificate (not approved)
The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.
The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if !c.RoundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(c.RoundTime) {
|
|
|
|
|
v.Seal = VerdictSealed
|
|
|
|
|
}
|
|
|
|
|
}
|