From bf12d97ef899b538e83ef7d4cb07fd049d3a2749 Mon Sep 17 00:00:00 2001 From: Max Altgelt Date: Fri, 24 Nov 2023 13:46:39 +0100 Subject: [PATCH] feat: support copying PKCS7 structs --- sign.go | 26 +++++++++++++++++++++ sign_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ verify.go | 13 +++++++++++ 3 files changed, 103 insertions(+) diff --git a/sign.go b/sign.go index 31c3654..eb07f30 100644 --- a/sign.go +++ b/sign.go @@ -23,6 +23,32 @@ type SignedData struct { encryptionOid asn1.ObjectIdentifier } +// Copy creates a new SignedData struct based on existing PKCS7 signed data. +func Copy(data *PKCS7, unauthenticatedAttrs ...Attribute) ([]byte, error) { + rawData, isSignedData := data.raw.(signedData) + if !isSignedData { + return nil, ErrUnsupportedContentType + } + + // Make a copy of the signer infos since we change them + rawData.SignerInfos = append([]signerInfo{}, rawData.SignerInfos...) + for i := range rawData.SignerInfos { + err := rawData.SignerInfos[i].SetUnauthenticatedAttributes(unauthenticatedAttrs) + if err != nil { + return nil, err + } + } + inner, err := asn1.Marshal(rawData) + if err != nil { + return nil, err + } + outer := contentInfo{ + ContentType: OIDSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, + } + return asn1.Marshal(outer) +} + // NewSignedData takes data and initializes a PKCS7 SignedData struct that is // ready to be signed via AddSigner. The digest algorithm is set to SHA1 by default // and can be changed by calling SetDigestAlgorithm. diff --git a/sign_test.go b/sign_test.go index 0ba6324..65e544d 100644 --- a/sign_test.go +++ b/sign_test.go @@ -264,3 +264,67 @@ func fromHex(s string) *big.Int { } return result } + +func TestCopy(t *testing.T) { + content := []byte("Hello World") + rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, x509.SHA256WithRSA, true) + if err != nil { + t.Fatalf("cannot generate root cert: %s", err) + } + truststore := x509.NewCertPool() + truststore.AddCert(rootCert.Certificate) + signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", rootCert, x509.SHA256WithRSA, false) + if err != nil { + t.Fatalf("cannot generate signer cert: %s", err) + } + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("cannot initialize signed data: %s", err) + } + + // Set the digest to match the end entity cert + signerDigest, _ := getDigestOIDForSignatureAlgorithm(signerCert.Certificate.SignatureAlgorithm) + toBeSigned.SetDigestAlgorithm(signerDigest) + + if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, nil, SignerInfoConfig{}); err != nil { + t.Fatalf("cannot add signer: %s", err) + } + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("cannot finish signing data: %s", err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) + + p7, err := Parse(signed) + if err != nil { + t.Fatalf("cannot parse signed data: %s", err) + } + testOid := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7} + testValue := "TestValue" + copied, err := Copy(p7, Attribute{ + Type: testOid, + Value: testValue, + }) + if err != nil { + t.Fatalf("cannot copy signed data: %s", err) + } + p7, err = Parse(copied) + if err != nil { + t.Fatalf("cannot parse copied data: %s", err) + } + + if !bytes.Equal(content, p7.Content) { + t.Errorf("content was not found in the copied data:\n\tExpected: %s\n\tActual: %s", content, p7.Content) + } + if err := p7.VerifyWithChain(truststore); err != nil { + t.Errorf("cannot verify copied data: %s", err) + } + + var copiedValue string + if err := p7.UnmarshalUnsignedAttribute(testOid, &copiedValue); err != nil { + t.Fatalf("could not unmarshal attribute: %s", err) + } + if copiedValue != testValue { + t.Errorf("incorrect attribute value: %s", copiedValue) + } +} diff --git a/verify.go b/verify.go index 2c6f32c..4fdef78 100644 --- a/verify.go +++ b/verify.go @@ -189,6 +189,19 @@ func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, o return unmarshalAttribute(attributes, attributeType, out) } +// UnmarshalUnsignedAttribute decodes a single attribute from the signer info +func (p7 *PKCS7) UnmarshalUnsignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { + sd, ok := p7.raw.(signedData) + if !ok { + return errors.New("pkcs7: payload is not signedData content") + } + if len(sd.SignerInfos) < 1 { + return errors.New("pkcs7: payload has no signers") + } + attributes := sd.SignerInfos[0].UnauthenticatedAttributes + return unmarshalAttribute(attributes, attributeType, out) +} + func parseSignedData(data []byte) (*PKCS7, error) { var sd signedData asn1.Unmarshal(data, &sd)