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.
349 lines
9.9 KiB
349 lines
9.9 KiB
// 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})
|
|
}
|