Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: more attribute support #22

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions marshal.go
Original file line number Diff line number Diff line change
@@ -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},
})
}
60 changes: 60 additions & 0 deletions marshal_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
28 changes: 28 additions & 0 deletions sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
60 changes: 60 additions & 0 deletions sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
23 changes: 22 additions & 1 deletion verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -337,12 +350,20 @@ 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) {
_, err := asn1.Unmarshal(attr.Value.Bytes, out)
return err
}
}
return errors.New("pkcs7: attribute type not in attributes")
return AttributeNotFoundError{AttributeType: attributeType}
}