internal/der checks that bytes are one element of DER. internal/cms reads the detached CMS signature of spec v0.11 29.10 and the RFC 3161 token of 29.11, in the order of the spec, with the closed table of algorithms (RSA PKCS 1 and PSS of 2048 to 4096 bits, ECDSA on P-256, P-384 and P-521, SHA-2), with the standard library only. A certificate is read with encoding/asn1, so that a key of a curve Go lacks makes a signature "not verifiable" and not malformed. internal/cms/cmstest builds them for tests. Not wired into capsule yet. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>v0.11
parent
b88c459eaf
commit
55a261c792
@ -0,0 +1,145 @@
|
||||
package cms
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cert is an X.509 certificate read for what spec v0.11 §29.10 uses of it:
|
||||
// who it names, when it is valid and its key. DateKeys does not check who
|
||||
// issued it. It is read with encoding/asn1 and not with crypto/x509, which
|
||||
// refuses a certificate with a key of a curve that Go does not have, such as
|
||||
// brainpool: that certificate is still one that a signer names, and its
|
||||
// signature is "not verifiable", not malformed.
|
||||
type Cert struct {
|
||||
// Raw is the DER of the certificate and Hash its SHA-256.
|
||||
Raw []byte
|
||||
Hash [32]byte
|
||||
|
||||
Serial *big.Int
|
||||
RawIssuer, RawSubject []byte
|
||||
SKI []byte
|
||||
NotBefore, NotAfter time.Time
|
||||
// SPKI is the DER of the SubjectPublicKeyInfo.
|
||||
SPKI []byte
|
||||
Subject pkix.RDNSequence
|
||||
Issuer pkix.RDNSequence
|
||||
}
|
||||
|
||||
type tbsCertificate struct {
|
||||
Raw asn1.RawContent
|
||||
Version int `asn1:"optional,explicit,default:0,tag:0"`
|
||||
Serial *big.Int
|
||||
SigAlg asn1.RawValue
|
||||
Issuer asn1.RawValue
|
||||
Validity validity
|
||||
Subject asn1.RawValue
|
||||
PublicKey asn1.RawValue
|
||||
IssuerUID asn1.BitString `asn1:"optional,tag:1"`
|
||||
SubjectUID asn1.BitString `asn1:"optional,tag:2"`
|
||||
Extensions []pkix.Extension `asn1:"optional,explicit,tag:3"`
|
||||
}
|
||||
|
||||
type validity struct{ NotBefore, NotAfter time.Time }
|
||||
|
||||
type certificate struct {
|
||||
TBS tbsCertificate
|
||||
SigAlg asn1.RawValue
|
||||
Sig asn1.BitString
|
||||
}
|
||||
|
||||
var oidSKI = asn1.ObjectIdentifier{2, 5, 29, 14}
|
||||
|
||||
// ParseCert reads the DER of a certificate.
|
||||
func ParseCert(raw []byte) (*Cert, error) {
|
||||
var c certificate
|
||||
rest, err := asn1.Unmarshal(raw, &c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("certificate: %w", err)
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("certificate: bytes after it")
|
||||
}
|
||||
t := &c.TBS
|
||||
if t.Serial == nil || t.Issuer.Tag != asn1.TagSequence || t.Subject.Tag != asn1.TagSequence || t.PublicKey.Tag != asn1.TagSequence {
|
||||
return nil, errors.New("certificate: malformed")
|
||||
}
|
||||
out := &Cert{
|
||||
Raw: raw, Hash: sha256.Sum256(raw), Serial: t.Serial,
|
||||
RawIssuer: t.Issuer.FullBytes, RawSubject: t.Subject.FullBytes,
|
||||
NotBefore: t.Validity.NotBefore, NotAfter: t.Validity.NotAfter, SPKI: t.PublicKey.FullBytes,
|
||||
}
|
||||
if _, err := asn1.Unmarshal(out.RawSubject, &out.Subject); err != nil {
|
||||
return nil, fmt.Errorf("certificate: subject: %w", err)
|
||||
}
|
||||
if _, err := asn1.Unmarshal(out.RawIssuer, &out.Issuer); err != nil {
|
||||
return nil, fmt.Errorf("certificate: issuer: %w", err)
|
||||
}
|
||||
for _, e := range t.Extensions {
|
||||
if e.Id.Equal(oidSKI) {
|
||||
var ski []byte
|
||||
if rest, err := asn1.Unmarshal(e.Value, &ski); err != nil || len(rest) != 0 {
|
||||
return nil, errors.New("certificate: subjectKeyIdentifier malformed")
|
||||
}
|
||||
out.SKI = ski
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var (
|
||||
oidCommonName = asn1.ObjectIdentifier{2, 5, 4, 3}
|
||||
oidSurname = asn1.ObjectIdentifier{2, 5, 4, 4}
|
||||
oidGivenName = asn1.ObjectIdentifier{2, 5, 4, 42}
|
||||
)
|
||||
|
||||
func rdnString(rdn pkix.RDNSequence, oid asn1.ObjectIdentifier) string {
|
||||
for _, set := range rdn {
|
||||
for _, a := range set {
|
||||
if s, ok := a.Value.(string); ok && a.Type.Equal(oid) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Holder returns the name of the subject, taken from its commonName or from
|
||||
// its givenName and surname, and "" when it has neither. It does not apply the
|
||||
// rules of text of spec §29.6 to it: the caller does, and shows the hash of
|
||||
// the certificate when they fail.
|
||||
func (c *Cert) Holder() string {
|
||||
if n := rdnString(c.Subject, oidCommonName); n != "" {
|
||||
return n
|
||||
}
|
||||
given, sur := rdnString(c.Subject, oidGivenName), rdnString(c.Subject, oidSurname)
|
||||
if given != "" && sur != "" {
|
||||
return given + " " + sur
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IssuerName returns the issuer as the certificate names it, as a text for
|
||||
// the person to read: commonName, or all of the attributes it has.
|
||||
func (c *Cert) IssuerName() string {
|
||||
if n := rdnString(c.Issuer, oidCommonName); n != "" {
|
||||
return n
|
||||
}
|
||||
return strings.TrimSpace(c.Issuer.String())
|
||||
}
|
||||
|
||||
// ValidAt reports whether t is in the validity period of the certificate.
|
||||
func (c *Cert) ValidAt(t time.Time) bool {
|
||||
return !t.Before(c.NotBefore) && !t.After(c.NotAfter)
|
||||
}
|
||||
|
||||
func (c *Cert) hasSID(issuer []byte, serial *big.Int) bool {
|
||||
return bytes.Equal(c.RawIssuer, issuer) && c.Serial.Cmp(serial) == 0
|
||||
}
|
||||
@ -0,0 +1,470 @@
|
||||
// Package cms reads the CMS signatures (RFC 5652) and the RFC 3161 time-stamp
|
||||
// tokens that spec v0.11 §29.10 and §29.11 define, with the CAdES profile
|
||||
// that AutoFirma and other signing applications produce, and checks them with
|
||||
// a closed table of algorithms. It uses the standard library only.
|
||||
//
|
||||
// It checks the signature and the dates, never who issued a certificate or
|
||||
// whether it was revoked: a validator of the country that corresponds does
|
||||
// that (spec §29.10).
|
||||
package cms
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"math/big"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/der"
|
||||
)
|
||||
|
||||
// ErrForm is the error of a signature or a token whose form breaks the
|
||||
// profile of spec §29.10 or §29.11: the verdicts F1 and S2.
|
||||
var ErrForm = errors.New("cms: the form breaks the profile")
|
||||
|
||||
// ErrAlgorithm is the error of a token that uses an algorithm outside the
|
||||
// table of spec §29.10: the verdict S1.
|
||||
var ErrAlgorithm = errors.New("cms: an algorithm outside the table")
|
||||
|
||||
func formErr(format string, args ...any) error {
|
||||
return fmt.Errorf("%w: %s", ErrForm, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// The object identifiers of the profile.
|
||||
var (
|
||||
oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
|
||||
oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
|
||||
oidContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
|
||||
oidMessageDig = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
|
||||
oidSigCertV1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12}
|
||||
oidSigCertV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47}
|
||||
oidSigTimeStamp = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}
|
||||
oidTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4}
|
||||
oidRIOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 16, 2}
|
||||
|
||||
oidSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
|
||||
oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
|
||||
oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
|
||||
oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
|
||||
)
|
||||
|
||||
// SignedData is the part of a CMS SignedData that the profile uses.
|
||||
type SignedData struct {
|
||||
Certs []*Cert
|
||||
// OCSP are the OCSP responses of crls.
|
||||
OCSP [][]byte
|
||||
// Signers are the SignerInfo, in the order of the encoding.
|
||||
Signers []*SignerInfo
|
||||
// EContent is the content of a token, nil in a detached signature.
|
||||
EContent []byte
|
||||
}
|
||||
|
||||
// SignerInfo is a SignerInfo with the certificate that its sid names.
|
||||
type SignerInfo struct {
|
||||
Cert *Cert
|
||||
// DigestAlg and SigAlg are the algorithm identifiers as written.
|
||||
DigestAlg, SigAlg algID
|
||||
// SignedAttrs is the DER of the signedAttrs as stored, with the
|
||||
// context tag [0]; the signature covers it with the tag of a SET.
|
||||
SignedAttrs []byte
|
||||
MessageDigest []byte
|
||||
Signature []byte
|
||||
// Token is the signature-time-stamp attribute, the DER of its
|
||||
// ContentInfo, nil when there is none.
|
||||
Token []byte
|
||||
}
|
||||
|
||||
type algID struct {
|
||||
OID asn1.ObjectIdentifier
|
||||
Params []byte // the DER of the parameters, nil when absent
|
||||
}
|
||||
|
||||
func parseAlgID(b []byte) (algID, error) {
|
||||
id, kids, err := der.Split(b)
|
||||
if err != nil || id != 0x30 || len(kids) < 1 || len(kids) > 2 || kids[0][0] != 0x06 {
|
||||
return algID{}, formErr("an AlgorithmIdentifier")
|
||||
}
|
||||
var a algID
|
||||
if _, err := asn1.Unmarshal(kids[0], &a.OID); err != nil {
|
||||
return algID{}, formErr("an AlgorithmIdentifier: %v", err)
|
||||
}
|
||||
if len(kids) == 2 {
|
||||
a.Params = kids[1]
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// hashOf returns the hash that an identifier of the table names, and false
|
||||
// for any other.
|
||||
func (a algID) hashOf() (func() hash.Hash, bool) {
|
||||
if a.Params != nil && !bytes.Equal(a.Params, []byte{5, 0}) {
|
||||
return nil, false
|
||||
}
|
||||
switch {
|
||||
case a.OID.Equal(oidSHA256):
|
||||
return sha256.New, true
|
||||
case a.OID.Equal(oidSHA384):
|
||||
return sha512.New384, true
|
||||
case a.OID.Equal(oidSHA512):
|
||||
return sha512.New, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ParseSignature reads the detached CMS signature of an author-signature of
|
||||
// alg 2 (spec §29.10), checking its form in the order of the spec.
|
||||
func ParseSignature(b []byte) (*SignedData, error) {
|
||||
return parse(b, false)
|
||||
}
|
||||
|
||||
func parse(b []byte, token bool) (*SignedData, error) {
|
||||
if err := der.Check(b); err != nil {
|
||||
return nil, formErr("%v", err)
|
||||
}
|
||||
_, ci, err := der.Split(b)
|
||||
if err != nil || len(ci) != 2 || ci[0][0] != 0x06 || ci[1][0] != 0xa0 {
|
||||
return nil, formErr("a ContentInfo")
|
||||
}
|
||||
var oid asn1.ObjectIdentifier
|
||||
if _, err := asn1.Unmarshal(ci[0], &oid); err != nil || !oid.Equal(oidSignedData) {
|
||||
return nil, formErr("the content type is not id-signedData")
|
||||
}
|
||||
_, inner, err := der.Split(ci[1])
|
||||
if err != nil || len(inner) != 1 || inner[0][0] != 0x30 {
|
||||
return nil, formErr("a SignedData")
|
||||
}
|
||||
_, sd, err := der.Split(inner[0])
|
||||
if err != nil || len(sd) < 4 || sd[0][0] != 0x02 || sd[1][0] != 0x31 || sd[2][0] != 0x30 {
|
||||
return nil, formErr("a SignedData")
|
||||
}
|
||||
// digestAlgorithms: a SET OF in order.
|
||||
_, algs, err := der.Split(sd[1])
|
||||
if err != nil || !der.SetOfSorted(algs) {
|
||||
return nil, formErr("digestAlgorithms is not a SET OF in DER order")
|
||||
}
|
||||
for _, a := range algs {
|
||||
if _, err := parseAlgID(a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
out := &SignedData{}
|
||||
if err := parseEncap(sd[2], token, out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rest := sd[3:]
|
||||
if len(rest) > 0 && rest[0][0] == 0xa0 {
|
||||
if err := parseCerts(rest[0], out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rest = rest[1:]
|
||||
}
|
||||
if len(rest) > 0 && rest[0][0] == 0xa1 {
|
||||
if err := parseCRLs(rest[0], out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rest = rest[1:]
|
||||
}
|
||||
if len(rest) != 1 || rest[0][0] != 0x31 {
|
||||
return nil, formErr("signerInfos")
|
||||
}
|
||||
_, infos, err := der.Split(rest[0])
|
||||
if err != nil || len(infos) == 0 || !der.SetOfSorted(infos) {
|
||||
return nil, formErr("signerInfos is not a SET OF in DER order, or is empty")
|
||||
}
|
||||
if token && len(infos) != 1 {
|
||||
return nil, formErr("a token has one SignerInfo, not %d", len(infos))
|
||||
}
|
||||
used := map[*Cert]bool{}
|
||||
for _, si := range infos {
|
||||
s, err := parseSignerInfo(si, out, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if used[s.Cert] {
|
||||
return nil, formErr("two SignerInfo for one certificate")
|
||||
}
|
||||
used[s.Cert] = true
|
||||
out.Signers = append(out.Signers, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// parseEncap checks encapContentInfo: id-data without content in a detached
|
||||
// signature, and id-ct-TSTInfo with its content in a token.
|
||||
func parseEncap(b []byte, token bool, out *SignedData) error {
|
||||
_, kids, err := der.Split(b)
|
||||
if err != nil || len(kids) < 1 || len(kids) > 2 || kids[0][0] != 0x06 {
|
||||
return formErr("encapContentInfo")
|
||||
}
|
||||
var oid asn1.ObjectIdentifier
|
||||
if _, err := asn1.Unmarshal(kids[0], &oid); err != nil {
|
||||
return formErr("encapContentInfo")
|
||||
}
|
||||
if !token {
|
||||
if !oid.Equal(oidData) || len(kids) != 1 {
|
||||
return formErr("a signature is detached: id-data and no eContent")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !oid.Equal(oidTSTInfo) || len(kids) != 2 || kids[1][0] != 0xa0 {
|
||||
return formErr("a token holds a TSTInfo")
|
||||
}
|
||||
_, e, err := der.Split(kids[1])
|
||||
if err != nil || len(e) != 1 || e[0][0] != 0x04 {
|
||||
return formErr("eContent")
|
||||
}
|
||||
if out.EContent, err = der.Content(e[0]); err != nil {
|
||||
return formErr("eContent")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCerts(b []byte, out *SignedData) error {
|
||||
_, kids, err := der.Split(b)
|
||||
if err != nil || !der.SetOfSorted(kids) {
|
||||
return formErr("certificates is not a SET OF in DER order")
|
||||
}
|
||||
for _, k := range kids {
|
||||
if k[0] != 0x30 { // another choice of CertificateChoices: it decides nothing
|
||||
continue
|
||||
}
|
||||
c, err := ParseCert(k)
|
||||
if err != nil {
|
||||
return formErr("%v", err)
|
||||
}
|
||||
out.Certs = append(out.Certs, c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCRLs(b []byte, out *SignedData) error {
|
||||
_, kids, err := der.Split(b)
|
||||
if err != nil || !der.SetOfSorted(kids) {
|
||||
return formErr("crls is not a SET OF in DER order")
|
||||
}
|
||||
for _, k := range kids {
|
||||
if k[0] != 0xa1 {
|
||||
return formErr("crls holds only OCSP responses")
|
||||
}
|
||||
_, f, err := der.Split(k)
|
||||
if err != nil || len(f) != 2 || f[0][0] != 0x06 {
|
||||
return formErr("an OtherRevocationInfoFormat")
|
||||
}
|
||||
var oid asn1.ObjectIdentifier
|
||||
if _, err := asn1.Unmarshal(f[0], &oid); err != nil || !oid.Equal(oidRIOCSP) {
|
||||
return formErr("crls holds only OCSP responses")
|
||||
}
|
||||
out.OCSP = append(out.OCSP, f[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSignerInfo(b []byte, sd *SignedData, token bool) (*SignerInfo, error) {
|
||||
_, f, err := der.Split(b)
|
||||
if err != nil || len(f) < 6 || f[0][0] != 0x02 || f[2][0] != 0x30 || f[3][0] != 0xa0 || f[4][0] != 0x30 || f[5][0] != 0x04 {
|
||||
return nil, formErr("a SignerInfo with signedAttrs")
|
||||
}
|
||||
s := &SignerInfo{SignedAttrs: f[3]}
|
||||
if s.Cert, err = findSigner(f[1], sd.Certs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.DigestAlg, err = parseAlgID(f[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.SigAlg, err = parseAlgID(f[4]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Signature, err = der.Content(f[5]); err != nil {
|
||||
return nil, formErr("signature")
|
||||
}
|
||||
if len(f) > 7 || len(f) == 7 && f[6][0] != 0xa1 {
|
||||
return nil, formErr("a SignerInfo with something after its signature")
|
||||
}
|
||||
if err := parseSignedAttrs(s, token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(f) == 7 {
|
||||
if err := parseUnsignedAttrs(s, f[6]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// findSigner returns the one certificate that the sid names.
|
||||
func findSigner(sid []byte, certs []*Cert) (*Cert, error) {
|
||||
var found *Cert
|
||||
n := 0
|
||||
switch sid[0] {
|
||||
case 0x30: // issuerAndSerialNumber
|
||||
_, p, err := der.Split(sid)
|
||||
if err != nil || len(p) != 2 || p[0][0] != 0x30 || p[1][0] != 0x02 {
|
||||
return nil, formErr("issuerAndSerialNumber")
|
||||
}
|
||||
var serial *big.Int
|
||||
if _, err := asn1.Unmarshal(p[1], &serial); err != nil {
|
||||
return nil, formErr("issuerAndSerialNumber")
|
||||
}
|
||||
for _, c := range certs {
|
||||
if c.hasSID(p[0], serial) {
|
||||
found, n = c, n+1
|
||||
}
|
||||
}
|
||||
case 0x80: // subjectKeyIdentifier
|
||||
ski, err := der.Content(sid)
|
||||
if err != nil {
|
||||
return nil, formErr("subjectKeyIdentifier")
|
||||
}
|
||||
for _, c := range certs {
|
||||
if c.SKI != nil && bytes.Equal(c.SKI, ski) {
|
||||
found, n = c, n+1
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, formErr("a SignerIdentifier")
|
||||
}
|
||||
if n != 1 {
|
||||
return nil, formErr("a sid that names %d certificates, not one", n)
|
||||
}
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// attrs returns the values of each attribute type of the SET OF Attribute b.
|
||||
func attrs(b []byte) (map[string][][]byte, error) {
|
||||
_, kids, err := der.Split(b)
|
||||
if err != nil || !der.SetOfSorted(kids) {
|
||||
return nil, formErr("attributes are not a SET OF in DER order")
|
||||
}
|
||||
out := map[string][][]byte{}
|
||||
for _, a := range kids {
|
||||
_, p, err := der.Split(a)
|
||||
if err != nil || len(p) != 2 || a[0] != 0x30 || p[0][0] != 0x06 || p[1][0] != 0x31 {
|
||||
return nil, formErr("an Attribute")
|
||||
}
|
||||
var oid asn1.ObjectIdentifier
|
||||
if _, err := asn1.Unmarshal(p[0], &oid); err != nil {
|
||||
return nil, formErr("an Attribute")
|
||||
}
|
||||
_, vals, err := der.Split(p[1])
|
||||
if err != nil || !der.SetOfSorted(vals) {
|
||||
return nil, formErr("the values of an attribute are not a SET OF in DER order")
|
||||
}
|
||||
out[oid.String()] = append(out[oid.String()], vals...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// one returns the only value of the attribute, which must exist once.
|
||||
func one(m map[string][][]byte, oid asn1.ObjectIdentifier, name string) ([]byte, error) {
|
||||
v := m[oid.String()]
|
||||
if len(v) != 1 {
|
||||
return nil, formErr("%s: %d values, not one", name, len(v))
|
||||
}
|
||||
return v[0], nil
|
||||
}
|
||||
|
||||
// parseSignedAttrs checks the signedAttrs of the profile (spec §29.10 rule 4,
|
||||
// §29.11): content-type, message-digest and the signing certificate.
|
||||
func parseSignedAttrs(s *SignerInfo, token bool) error {
|
||||
m, err := attrs(s.SignedAttrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Each of the three is one attribute with one value: attrs merges the
|
||||
// values of attributes of one type, so two attributes show as two values.
|
||||
ct, err := one(m, oidContentType, "content-type")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
want := oidData
|
||||
if token {
|
||||
want = oidTSTInfo
|
||||
}
|
||||
var got asn1.ObjectIdentifier
|
||||
if _, err := asn1.Unmarshal(ct, &got); err != nil || !got.Equal(want) {
|
||||
return formErr("content-type is not %v", want)
|
||||
}
|
||||
md, err := one(m, oidMessageDig, "message-digest")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if md[0] != 0x04 {
|
||||
return formErr("message-digest is not an OCTET STRING")
|
||||
}
|
||||
if s.MessageDigest, err = der.Content(md); err != nil {
|
||||
return formErr("message-digest")
|
||||
}
|
||||
switch {
|
||||
case len(m[oidSigCertV2.String()]) == 1 && len(m[oidSigCertV1.String()]) == 0:
|
||||
return checkESSCert(s.Cert, m[oidSigCertV2.String()][0], true)
|
||||
case token && len(m[oidSigCertV1.String()]) == 1 && len(m[oidSigCertV2.String()]) == 0:
|
||||
return checkESSCert(s.Cert, m[oidSigCertV1.String()][0], false)
|
||||
}
|
||||
return formErr("signing-certificate: one attribute of one value is required")
|
||||
}
|
||||
|
||||
// checkESSCert checks that the first ESSCertID of a signing-certificate or
|
||||
// signing-certificate-v2 (RFC 2634, RFC 5035) is the hash of the certificate.
|
||||
func checkESSCert(c *Cert, v []byte, v2 bool) error {
|
||||
id, sc, err := der.Split(v)
|
||||
if err != nil || id != 0x30 || len(sc) < 1 || sc[0][0] != 0x30 {
|
||||
return formErr("a SigningCertificate")
|
||||
}
|
||||
_, ids, err := der.Split(sc[0])
|
||||
if err != nil || len(ids) < 1 || ids[0][0] != 0x30 {
|
||||
return formErr("an ESSCertID")
|
||||
}
|
||||
_, f, err := der.Split(ids[0])
|
||||
if err != nil || len(f) < 1 {
|
||||
return formErr("an ESSCertID")
|
||||
}
|
||||
newHash := sha1.New
|
||||
if v2 {
|
||||
newHash = sha256.New
|
||||
if f[0][0] == 0x30 { // hashAlgorithm, which defaults to SHA-256
|
||||
a, err := parseAlgID(f[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, ok := a.hashOf()
|
||||
if !ok {
|
||||
return formErr("the hash of the ESSCertIDv2 is outside the table")
|
||||
}
|
||||
newHash, f = h, f[1:]
|
||||
}
|
||||
}
|
||||
if len(f) < 1 || f[0][0] != 0x04 {
|
||||
return formErr("certHash")
|
||||
}
|
||||
hv, err := der.Content(f[0])
|
||||
if err != nil {
|
||||
return formErr("certHash")
|
||||
}
|
||||
h := newHash()
|
||||
h.Write(c.Raw)
|
||||
if !bytes.Equal(h.Sum(nil), hv) {
|
||||
return formErr("the certHash is not that of the certificate of the signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseUnsignedAttrs reads the signature-time-stamp, at most one with one
|
||||
// value (spec §29.10 rule 4); the other attributes decide nothing.
|
||||
func parseUnsignedAttrs(s *SignerInfo, b []byte) error {
|
||||
m, err := attrs(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := m[oidSigTimeStamp.String()]; len(v) {
|
||||
case 0:
|
||||
case 1:
|
||||
s.Token = v[0]
|
||||
default:
|
||||
return formErr("signature-time-stamp: %d values, not one", len(v))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,175 @@
|
||||
package cms_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/elliptic"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/cms"
|
||||
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||
)
|
||||
|
||||
var (
|
||||
from = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
to = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
now = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC)
|
||||
msg = []byte("datekeys:dkc3:author-signature:v1\n00\n")
|
||||
)
|
||||
|
||||
func TestSignatureAlgorithms(t *testing.T) {
|
||||
rsa2048 := cmstest.NewRSA("Ana López", 2048, from, to)
|
||||
p256 := cmstest.NewECDSA("Luis", elliptic.P256(), from, to)
|
||||
p384 := cmstest.NewECDSA("Eva", elliptic.P384(), from, to)
|
||||
p521 := cmstest.NewECDSA("Raúl", elliptic.P521(), from, to)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
opts cmstest.Options
|
||||
s cmstest.Signer
|
||||
}{
|
||||
{"RSA PKCS1 SHA-256", cmstest.Options{}, rsa2048},
|
||||
{"RSA PKCS1 SHA-384", cmstest.Options{Hash: crypto.SHA384}, rsa2048},
|
||||
{"RSA PKCS1 SHA-512", cmstest.Options{Hash: crypto.SHA512}, rsa2048},
|
||||
{"RSA PSS SHA-256", cmstest.Options{PSS: true}, rsa2048},
|
||||
{"RSA PSS SHA-512", cmstest.Options{PSS: true, Hash: crypto.SHA512}, rsa2048},
|
||||
{"RSA by subjectKeyIdentifier", cmstest.Options{SKI: true}, rsa2048},
|
||||
{"ECDSA P-256", cmstest.Options{}, p256},
|
||||
{"ECDSA P-384 SHA-384", cmstest.Options{Hash: crypto.SHA384}, p384},
|
||||
{"ECDSA P-521 SHA-512", cmstest.Options{Hash: crypto.SHA512}, p521},
|
||||
} {
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, tc.opts, tc.s))
|
||||
if err != nil || len(sd.Signers) != 1 {
|
||||
t.Errorf("%s: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
si := sd.Signers[0]
|
||||
if si.Cert.Hash != [32]byte(sha(tc.s.Cert.Raw)) || si.Check(msg) != cms.Valid {
|
||||
t.Errorf("%s: result %v", tc.name, si.Check(msg))
|
||||
}
|
||||
if si.Check([]byte("another message")) != cms.Invalid {
|
||||
t.Errorf("%s: another message verifies", tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoSignature(t *testing.T) {
|
||||
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
|
||||
b := cmstest.NewRSA("Banco S.A.", 2048, from, to)
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{}, a, b))
|
||||
if err != nil || len(sd.Signers) != 2 || len(sd.Certs) != 2 {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
for _, s := range sd.Signers {
|
||||
if s.Check(msg) != cms.Valid {
|
||||
t.Errorf("%s does not verify", s.Cert.Holder())
|
||||
}
|
||||
}
|
||||
if sd.Signers[0].Cert.Holder() == "" || sd.Signers[0].Cert.IssuerName() == "" {
|
||||
t.Error("no holder or issuer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenOverSignature(t *testing.T) {
|
||||
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
|
||||
tsa := cmstest.NewRSA("TSA de prueba", 2048, from, to)
|
||||
tok := func(sig []byte) []byte {
|
||||
return cmstest.Token(sig, now, cmstest.TokenOptions{Accuracy: 2 * time.Second}, tsa)
|
||||
}
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{Token: tok}, a))
|
||||
if err != nil || sd.Signers[0].Token == nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
token, err := cms.ParseToken(sd.Signers[0].Token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !token.GenTime.Equal(now) || token.Accuracy != 2*time.Second || token.TSA.Holder() != "TSA de prueba" || !token.ImprintIsSHA256() {
|
||||
t.Errorf("token %+v", token)
|
||||
}
|
||||
if !token.Check(sd.Signers[0].Signature) || token.Check([]byte("other")) {
|
||||
t.Error("the token seals the signature value and nothing else")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenFailures(t *testing.T) {
|
||||
tsa := cmstest.NewECDSA("TSA", elliptic.P256(), from, to)
|
||||
old := cmstest.NewECDSA("TSA caducada", elliptic.P256(), from, time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
subject := []byte("seal subject")
|
||||
|
||||
if tok, err := cms.ParseToken(cmstest.Token(subject, now, cmstest.TokenOptions{}, tsa)); err != nil || !tok.Check(subject) {
|
||||
t.Fatalf("a good token: %v", err)
|
||||
}
|
||||
// S3: it verifies as a token, but not for this content or this date.
|
||||
for name, der := range map[string][]byte{
|
||||
"another imprint": cmstest.Token(subject, now, cmstest.TokenOptions{Imprint: bytes.Repeat([]byte{1}, 32)}, tsa),
|
||||
"the TSA had expired": cmstest.Token(subject, now, cmstest.TokenOptions{}, old),
|
||||
} {
|
||||
tok, err := cms.ParseToken(der)
|
||||
if err != nil || tok.Check(subject) {
|
||||
t.Errorf("%s: %v, valid %v", name, err, err == nil && tok.Check(subject))
|
||||
}
|
||||
}
|
||||
// S2: the form.
|
||||
for name, der := range map[string][]byte{
|
||||
"a TSTInfo of version 2": cmstest.Token(subject, now, cmstest.TokenOptions{Version: 2}, tsa),
|
||||
"not DER": {0x30, 0x80, 0x00, 0x00},
|
||||
"empty": nil,
|
||||
} {
|
||||
if _, err := cms.ParseToken(der); !errors.Is(err, cms.ErrForm) {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
// S1: an algorithm outside the table.
|
||||
small := cmstest.NewRSA("TSA 1024", 1024, from, to)
|
||||
if _, err := cms.ParseToken(cmstest.Token(subject, now, cmstest.TokenOptions{}, small)); !errors.Is(err, cms.ErrAlgorithm) {
|
||||
t.Errorf("a key of 1024 bits: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureNotVerifiable(t *testing.T) {
|
||||
small := cmstest.NewRSA("Chica", 1024, from, to)
|
||||
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{}, small))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r := sd.Signers[0].Check(msg); r != cms.NotVerifiable {
|
||||
t.Errorf("RSA of 1024 bits: %v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureForm(t *testing.T) {
|
||||
a := cmstest.NewECDSA("Ana", elliptic.P256(), from, to)
|
||||
good := cmstest.Signature(msg, cmstest.Options{}, a)
|
||||
if _, err := cms.ParseSignature(good); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bad := map[string][]byte{
|
||||
"a byte after it": append(bytes.Clone(good), 0),
|
||||
"truncated": good[:len(good)-1],
|
||||
"not a SignedData": {0x30, 0x03, 0x02, 0x01, 0x00},
|
||||
"no certificate of the signer": cmstest.Signature(msg, cmstest.Options{OmitCert: true}, a),
|
||||
"two timestamp attributes": cmstest.Signature(msg, cmstest.Options{Token2: true, Token: func(s []byte) []byte {
|
||||
return cmstest.Seq(cmstest.OID(cmstest.OIDData))
|
||||
}}, a),
|
||||
"a signing-certificate with another hash": cmstest.Signature(msg, cmstest.Options{Mutate: func(attrs [][]byte) [][]byte {
|
||||
attrs[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 32)))))))
|
||||
return attrs
|
||||
}}, a),
|
||||
"no message-digest": cmstest.Signature(msg, cmstest.Options{Mutate: func(attrs [][]byte) [][]byte { return append(attrs[:1], attrs[2:]...) }}, a),
|
||||
}
|
||||
for name, der := range bad {
|
||||
if _, err := cms.ParseSignature(der); !errors.Is(err, cms.ErrForm) {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sha(b []byte) []byte {
|
||||
h := cmstestSHA(b)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
func cmstestSHA(b []byte) [32]byte { return sha256.Sum256(b) }
|
||||
@ -0,0 +1,348 @@
|
||||
// Package cmstest builds the CMS signatures and the RFC 3161 tokens that the
|
||||
// tests of internal/cms and of capsule read: certificates of test keys, a
|
||||
// detached signature of a message with its signedAttrs and, optionally, a
|
||||
// time-stamp token of its signature. It is the encoder that a signing
|
||||
// application has; nothing outside tests uses it.
|
||||
package cmstest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"math/big"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Signer is a certificate with its private key.
|
||||
type Signer struct {
|
||||
Cert *x509.Certificate
|
||||
Key crypto.Signer
|
||||
}
|
||||
|
||||
// NewRSA returns a signer with an RSA key of bits bits, valid in
|
||||
// [notBefore, notAfter], named cn.
|
||||
func NewRSA(cn string, bits int, notBefore, notAfter time.Time) Signer {
|
||||
k, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newSigner(cn, k, notBefore, notAfter)
|
||||
}
|
||||
|
||||
// NewECDSA returns a signer with an ECDSA key on curve.
|
||||
func NewECDSA(cn string, curve elliptic.Curve, notBefore, notAfter time.Time) Signer {
|
||||
k, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newSigner(cn, k, notBefore, notAfter)
|
||||
}
|
||||
|
||||
func newSigner(cn string, k crypto.Signer, notBefore, notAfter time.Time) Signer {
|
||||
serial, _ := rand.Int(rand.Reader, big.NewInt(1<<62))
|
||||
t := &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: cn, Organization: []string{"DateKeys test"}},
|
||||
NotBefore: notBefore, NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
SubjectKeyId: []byte(cn),
|
||||
}
|
||||
raw, err := x509.CreateCertificate(rand.Reader, t, t, k.Public(), k)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c, err := x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Signer{Cert: c, Key: k}
|
||||
}
|
||||
|
||||
// The DER building blocks.
|
||||
|
||||
func tlv(tag byte, content ...[]byte) []byte {
|
||||
c := bytes.Join(content, nil)
|
||||
out := []byte{tag}
|
||||
switch n := len(c); {
|
||||
case n < 0x80:
|
||||
out = append(out, byte(n))
|
||||
case n < 0x100:
|
||||
out = append(out, 0x81, byte(n))
|
||||
case n < 0x10000:
|
||||
out = append(out, 0x82, byte(n>>8), byte(n))
|
||||
default:
|
||||
out = append(out, 0x83, byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
return append(out, c...)
|
||||
}
|
||||
|
||||
// Seq is a SEQUENCE.
|
||||
func Seq(content ...[]byte) []byte { return tlv(0x30, content...) }
|
||||
|
||||
// Set is a SET OF, in DER order.
|
||||
func Set(tag byte, elems ...[]byte) []byte {
|
||||
e := slices.Clone(elems)
|
||||
slices.SortFunc(e, bytes.Compare)
|
||||
return tlv(tag, e...)
|
||||
}
|
||||
|
||||
// OID is an OBJECT IDENTIFIER.
|
||||
func OID(oid asn1.ObjectIdentifier) []byte {
|
||||
b, err := asn1.Marshal(oid)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Octets is an OCTET STRING.
|
||||
func Octets(b []byte) []byte { return tlv(0x04, b) }
|
||||
|
||||
// Int is an INTEGER.
|
||||
func Int(n int64) []byte {
|
||||
b, err := asn1.Marshal(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
var (
|
||||
OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
|
||||
OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
|
||||
oidContent = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
|
||||
oidDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
|
||||
OIDSigCertV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47}
|
||||
oidSigCertV1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12}
|
||||
OIDTimeStamp = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}
|
||||
oidTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4}
|
||||
oidOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 16, 2}
|
||||
oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
|
||||
oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
|
||||
oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
|
||||
oidRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
oidPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
|
||||
oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}
|
||||
oidECDSA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
|
||||
oidECDSA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
|
||||
oidECDSA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
|
||||
)
|
||||
|
||||
func hashAlg(h crypto.Hash) []byte {
|
||||
switch h {
|
||||
case crypto.SHA384:
|
||||
return Seq(OID(oidSHA384))
|
||||
case crypto.SHA512:
|
||||
return Seq(OID(oidSHA512))
|
||||
}
|
||||
return Seq(OID(oidSHA256))
|
||||
}
|
||||
|
||||
func hashOID(h crypto.Hash) asn1.ObjectIdentifier {
|
||||
switch h {
|
||||
case crypto.SHA384:
|
||||
return oidSHA384
|
||||
case crypto.SHA512:
|
||||
return oidSHA512
|
||||
}
|
||||
return oidSHA256
|
||||
}
|
||||
|
||||
func sum(h crypto.Hash, b []byte) []byte {
|
||||
x := h.New()
|
||||
x.Write(b)
|
||||
return x.Sum(nil)
|
||||
}
|
||||
|
||||
// Options changes what Build writes, to make signatures that the profile
|
||||
// rejects or that verify to something else.
|
||||
type Options struct {
|
||||
// Hash is the digest of the signature, SHA-256 by default.
|
||||
Hash crypto.Hash
|
||||
// PSS signs with RSASSA-PSS instead of PKCS #1 v1.5.
|
||||
PSS bool
|
||||
// SKI names the signer by subjectKeyIdentifier instead of by issuer
|
||||
// and serial.
|
||||
SKI bool
|
||||
// Token, when not nil, is the time-stamp token of the signature that
|
||||
// Build wants as an unsigned attribute: it receives the signature value.
|
||||
Token func(signature []byte) []byte
|
||||
// Message is what the message-digest covers, when not the signed message.
|
||||
Message []byte
|
||||
// Mutate edits the signedAttrs, as a list of the DER of each attribute,
|
||||
// before they are signed.
|
||||
Mutate func(attrs [][]byte) [][]byte
|
||||
// Token2 puts two signature-time-stamp attributes, to break the profile.
|
||||
Token2 bool
|
||||
// OCSP adds this response in crls.
|
||||
OCSP []byte
|
||||
// OmitCert leaves the certificate of the signer out of certificates.
|
||||
OmitCert bool
|
||||
}
|
||||
|
||||
func attr(oid asn1.ObjectIdentifier, values ...[]byte) []byte {
|
||||
return Seq(OID(oid), Set(0x31, values...))
|
||||
}
|
||||
|
||||
// Signature returns the detached CMS signature of message by the signers, in
|
||||
// DER, as AutoFirma writes it: one SignerInfo each, with content-type,
|
||||
// message-digest and signing-certificate-v2 as signed attributes.
|
||||
func Signature(message []byte, opts Options, signers ...Signer) []byte {
|
||||
return build(message, opts, false, signers)
|
||||
}
|
||||
|
||||
func build(message []byte, o Options, token bool, signers []Signer) []byte {
|
||||
if o.Hash == 0 {
|
||||
o.Hash = crypto.SHA256
|
||||
}
|
||||
var certs, infos [][]byte
|
||||
for _, s := range signers {
|
||||
if !o.OmitCert {
|
||||
certs = append(certs, s.Cert.Raw)
|
||||
}
|
||||
infos = append(infos, signerInfo(message, o, token, s))
|
||||
}
|
||||
var body []byte
|
||||
body = append(body, Int(1)...)
|
||||
body = append(body, Set(0x31, hashAlg(o.Hash))...)
|
||||
body = append(body, encap(message, token)...)
|
||||
if len(certs) > 0 {
|
||||
body = append(body, Set(0xa0, certs...)...)
|
||||
}
|
||||
if o.OCSP != nil {
|
||||
body = append(body, Set(0xa1, tlv(0xa1, OID(oidOCSP), o.OCSP))...)
|
||||
}
|
||||
body = append(body, Set(0x31, infos...)...)
|
||||
return Seq(OID(OIDSignedData), tlv(0xa0, Seq(body)))
|
||||
}
|
||||
|
||||
func encap(content []byte, token bool) []byte {
|
||||
if !token {
|
||||
return Seq(OID(OIDData))
|
||||
}
|
||||
return Seq(OID(oidTSTInfo), tlv(0xa0, Octets(content)))
|
||||
}
|
||||
|
||||
func signerInfo(message []byte, o Options, token bool, s Signer) []byte {
|
||||
var sid []byte
|
||||
if o.SKI {
|
||||
sid = tlv(0x80, s.Cert.SubjectKeyId)
|
||||
} else {
|
||||
sid = Seq(s.Cert.RawIssuer, mustMarshal(s.Cert.SerialNumber))
|
||||
}
|
||||
contentType := OIDData
|
||||
if token {
|
||||
contentType = oidTSTInfo
|
||||
}
|
||||
md := message
|
||||
if o.Message != nil {
|
||||
md = o.Message
|
||||
}
|
||||
essHash := sha256.Sum256(s.Cert.Raw)
|
||||
attrs := [][]byte{
|
||||
attr(oidContent, OID(contentType)),
|
||||
attr(oidDigest, Octets(sum(o.Hash, md))),
|
||||
}
|
||||
if token {
|
||||
h := sha1.Sum(s.Cert.Raw)
|
||||
attrs = append(attrs, attr(oidSigCertV1, Seq(Seq(Seq(Octets(h[:]))))))
|
||||
} else {
|
||||
attrs = append(attrs, attr(OIDSigCertV2, Seq(Seq(Seq(Octets(essHash[:]))))))
|
||||
}
|
||||
if o.Mutate != nil {
|
||||
attrs = o.Mutate(attrs)
|
||||
}
|
||||
signed := Set(0xa0, attrs...)
|
||||
forSig := append([]byte{0x31}, signed[1:]...)
|
||||
digest := sum(o.Hash, forSig)
|
||||
|
||||
var sigAlg, sig []byte
|
||||
switch k := s.Key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if o.PSS {
|
||||
params := Seq(tlv(0xa0, hashAlg(o.Hash)), tlv(0xa1, Seq(OID(oidMGF1), hashAlg(o.Hash))), tlv(0xa2, Int(int64(o.Hash.Size()))))
|
||||
sigAlg = Seq(OID(oidPSS), params)
|
||||
sig, _ = rsa.SignPSS(rand.Reader, k, o.Hash, digest, &rsa.PSSOptions{SaltLength: o.Hash.Size(), Hash: o.Hash})
|
||||
} else {
|
||||
sigAlg = Seq(OID(oidRSA), []byte{5, 0})
|
||||
sig, _ = rsa.SignPKCS1v15(rand.Reader, k, o.Hash, digest)
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
oid := oidECDSA256
|
||||
switch o.Hash {
|
||||
case crypto.SHA384:
|
||||
oid = oidECDSA384
|
||||
case crypto.SHA512:
|
||||
oid = oidECDSA512
|
||||
}
|
||||
sigAlg = Seq(OID(oid))
|
||||
sig, _ = ecdsa.SignASN1(rand.Reader, k, digest)
|
||||
}
|
||||
f := [][]byte{Int(1), sid, hashAlg(o.Hash), signed, sigAlg, Octets(sig)}
|
||||
if o.Token != nil {
|
||||
t := o.Token(sig)
|
||||
v := attr(OIDTimeStamp, t)
|
||||
if o.Token2 {
|
||||
v = Seq(OID(OIDTimeStamp), Set(0x31, t, Seq(OID(OIDData))))
|
||||
}
|
||||
f = append(f, Set(0xa1, v))
|
||||
}
|
||||
return Seq(f...)
|
||||
}
|
||||
|
||||
func mustMarshal(v any) []byte {
|
||||
b, err := asn1.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// TokenOptions changes what Token writes.
|
||||
type TokenOptions struct {
|
||||
Hash crypto.Hash // the hash of the messageImprint, SHA-256 by default
|
||||
Accuracy time.Duration // whole seconds; zero for none
|
||||
Version int // the version of the TSTInfo, 1 by default
|
||||
// Imprint, when not nil, is written as the hashed message instead of
|
||||
// the hash of the subject.
|
||||
Imprint []byte
|
||||
}
|
||||
|
||||
// Token returns the RFC 3161 time-stamp token that tsa issues over subject
|
||||
// at genTime.
|
||||
func Token(subject []byte, genTime time.Time, o TokenOptions, tsa Signer) []byte {
|
||||
if o.Hash == 0 {
|
||||
o.Hash = crypto.SHA256
|
||||
}
|
||||
if o.Version == 0 {
|
||||
o.Version = 1
|
||||
}
|
||||
imprint := sum(o.Hash, subject)
|
||||
if o.Imprint != nil {
|
||||
imprint = o.Imprint
|
||||
}
|
||||
gt, err := asn1.MarshalWithParams(genTime.UTC(), "generalized")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
info := []byte{}
|
||||
info = append(info, Int(int64(o.Version))...)
|
||||
info = append(info, OID(asn1.ObjectIdentifier{1, 2, 3, 4})...)
|
||||
info = append(info, Seq(hashAlg(o.Hash), Octets(imprint))...)
|
||||
info = append(info, Int(42)...)
|
||||
info = append(info, gt...)
|
||||
if o.Accuracy != 0 {
|
||||
info = append(info, Seq(Int(int64(o.Accuracy/time.Second)))...)
|
||||
}
|
||||
tst := Seq(info)
|
||||
return build(tst, Options{Hash: crypto.SHA256}, true, []Signer{tsa})
|
||||
}
|
||||
@ -0,0 +1,304 @@
|
||||
package cms
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"g.activething.com/go/DateKeys/internal/der"
|
||||
)
|
||||
|
||||
// Result is the result of checking the signature of a SignerInfo (spec
|
||||
// §29.10, "Verificación").
|
||||
type Result int
|
||||
|
||||
const (
|
||||
// Valid: the message-digest is the hash of the message and the signature
|
||||
// of the signedAttrs verifies with the key of the certificate.
|
||||
Valid Result = iota
|
||||
// Invalid: one of the two does not hold.
|
||||
Invalid
|
||||
// NotVerifiable: an algorithm, a key size or a curve outside the table.
|
||||
NotVerifiable
|
||||
)
|
||||
|
||||
var (
|
||||
oidRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
oidSHA256RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
|
||||
oidSHA384RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
|
||||
oidSHA512RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
|
||||
oidPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
|
||||
oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}
|
||||
oidECDSA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
|
||||
oidECDSA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
|
||||
oidECDSA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
|
||||
)
|
||||
|
||||
type scheme int
|
||||
|
||||
const (
|
||||
schemePKCS1 scheme = iota + 1
|
||||
schemePSS
|
||||
schemeECDSA
|
||||
)
|
||||
|
||||
// params returns the hash, the signature scheme and the hash that the
|
||||
// signature algorithm itself names, when it does, of the SignerInfo, and
|
||||
// whether they are in the table.
|
||||
func (s *SignerInfo) params() (crypto.Hash, scheme, bool) {
|
||||
newHash, ok := s.DigestAlg.hashOf()
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
var h crypto.Hash
|
||||
switch newHash().Size() {
|
||||
case 32:
|
||||
h = crypto.SHA256
|
||||
case 48:
|
||||
h = crypto.SHA384
|
||||
default:
|
||||
h = crypto.SHA512
|
||||
}
|
||||
oid, params := s.SigAlg.OID, s.SigAlg.Params
|
||||
nullOrAbsent := params == nil || bytes.Equal(params, []byte{5, 0})
|
||||
switch {
|
||||
case oid.Equal(oidRSAEncryption):
|
||||
return h, schemePKCS1, nullOrAbsent
|
||||
case oid.Equal(oidSHA256RSA):
|
||||
return h, schemePKCS1, nullOrAbsent && h == crypto.SHA256
|
||||
case oid.Equal(oidSHA384RSA):
|
||||
return h, schemePKCS1, nullOrAbsent && h == crypto.SHA384
|
||||
case oid.Equal(oidSHA512RSA):
|
||||
return h, schemePKCS1, nullOrAbsent && h == crypto.SHA512
|
||||
case oid.Equal(oidECDSA256):
|
||||
return h, schemeECDSA, params == nil && h == crypto.SHA256
|
||||
case oid.Equal(oidECDSA384):
|
||||
return h, schemeECDSA, params == nil && h == crypto.SHA384
|
||||
case oid.Equal(oidECDSA512):
|
||||
return h, schemeECDSA, params == nil && h == crypto.SHA512
|
||||
case oid.Equal(oidPSS):
|
||||
return h, schemePSS, pssParamsOK(params, newHash().Size(), s.DigestAlg)
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
// pssParamsOK checks RSASSA-PSS-params (RFC 4055): the hash of digestAlgorithm,
|
||||
// MGF1 with that hash, a salt of its length and trailerField 1.
|
||||
func pssParamsOK(params []byte, hashLen int, digest algID) bool {
|
||||
if params == nil {
|
||||
return false
|
||||
}
|
||||
id, f, err := der.Split(params)
|
||||
if err != nil || id != 0x30 {
|
||||
return false
|
||||
}
|
||||
var hashOK, mgfOK, saltOK bool
|
||||
for _, e := range f {
|
||||
_, in, err := der.Split(e)
|
||||
if err != nil || len(in) != 1 {
|
||||
return false
|
||||
}
|
||||
switch e[0] {
|
||||
case 0xa0:
|
||||
a, err := parseAlgID(in[0])
|
||||
hashOK = err == nil && a.OID.Equal(digest.OID) && (a.Params == nil || bytes.Equal(a.Params, []byte{5, 0}))
|
||||
case 0xa1:
|
||||
a, err := parseAlgID(in[0])
|
||||
if err != nil || !a.OID.Equal(oidMGF1) || a.Params == nil {
|
||||
return false
|
||||
}
|
||||
inner, err := parseAlgID(a.Params)
|
||||
mgfOK = err == nil && inner.OID.Equal(digest.OID) && (inner.Params == nil || bytes.Equal(inner.Params, []byte{5, 0}))
|
||||
case 0xa2:
|
||||
var n int
|
||||
_, err := asn1.Unmarshal(in[0], &n)
|
||||
saltOK = err == nil && n == hashLen
|
||||
case 0xa3:
|
||||
var n int
|
||||
if _, err := asn1.Unmarshal(in[0], &n); err != nil || n != 1 {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
// hashAlgorithm and maskGenAlgorithm default to SHA-1, and the salt to 20
|
||||
// bytes: none of them is in the table, so each must be present.
|
||||
return hashOK && mgfOK && saltOK
|
||||
}
|
||||
|
||||
// publicKey returns the key of the certificate when it is in the table:
|
||||
// RSA of 2048 to 4096 bits with an odd exponent from 3 to 2^31 - 1, or ECDSA
|
||||
// on P-256, P-384 or P-521.
|
||||
func (c *Cert) publicKey() (any, scheme, bool) {
|
||||
k, err := x509.ParsePKIXPublicKey(c.SPKI)
|
||||
if err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
switch k := k.(type) {
|
||||
case *rsa.PublicKey:
|
||||
bits := k.N.BitLen()
|
||||
if bits < 2048 || bits > 4096 || k.E < 3 || k.E%2 == 0 || int64(k.E) > 1<<31-1 {
|
||||
return nil, 0, false
|
||||
}
|
||||
return k, schemePKCS1, true
|
||||
case *ecdsa.PublicKey:
|
||||
switch k.Curve {
|
||||
case elliptic.P256(), elliptic.P384(), elliptic.P521():
|
||||
return k, schemeECDSA, true
|
||||
}
|
||||
}
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
// algorithmsOK reports whether the algorithms of the SignerInfo and the key of
|
||||
// its certificate are in the table of spec §29.10.
|
||||
func (s *SignerInfo) algorithmsOK() bool {
|
||||
_, sch, ok := s.params()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ksch, ok := s.Cert.publicKey()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return sch == ksch || sch == schemePSS && ksch == schemePKCS1
|
||||
}
|
||||
|
||||
// Check checks the signature of the SignerInfo over message, the bytes that
|
||||
// the signature is detached from (spec §29.10): not verifiable for an
|
||||
// algorithm outside the table; invalid when the message-digest is not the
|
||||
// hash of message or the signature of the signedAttrs does not verify.
|
||||
func (s *SignerInfo) Check(message []byte) Result {
|
||||
if !s.algorithmsOK() {
|
||||
return NotVerifiable
|
||||
}
|
||||
h, sch, _ := s.params()
|
||||
if sum := hashBytes(h, message); !bytes.Equal(sum, s.MessageDigest) {
|
||||
return Invalid
|
||||
}
|
||||
// The signature covers the signedAttrs with the tag of a SET.
|
||||
attrs := bytes.Clone(s.SignedAttrs)
|
||||
attrs[0] = 0x31
|
||||
digest := hashBytes(h, attrs)
|
||||
key, _, _ := s.Cert.publicKey()
|
||||
var ok bool
|
||||
switch k := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
if sch == schemePSS {
|
||||
ok = rsa.VerifyPSS(k, h, digest, s.Signature, &rsa.PSSOptions{SaltLength: h.Size(), Hash: h}) == nil
|
||||
} else {
|
||||
ok = rsa.VerifyPKCS1v15(k, h, digest, s.Signature) == nil
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
ok = ecdsa.VerifyASN1(k, digest, s.Signature)
|
||||
}
|
||||
if !ok {
|
||||
return Invalid
|
||||
}
|
||||
return Valid
|
||||
}
|
||||
|
||||
func hashBytes(h crypto.Hash, b []byte) []byte {
|
||||
x := h.New()
|
||||
x.Write(b)
|
||||
return x.Sum(nil)
|
||||
}
|
||||
|
||||
// Token is a time-stamp token of RFC 3161 read with the profile of spec
|
||||
// §29.11.
|
||||
type Token struct {
|
||||
// GenTime is t, and Accuracy the precision of the token, zero when it
|
||||
// has none.
|
||||
GenTime time.Time
|
||||
Accuracy time.Duration
|
||||
// ImprintAlg is the hash of the messageImprint, and Imprint the hash.
|
||||
ImprintAlg asn1.ObjectIdentifier
|
||||
Imprint []byte
|
||||
// TSA is the certificate of the time-stamping authority.
|
||||
TSA *Cert
|
||||
|
||||
data *SignedData
|
||||
}
|
||||
|
||||
type messageImprint struct {
|
||||
Alg asn1.RawValue
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
type accuracy struct {
|
||||
Seconds int `asn1:"optional"`
|
||||
Millis int `asn1:"optional,tag:0"`
|
||||
Micros int `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
type tstInfo struct {
|
||||
Version int
|
||||
Policy asn1.ObjectIdentifier
|
||||
Imprint messageImprint
|
||||
Serial *big.Int
|
||||
GenTime time.Time `asn1:"generalized"`
|
||||
Accuracy accuracy `asn1:"optional"`
|
||||
Ordering bool `asn1:"optional,default:false"`
|
||||
Nonce *big.Int `asn1:"optional"`
|
||||
TSA asn1.RawValue `asn1:"optional,explicit,tag:0"`
|
||||
Ext asn1.RawValue `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
// ParseToken reads a time-stamp token. It fails with ErrForm when the form
|
||||
// breaks the profile, and with ErrAlgorithm when an algorithm is outside the
|
||||
// table, in that order (spec §29.11): the verdicts S2 and S1.
|
||||
func ParseToken(b []byte) (*Token, error) {
|
||||
sd, err := parse(b, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info tstInfo
|
||||
if rest, err := asn1.Unmarshal(sd.EContent, &info); err != nil || len(rest) != 0 {
|
||||
return nil, formErr("the TSTInfo does not decode")
|
||||
}
|
||||
if info.Version != 1 || info.Serial == nil || info.Imprint.Alg.Tag != asn1.TagSequence {
|
||||
return nil, formErr("a TSTInfo of version 1")
|
||||
}
|
||||
ia, err := parseAlgID(info.Imprint.Alg.FullBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &Token{
|
||||
GenTime: info.GenTime, ImprintAlg: ia.OID, Imprint: info.Imprint.Hash, TSA: sd.Signers[0].Cert, data: sd,
|
||||
Accuracy: time.Duration(info.Accuracy.Seconds)*time.Second + time.Duration(info.Accuracy.Millis)*time.Millisecond + time.Duration(info.Accuracy.Micros)*time.Microsecond,
|
||||
}
|
||||
newHash, ok := ia.hashOf()
|
||||
if ok && len(info.Imprint.Hash) != newHash().Size() {
|
||||
return nil, formErr("a messageImprint of the wrong length")
|
||||
}
|
||||
if !ok || !sd.Signers[0].algorithmsOK() {
|
||||
return nil, ErrAlgorithm
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ImprintIsSHA256 reports whether the messageImprint uses SHA-256, which a
|
||||
// seal of seal_type 2 requires (spec §29.11).
|
||||
func (t *Token) ImprintIsSHA256() bool { return t.ImprintAlg.Equal(oidSHA256) }
|
||||
|
||||
// Check verifies the token over subject, the bytes that it seals: the
|
||||
// message-digest is the hash of the TSTInfo, the signature of the TSA
|
||||
// verifies, the messageImprint is the hash of subject and the certificate of
|
||||
// the TSA is valid at genTime. It returns false for the verdict S3.
|
||||
func (t *Token) Check(subject []byte) bool {
|
||||
s := t.data.Signers[0]
|
||||
if s.Check(t.data.EContent) != Valid {
|
||||
return false
|
||||
}
|
||||
newHash, _ := algID{OID: t.ImprintAlg}.hashOf()
|
||||
h := newHash()
|
||||
h.Write(subject)
|
||||
return bytes.Equal(h.Sum(nil), t.Imprint) && t.TSA.ValidAt(t.GenTime)
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
// Package der checks that bytes are one element in the Distinguished Encoding
|
||||
// Rules of X.690, as spec v0.11 §29.10 asks of a CMS signature before it
|
||||
// looks inside it: definite and minimal lengths, no high tag numbers, no
|
||||
// constructed form of a type that DER only has primitive, canonical BOOLEAN,
|
||||
// INTEGER, NULL, OBJECT IDENTIFIER and BIT STRING, and no bytes after the
|
||||
// element. Go's encoding/asn1 accepts some of what DER forbids, and a
|
||||
// signature that two implementations read the same must not depend on it.
|
||||
//
|
||||
// The order of the elements of a SET OF cannot be checked without a schema:
|
||||
// SetOfSorted does it for the callers that know theirs.
|
||||
package der
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const maxDepth = 32
|
||||
|
||||
// Check returns nil when b is exactly one DER element.
|
||||
func Check(b []byte) error {
|
||||
n, err := check(b, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != len(b) {
|
||||
return fmt.Errorf("der: %d bytes after the element", len(b)-n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Split returns the content of the DER element b, which must be constructed,
|
||||
// as the encodings of its children, and the identifier octet of b. It
|
||||
// assumes Check passed.
|
||||
func Split(b []byte) (id byte, children [][]byte, err error) {
|
||||
if len(b) == 0 || b[0]&0x20 == 0 {
|
||||
return 0, nil, errors.New("der: not a constructed element")
|
||||
}
|
||||
hl, cl, err := header(b)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
rest := b[hl : hl+cl]
|
||||
for len(rest) > 0 {
|
||||
h, c, err := header(rest)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
children = append(children, rest[:h+c])
|
||||
rest = rest[h+c:]
|
||||
}
|
||||
return b[0], children, nil
|
||||
}
|
||||
|
||||
// Content returns the content octets of the DER element b.
|
||||
func Content(b []byte) ([]byte, error) {
|
||||
hl, cl, err := header(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[hl : hl+cl], nil
|
||||
}
|
||||
|
||||
// SetOfSorted reports whether the encodings are in ascending order of their
|
||||
// bytes, as DER requires of the elements of a SET OF (X.690 11.6), and
|
||||
// without repetitions: a SET OF of this profile has none.
|
||||
func SetOfSorted(elems [][]byte) bool {
|
||||
for i := 1; i < len(elems); i++ {
|
||||
if bytes.Compare(elems[i-1], elems[i]) >= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// header returns the length of the identifier and length octets of the
|
||||
// element at the start of b, and the length of its content, which must fit
|
||||
// in b.
|
||||
func header(b []byte) (headerLen, contentLen int, err error) {
|
||||
if len(b) < 2 {
|
||||
return 0, 0, errors.New("der: truncated element")
|
||||
}
|
||||
if b[0]&0x1f == 0x1f {
|
||||
return 0, 0, errors.New("der: a tag number of 31 or more")
|
||||
}
|
||||
l := b[1]
|
||||
switch {
|
||||
case l < 0x80:
|
||||
headerLen, contentLen = 2, int(l)
|
||||
case l == 0x80:
|
||||
return 0, 0, errors.New("der: an indefinite length")
|
||||
case l == 0xff:
|
||||
return 0, 0, errors.New("der: a length of 0xff")
|
||||
default:
|
||||
n := int(l & 0x7f)
|
||||
if n > 4 || len(b) < 2+n {
|
||||
return 0, 0, errors.New("der: a length that does not fit")
|
||||
}
|
||||
if b[2] == 0 {
|
||||
return 0, 0, errors.New("der: a length with a leading zero")
|
||||
}
|
||||
v := 0
|
||||
for _, c := range b[2 : 2+n] {
|
||||
v = v<<8 | int(c)
|
||||
}
|
||||
if v < 0x80 {
|
||||
return 0, 0, errors.New("der: a long form for a length under 128")
|
||||
}
|
||||
headerLen, contentLen = 2+n, v
|
||||
}
|
||||
if contentLen < 0 || contentLen > len(b)-headerLen {
|
||||
return 0, 0, errors.New("der: an element longer than its container")
|
||||
}
|
||||
return headerLen, contentLen, nil
|
||||
}
|
||||
|
||||
// check validates the element at the start of b and returns its length.
|
||||
func check(b []byte, depth int) (int, error) {
|
||||
if depth > maxDepth {
|
||||
return 0, errors.New("der: nested too deep")
|
||||
}
|
||||
hl, cl, err := header(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
content := b[hl : hl+cl]
|
||||
id := b[0]
|
||||
class, constructed, tag := id>>6, id&0x20 != 0, id&0x1f
|
||||
if constructed {
|
||||
if class == 0 && tag != 16 && tag != 17 {
|
||||
return 0, fmt.Errorf("der: constructed form of the universal type %d", tag)
|
||||
}
|
||||
for len(content) > 0 {
|
||||
n, err := check(content, depth+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
content = content[n:]
|
||||
}
|
||||
return hl + cl, nil
|
||||
}
|
||||
if class == 0 {
|
||||
if err := checkPrimitive(tag, content); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return hl + cl, nil
|
||||
}
|
||||
|
||||
func checkPrimitive(tag byte, c []byte) error {
|
||||
switch tag {
|
||||
case 1: // BOOLEAN
|
||||
if len(c) != 1 || (c[0] != 0 && c[0] != 0xff) {
|
||||
return errors.New("der: a BOOLEAN that is not 00 or FF")
|
||||
}
|
||||
case 2, 10: // INTEGER, ENUMERATED
|
||||
if len(c) == 0 {
|
||||
return errors.New("der: an empty INTEGER")
|
||||
}
|
||||
if len(c) > 1 && (c[0] == 0 && c[1]&0x80 == 0 || c[0] == 0xff && c[1]&0x80 != 0) {
|
||||
return errors.New("der: an INTEGER that is not minimal")
|
||||
}
|
||||
case 3: // BIT STRING
|
||||
if len(c) == 0 || c[0] > 7 || len(c) == 1 && c[0] != 0 {
|
||||
return errors.New("der: a malformed BIT STRING")
|
||||
}
|
||||
if len(c) > 1 && c[0] != 0 && c[len(c)-1]&(1<<c[0]-1) != 0 {
|
||||
return errors.New("der: a BIT STRING with unused bits that are not zero")
|
||||
}
|
||||
case 5: // NULL
|
||||
if len(c) != 0 {
|
||||
return errors.New("der: a NULL with content")
|
||||
}
|
||||
case 6: // OBJECT IDENTIFIER
|
||||
if len(c) == 0 || c[len(c)-1]&0x80 != 0 {
|
||||
return errors.New("der: a malformed OBJECT IDENTIFIER")
|
||||
}
|
||||
start := true
|
||||
for _, x := range c {
|
||||
if start && x == 0x80 {
|
||||
return errors.New("der: an OBJECT IDENTIFIER with a leading 0x80 in a subidentifier")
|
||||
}
|
||||
start = x&0x80 == 0
|
||||
}
|
||||
case 4, 12, 19, 22, 23, 24, 30: // OCTET STRING, UTF8String, PrintableString, IA5String, UTCTime, GeneralizedTime, BMPString
|
||||
case 16, 17:
|
||||
return fmt.Errorf("der: the universal type %d in primitive form", tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package der
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, hex string
|
||||
ok bool
|
||||
}{
|
||||
{"sequence of an integer and a null", "3005020101" + "0500", true},
|
||||
{"a long length", "04" + "8180" + zeros(128), true},
|
||||
{"a long form under 128", "0481" + "01" + "00", false},
|
||||
{"a length with a leading zero", "04820001" + "00", false},
|
||||
{"an indefinite length", "30800000", false},
|
||||
{"a high tag number", "1f0100", false},
|
||||
{"truncated", "0402aa", false},
|
||||
{"trailing bytes", "0500" + "00", false},
|
||||
{"BOOLEAN 01", "010101", false},
|
||||
{"BOOLEAN FF", "0101ff", true},
|
||||
{"INTEGER with a leading zero", "02020001", false},
|
||||
{"INTEGER 0x80 with its zero", "02020080", true},
|
||||
{"INTEGER with a leading FF", "0202ff80", false},
|
||||
{"empty INTEGER", "0200", false},
|
||||
{"NULL with content", "050100", false},
|
||||
{"constructed OCTET STRING", "2404" + "0402aabb", false},
|
||||
{"BIT STRING with unused bits set", "03020701", false},
|
||||
{"BIT STRING with unused bits clear", "03020780", true},
|
||||
{"BIT STRING of 4 bits", "030204f0", true},
|
||||
{"OID", "06032a0304", true},
|
||||
{"OID with a leading 0x80", "06038001" + "02", false},
|
||||
{"OID that does not end", "06022a83", false},
|
||||
{"context tag, constructed", "a003020101", true},
|
||||
{"SET in primitive form", "1100", false},
|
||||
} {
|
||||
b, err := hex.DecodeString(tc.hex)
|
||||
if err != nil {
|
||||
t.Fatal(tc.name, err)
|
||||
}
|
||||
if err := Check(b); (err == nil) != tc.ok {
|
||||
t.Errorf("%s: %v", tc.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func zeros(n int) string {
|
||||
b := make([]byte, n*2)
|
||||
for i := range b {
|
||||
b[i] = '0'
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func TestSetOfSorted(t *testing.T) {
|
||||
a, b := []byte{0x02, 0x01, 0x01}, []byte{0x02, 0x01, 0x02}
|
||||
if !SetOfSorted([][]byte{a, b}) || SetOfSorted([][]byte{b, a}) || SetOfSorted([][]byte{a, a}) {
|
||||
t.Error("SetOfSorted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
b, _ := hex.DecodeString("30050201010500")
|
||||
if err := Check(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id, kids, err := Split(b)
|
||||
if err != nil || id != 0x30 || len(kids) != 2 || len(kids[0]) != 3 || len(kids[1]) != 2 {
|
||||
t.Errorf("%x %v %v", id, kids, err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue