Skip to content
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
39 changes: 21 additions & 18 deletions pkcs7/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,57 +25,60 @@ var (
oidPKCS1RSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
)

type AlgorithmIdentifier struct {
type algorithmIdentifier struct {
Algorithm asn1.ObjectIdentifier
Parameters asn1.RawValue
}

type IssuerAndSerialNumber struct {
type issuerAndSerialNumber struct {
Issuer asn1.RawValue
SerialNumber *big.Int
}

type SignerInfo struct {
type signerInfo struct {
Version int
SignedIdentifier IssuerAndSerialNumber
DigestAlgorithm AlgorithmIdentifier
AuthenticatedAttributes Attributes `asn1:"tag:0"`
DigestEncryptionAlgorithm AlgorithmIdentifier
SignedIdentifier issuerAndSerialNumber
DigestAlgorithm algorithmIdentifier
AuthenticatedAttributes []attribute `asn1:"tag:0"`
DigestEncryptionAlgorithm algorithmIdentifier
EncryptedDigest []byte
UnauthenticatedAttributes int `asn1:"optional"`
}

type ContentInfo struct {
type contentInfo struct {
ContentType asn1.ObjectIdentifier
Content asn1.RawValue `asn1:"optional"`
}

type SignedData struct {
type signedData struct {
Version int
DigestAlgorithms []AlgorithmIdentifier `asn1:"set"`
ContentInfo ContentInfo
DigestAlgorithms []algorithmIdentifier `asn1:"set"`
ContentInfo contentInfo
Certificates asn1.RawValue `asn1:"optional"`
Crls asn1.RawValue `asn1:"optional"`
SignerInfos []SignerInfo `asn1:"set"`
SignerInfos []signerInfo `asn1:"set"`
}

type SignedDataWrapper struct {
type signedDataWrapper struct {
Oid asn1.ObjectIdentifier
SignedData asn1.RawValue
}

//

type Attribute struct {
type attribute struct {
Type asn1.ObjectIdentifier
Values []interface{} `asn1:"set"`
}

type Attributes []Attribute

func NewAttribute(typ asn1.ObjectIdentifier, val interface{}) Attribute {
func newAttribute(typ asn1.ObjectIdentifier, val interface{}) attribute {
if t, ok := val.(time.Time); ok {
val = asn1.RawValue{Tag: 23, Bytes: []byte(t.Format("060102150405Z"))}
}
return Attribute{Type: typ, Values: []interface{}{val}}
return attribute{
Type: typ,
Values: []interface{}{
val,
},
}
}
44 changes: 44 additions & 0 deletions pkcs7/hashable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package pkcs7

import (
"crypto/sha256"
"io"
)

type Hashable interface {
Sha256() ([]byte, error)
}

type hashableReader struct {
r io.Reader
}

func (h *hashableReader) Sha256() ([]byte, error) {
hash := sha256.New()

if _, err := io.Copy(hash, h.r); err != nil {
return nil, err
}

return hash.Sum(nil), nil
}

// Creates a new Hashable from io.Reader
func NewHashableReader(r io.Reader) Hashable {
return &hashableReader{r: r}
}

type hashableBytes struct {
b []byte
}

func (h *hashableBytes) Sha256() ([]byte, error) {
hash := sha256.New()
hash.Write(h.b)
return hash.Sum(nil), nil
}

// Creates a new Hashable from bytes
func NewHashableBytes(b []byte) Hashable {
return &hashableBytes{b: b}
}
105 changes: 105 additions & 0 deletions pkcs7/hashable_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package pkcs7

import (
"bytes"
"os"
"testing"
)

func Test_NewHashableReader(t *testing.T) {
cases := []struct {
FileName string
Hash []byte
Error string
}{
{
FileName: "testdata/test1.txt",
Hash: []byte{
112, 80, 230, 94, 235, 41, 94, 59,
18, 172, 134, 98, 53, 154, 178, 111,
36, 89, 195, 198, 55, 200, 95, 36,
193, 157, 229, 137, 123, 224, 99, 221,
},
Error: "",
},
}

for _, c := range cases {
f, err := os.Open(c.FileName)
if err != nil {
t.Errorf("%v", err.Error())
continue
}
defer f.Close()

hashable := NewHashableReader(f)
if hashable == nil {
t.Errorf("Got nil from NewHashableReader(\"%v\")")
continue
}

hash, err := hashable.Sha256()
if c.Error != "" {
if err != nil && err.Error() == c.Error {
continue
}

t.Errorf("Expected Error %v, found %v", c.Error, err)
continue
}

if l := len(hash); l != 32 {
t.Errorf("len(hash) must be 32, found %v", l)
continue
}

if !bytes.Equal(hash, c.Hash) {
t.Errorf("Expected hash %v for file %v, found %v", c.Hash, c.FileName, hash)
}
}
}

func Test_NewHashableBytes(t *testing.T) {
cases := []struct {
Data []byte
Hash []byte
Error string
}{
{
Data: []byte("Hello World!"),
Hash: []byte{
127, 131, 177, 101, 127, 241, 252, 83, 185, 45,
193, 129, 72, 161, 214, 93, 252, 45, 75, 31, 163,
214, 119, 40, 74, 221, 210, 0, 18, 109, 144, 105,
},
Error: "",
},
}

for i, c := range cases {
hashable := NewHashableBytes(c.Data)
if hashable == nil {
t.Errorf("Got nil from NewHashableReader(\"%v\")")
continue
}

hash, err := hashable.Sha256()
if c.Error != "" {
if err != nil && err.Error() == c.Error {
continue
}

t.Errorf("Expected Error %v, found %v", c.Error, err)
continue
}

if l := len(hash); l != 32 {
t.Errorf("len(hash) must be 32, found %v", l)
continue
}

if !bytes.Equal(hash, c.Hash) {
t.Errorf("Expected hash %v for test %v, found %v", c.Hash, i, hash)
}
}
}
118 changes: 88 additions & 30 deletions pkcs7/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,103 @@ import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"errors"
"io"
"time"
)

func Sign(r io.Reader, cert *x509.Certificate, priv *rsa.PrivateKey) ([]byte, error) {
hash := sha256.New()
if _, err := io.Copy(hash, r); err != nil {
// Create a signature from io.Reader
// Returns the signature and any error encountered.
func Sign(reader io.Reader, certificate *x509.Certificate, privateKey *rsa.PrivateKey) ([]byte, error) {
hashable := NewHashableReader(reader)
return SignDataIntermediate(hashable, certificate, privateKey, nil)
}

// Create a signature from io.Reader including intermediate certificates.
// Returns the signature and any error encountered.
func SignIntermediate(reader io.Reader, certificate *x509.Certificate, privateKey *rsa.PrivateKey, intermediateCertificates []*x509.Certificate) ([]byte, error) {
hashable := NewHashableReader(reader)
return SignDataIntermediate(hashable, certificate, privateKey, intermediateCertificates)
}

// Creates a signature
// Returns the signature and any error encountered.
func SignData(hashable Hashable, certificate *x509.Certificate, privateKey *rsa.PrivateKey) ([]byte, error) {
return SignDataIntermediate(hashable, certificate, privateKey, nil)
}

// Create a signature including intermediate certificates.
// Returns the signature and any error encountered.
func SignDataIntermediate(hashable Hashable, certificate *x509.Certificate, privateKey *rsa.PrivateKey, intermediateCertificates []*x509.Certificate) ([]byte, error) {
// Check if parameters are valid
if certificate == nil {
return nil, errors.New("\"certificate\" cannot be nil.")
}

if privateKey == nil {
return nil, errors.New("\"privateKey\" cannot be nil.")
}

messageDigest, err := hashable.Sha256()
if err != nil {
return nil, err
}
messageDigest := hash.Sum(nil)

signedData := SignedData{
// Copy intermediateCertificates to certificate stack
raw := certificate.Raw
for _, intermediate := range intermediateCertificates {
if intermediate != nil {
raw = append(raw, intermediate.Raw...)
}
}

signedData := signedData{
Version: 1,
DigestAlgorithms: []AlgorithmIdentifier{
AlgorithmIdentifier{Algorithm: oidSHA256, Parameters: asn1.RawValue{Tag: 5}},
DigestAlgorithms: []algorithmIdentifier{
{
Algorithm: oidSHA256,
Parameters: asn1.RawValue{
Tag: 5,
},
},
},
ContentInfo: ContentInfo{
ContentInfo: contentInfo{
ContentType: oidPKCS7Data,
},
Certificates: asn1.RawValue{Class: 2, Tag: 0, Bytes: cert.Raw, IsCompound: true},
SignerInfos: []SignerInfo{
SignerInfo{
Certificates: asn1.RawValue{
Class: 2,
Tag: 0,
Bytes: raw,
IsCompound: true,
},
SignerInfos: []signerInfo{
{
Version: 1,
SignedIdentifier: IssuerAndSerialNumber{
Issuer: asn1.RawValue{FullBytes: cert.RawIssuer},
SerialNumber: cert.SerialNumber,
SignedIdentifier: issuerAndSerialNumber{
Issuer: asn1.RawValue{
FullBytes: certificate.RawIssuer,
},
SerialNumber: certificate.SerialNumber,
},
DigestAlgorithm: algorithmIdentifier{
Algorithm: oidSHA256,
Parameters: asn1.RawValue{
Tag: 5,
},
},
DigestAlgorithm: AlgorithmIdentifier{Algorithm: oidSHA256, Parameters: asn1.RawValue{Tag: 5}},
AuthenticatedAttributes: Attributes{
NewAttribute(oidPKCS9ContentType, oidPKCS7Data),
NewAttribute(oidPKCS9SigningTime, time.Now().UTC()),
NewAttribute(oidPKCS9MessageDigest, messageDigest),
AuthenticatedAttributes: []attribute{
newAttribute(oidPKCS9ContentType, oidPKCS7Data),
newAttribute(oidPKCS9SigningTime, time.Now().UTC()),
newAttribute(oidPKCS9MessageDigest, messageDigest),
},
DigestEncryptionAlgorithm: algorithmIdentifier{
Algorithm: oidPKCS1RSAEncryption,
Parameters: asn1.RawValue{
Tag: 5,
},
},
DigestEncryptionAlgorithm: AlgorithmIdentifier{Algorithm: oidPKCS1RSAEncryption, Parameters: asn1.RawValue{Tag: 5}},
EncryptedDigest: nil, // We fill this in later
UnauthenticatedAttributes: 0,
},
Expand All @@ -56,20 +116,18 @@ func Sign(r io.Reader, cert *x509.Certificate, priv *rsa.PrivateKey) ([]byte, er
return nil, err
}

// For the digest of the authenticated attributes, we need a
// slightly different encoding. Change the attributes from a
// SEQUENCE to a SET.

originalFirstByte := encodedAuthenticatedAttributes[0]
encodedAuthenticatedAttributes[0] = 0x31

hash = sha256.New()
hash.Write(encodedAuthenticatedAttributes)
attributesDigest := hash.Sum(nil)
digest := NewHashableBytes(encodedAuthenticatedAttributes)
attributesDigest, err := digest.Sha256()
if err != nil {
return nil, err
}

encodedAuthenticatedAttributes[0] = originalFirstByte

encryptedDigest, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, attributesDigest)
encryptedDigest, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, attributesDigest)
if err != nil {
return nil, err
}
Expand All @@ -80,7 +138,7 @@ func Sign(r io.Reader, cert *x509.Certificate, priv *rsa.PrivateKey) ([]byte, er
return nil, err
}

signedDataWrapper := SignedDataWrapper{
signedDataWrapper := signedDataWrapper{
Oid: oidPKCS7SignedData,
SignedData: asn1.RawValue{Class: 2, Tag: 0, Bytes: encodedSignedData, IsCompound: true},
}
Expand Down
Loading