diff --git a/marshal.go b/marshal.go new file mode 100644 index 0000000..e94339b --- /dev/null +++ b/marshal.go @@ -0,0 +1,25 @@ +package pkcs7 + +import "encoding/asn1" + +func (p7 *PKCS7) Marshal() ([]byte, error) { + var contentType asn1.ObjectIdentifier + switch p7.raw.(type) { + case signedData: + contentType = OIDSignedData + case envelopedData: + contentType = OIDEnvelopedData + case encryptedData: + contentType = OIDEncryptedData + default: + return nil, ErrUnsupportedContentType + } + inner, err := asn1.Marshal(p7.raw) + if err != nil { + return nil, err + } + return asn1.Marshal(contentInfo{ + ContentType: contentType, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, + }) +} diff --git a/marshal_test.go b/marshal_test.go new file mode 100644 index 0000000..204b450 --- /dev/null +++ b/marshal_test.go @@ -0,0 +1,60 @@ +package pkcs7 + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "os" + "testing" +) + +func TestPKCS7_Marshal(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) + } + + marshaled, err := p7.Marshal() + if err != nil { + t.Fatalf("cannot marshal signed data: %s", err) + } + p7Reparsed, err := Parse(marshaled) + if err != nil { + t.Fatalf("cannot reparse signed data: %s", err) + } + if !bytes.Equal(p7.Content, p7Reparsed.Content) { + t.Errorf("content was not found in the reparsed data:\n\tExpected: %s\n\tActual: %s", p7.Content, p7Reparsed.Content) + } + if err := p7Reparsed.VerifyWithChain(truststore); err != nil { + t.Errorf("cannot verify reparsed data: %s", err) + } +} diff --git a/sign.go b/sign.go index 31c3654..8ecc18d 100644 --- a/sign.go +++ b/sign.go @@ -23,6 +23,34 @@ type SignedData struct { encryptionOid asn1.ObjectIdentifier } +// CopyWithUnsignedAttributes creates a copy of the PKCS7 struct with different unsigned attributes. +func (p7 *PKCS7) CopyWithUnsignedAttributes(unauthenticatedAttrs ...Attribute) (*PKCS7, error) { + rawData, isSignedData := p7.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 + } + } + certs, err := rawData.Certificates.Parse() + if err != nil { + return nil, err + } + return &PKCS7{ + Content: p7.Content, + Certificates: certs, + CRLs: rawData.CRLs, + Signers: rawData.SignerInfos, + raw: rawData, + }, nil +} + // 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..ee1a2c4 100644 --- a/sign_test.go +++ b/sign_test.go @@ -264,3 +264,63 @@ func fromHex(s string) *big.Int { } return result } + +func TestCopyWithUnsignedAttributes(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" + p7, err = p7.CopyWithUnsignedAttributes(Attribute{ + Type: testOid, + Value: testValue, + }) + if err != nil { + t.Fatalf("cannot copy signed 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..e1fed88 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) @@ -337,6 +350,14 @@ func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndS return nil } +type AttributeNotFoundError struct { + AttributeType asn1.ObjectIdentifier +} + +func (a AttributeNotFoundError) Error() string { + return fmt.Sprintf("pkcs7: attribute %s does not exist", a.AttributeType) +} + func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { for _, attr := range attrs { if attr.Type.Equal(attributeType) { @@ -344,5 +365,5 @@ func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, return err } } - return errors.New("pkcs7: attribute type not in attributes") + return AttributeNotFoundError{AttributeType: attributeType} }