From 793b36af8c7e5bc6ccba07bcc273931362ff6258 Mon Sep 17 00:00:00 2001 From: Jonathan Klabunde Tomer Date: Tue, 19 Mar 2024 13:08:32 -0700 Subject: [PATCH] ietf-cms: add support for user-supplied signed attributes Some use cases for CMS (notably the code signatures in Apple signed MachO binaries) require additional attributes to be signed. This change adds a SignWithAttrs method to cms.SignedData that accepts additional signed attributes to include. --- ietf-cms/protocol/protocol.go | 7 ++- ietf-cms/protocol/protocol_test.go | 93 ++++++++++++++++++++++++++++++ ietf-cms/sign.go | 7 +++ 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/ietf-cms/protocol/protocol.go b/ietf-cms/protocol/protocol.go index 127ff51..1f65499 100644 --- a/ietf-cms/protocol/protocol.go +++ b/ietf-cms/protocol/protocol.go @@ -626,6 +626,11 @@ func NewSignedData(eci EncapsulatedContentInfo) (*SignedData, error) { // AddSignerInfo adds a SignerInfo to the SignedData. func (sd *SignedData) AddSignerInfo(chain []*x509.Certificate, signer crypto.Signer) error { + return sd.AddSignerInfoWithAttrs(nil, chain, signer) +} + +// AddSignerInfoWithAttrs adds a SignerInfo to the SignedData, allowing for the caller to supply extra Attributes to sign. +func (sd *SignedData) AddSignerInfoWithAttrs(attrs []Attribute, chain []*x509.Certificate, signer crypto.Signer) error { // figure out which certificate is associated with signer. pub, err := x509.MarshalPKIXPublicKey(signer.Public()) if err != nil { @@ -712,7 +717,7 @@ func (sd *SignedData) AddSignerInfo(chain []*x509.Certificate, signer crypto.Sig } // sort attributes to match required order in marshaled form - si.SignedAttrs, err = sortAttributes(stAttr, mdAttr, ctAttr) + si.SignedAttrs, err = sortAttributes(append([]Attribute{stAttr, mdAttr, ctAttr}, attrs...)...) if err != nil { return err } diff --git a/ietf-cms/protocol/protocol_test.go b/ietf-cms/protocol/protocol_test.go index cc3018d..a16742b 100644 --- a/ietf-cms/protocol/protocol_test.go +++ b/ietf-cms/protocol/protocol_test.go @@ -88,6 +88,99 @@ func TestSignerInfo(t *testing.T) { } } +func TestSignerInfoWithExtraAttrs(t *testing.T) { + priv, cert, err := pkcs12.Decode(fixturePFX, "asdf") + if err != nil { + t.Fatal(err) + } + + msg := []byte("hello, world!") + + eci, err := NewEncapsulatedContentInfo(oid.ContentTypeData, msg) + if err != nil { + t.Fatal(err) + } + + sd, err := NewSignedData(eci) + if err != nil { + t.Fatal(err) + } + + extraAttr, err := NewAttribute(asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6}, []byte("this is an attribute")) + if err != nil { + t.Fatal(err) + } + + chain := []*x509.Certificate{cert} + if err = sd.AddSignerInfoWithAttrs(Attributes{extraAttr}, chain, priv.(*ecdsa.PrivateKey)); err != nil { + t.Fatal(err) + } + + der, err := sd.ContentInfoDER() + if err != nil { + t.Fatal(err) + } + + ci, err := ParseContentInfo(der) + if err != nil { + t.Fatal(err) + } + + sd2, err := ci.SignedDataContent() + if err != nil { + t.Fatal(err) + } + + msg2, err := sd2.EncapContentInfo.DataEContent() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(msg, msg2) { + t.Fatal() + } + + attrs := sd2.SignerInfos[0].SignedAttrs + found := false + for _, attr := range attrs { + if attr.Type.Equal(extraAttr.Type) { + if !bytes.Equal(attr.RawValue.FullBytes, extraAttr.RawValue.FullBytes) { + t.Fatalf("Extra signed attribute data round trip mismatch: want %#v, got %#v", extraAttr, attr) + } + found = true + break + } + } + if !found { + t.Fatalf("Extra attribute not present in signer info") + } + + // Make detached + sd.EncapContentInfo.EContent = asn1.RawValue{} + + der, err = sd.ContentInfoDER() + if err != nil { + t.Fatal(err) + } + + ci, err = ParseContentInfo(der) + if err != nil { + t.Fatal(err) + } + + sd2, err = ci.SignedDataContent() + if err != nil { + t.Fatal(err) + } + + msg2, err = sd2.EncapContentInfo.DataEContent() + if err != nil { + t.Fatal(err) + } + if msg2 != nil { + t.Fatal() + } +} + func TestEncapsulatedContentInfo(t *testing.T) { ci, _ := ParseContentInfo(fixtureSignatureOpenSSLAttached) sd, _ := ci.SignedDataContent() diff --git a/ietf-cms/sign.go b/ietf-cms/sign.go index dd2de9e..dceecbb 100644 --- a/ietf-cms/sign.go +++ b/ietf-cms/sign.go @@ -3,6 +3,8 @@ package cms import ( "crypto" "crypto/x509" + + "github.com/github/smimesign/ietf-cms/protocol" ) // Sign creates a CMS SignedData from the content and signs it with signer. At @@ -47,3 +49,8 @@ func SignDetached(data []byte, chain []*x509.Certificate, signer crypto.Signer) func (sd *SignedData) Sign(chain []*x509.Certificate, signer crypto.Signer) error { return sd.psd.AddSignerInfo(chain, signer) } + +// SignWithAttrs adds a signature with extra signed attributes to the SignedData. +func (sd *SignedData) SignWithAttrs(attrs protocol.Attributes, chain []*x509.Certificate, signer crypto.Signer) error { + return sd.psd.AddSignerInfoWithAttrs(attrs, chain, signer) +}