Added cms/protocol which is a fork of https://github.com/mastahyeti/cms/tree/master/protocol with support for encryption.
This commit is contained in:
parent
9179582f90
commit
ca99f63569
25
cms/protocol/asn1.go
Normal file
25
cms/protocol/asn1.go
Normal file
@ -0,0 +1,25 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
|
||||
asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1"
|
||||
)
|
||||
|
||||
// RawValue marshals val and returns the asn1.RawValue
|
||||
func RawValue(val interface{}, params ...string) (rv asn1.RawValue, err error) {
|
||||
param := ""
|
||||
if len(params) > 0 {
|
||||
param = params[0]
|
||||
}
|
||||
|
||||
var der []byte
|
||||
if der, err = asn.MarshalWithParams(val, param); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = asn.Unmarshal(der, &rv); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
74
cms/protocol/attribute.go
Normal file
74
cms/protocol/attribute.go
Normal file
@ -0,0 +1,74 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
)
|
||||
|
||||
// Attribute ::= SEQUENCE {
|
||||
// attrType OBJECT IDENTIFIER,
|
||||
// attrValues SET OF AttributeValue }
|
||||
//
|
||||
// AttributeValue ::= ANY
|
||||
type Attribute struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
|
||||
// This should be a SET OF ANY, but Go's asn1 parser can't handle slices of
|
||||
// RawValues. Use value() to get an AnySet of the value.
|
||||
RawValue []asn1.RawValue `asn1:"set"`
|
||||
}
|
||||
|
||||
// NewAttribute creates a single-value Attribute.
|
||||
func NewAttribute(attrType asn1.ObjectIdentifier, val interface{}) (attr Attribute, err error) {
|
||||
var rv asn1.RawValue
|
||||
if rv, err = RawValue(val); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
attr = Attribute{attrType, []asn1.RawValue{rv}}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Attributes is a common Go type for SignedAttributes and UnsignedAttributes.
|
||||
//
|
||||
// SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
//
|
||||
// UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
type Attributes []Attribute
|
||||
|
||||
// GetOnlyAttributeValueBytes gets an attribute value, returning an error if the
|
||||
// attribute occurs multiple times or has multiple values.
|
||||
func (attrs Attributes) GetOnlyAttributeValueBytes(oid asn1.ObjectIdentifier) (rv asn1.RawValue, err error) {
|
||||
var vals [][]asn1.RawValue
|
||||
if vals, err = attrs.GetValues(oid); err != nil {
|
||||
return
|
||||
}
|
||||
if len(vals) != 1 {
|
||||
err = ASN1Error{"bad attribute count"}
|
||||
return
|
||||
}
|
||||
if len(vals[0]) != 1 {
|
||||
err = ASN1Error{"bad attribute element count"}
|
||||
return
|
||||
}
|
||||
|
||||
return vals[0][0], nil
|
||||
}
|
||||
|
||||
// GetValues retreives the attributes with the given OID. A nil value is
|
||||
// returned if the OPTIONAL SET of Attributes is missing from the SignerInfo. An
|
||||
// empty slice is returned if the specified attribute isn't in the set.
|
||||
func (attrs Attributes) GetValues(oid asn1.ObjectIdentifier) ([][]asn1.RawValue, error) {
|
||||
if attrs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
vals := [][]asn1.RawValue{}
|
||||
for _, attr := range attrs {
|
||||
if attr.Type.Equal(oid) {
|
||||
vals = append(vals, attr.RawValue)
|
||||
}
|
||||
}
|
||||
|
||||
return vals, nil
|
||||
}
|
145
cms/protocol/authenvdata.go
Normal file
145
cms/protocol/authenvdata.go
Normal file
@ -0,0 +1,145 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/asn1"
|
||||
"log"
|
||||
|
||||
asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1"
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
//AuthEnvelopedData ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
|
||||
// recipientInfos RecipientInfos,
|
||||
// authEncryptedContentInfo EncryptedContentInfo,
|
||||
/// authAttrs [1] IMPLICIT AuthAttributes OPTIONAL,
|
||||
// mac MessageAuthenticationCode,
|
||||
// unauthAttrs [2] IMPLICIT UnauthAttributes OPTIONAL }
|
||||
//https://tools.ietf.org/html/rfc5083##section-2.1
|
||||
type AuthEnvelopedData struct {
|
||||
Version int
|
||||
OriginatorInfo asn1.RawValue `asn1:"optional,tag:0"`
|
||||
RecipientInfos []RecipientInfo `asn1:"set,choice"`
|
||||
AECI EncryptedContentInfo
|
||||
AauthAttrs []Attribute `asn1:"set,optional,tag:1"`
|
||||
MAC []byte
|
||||
UnAauthAttrs []Attribute `asn1:"set,optional,tag:2"`
|
||||
}
|
||||
|
||||
// Decrypt decrypts AuthEnvelopedData and returns the plaintext.
|
||||
func (ed *AuthEnvelopedData) Decrypt(keyPair []tls.Certificate) (plain []byte, err error) {
|
||||
|
||||
// Find the right key
|
||||
var key []byte
|
||||
for i := range keyPair {
|
||||
key, err = ed.decryptKey(keyPair[i])
|
||||
switch err {
|
||||
case ErrNoKeyFound:
|
||||
continue
|
||||
case nil:
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
encAlg := &oid.EncryptionAlgorithm{
|
||||
Key: key,
|
||||
ContentEncryptionAlgorithmIdentifier: ed.AECI.ContentEncryptionAlgorithm,
|
||||
}
|
||||
encAlg.MAC = ed.MAC
|
||||
|
||||
plain, err = encAlg.Decrypt(ed.AECI.EContent)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ed *AuthEnvelopedData) decryptKey(keyPair tls.Certificate) (key []byte, err error) {
|
||||
|
||||
for i := range ed.RecipientInfos {
|
||||
|
||||
key, err = ed.RecipientInfos[i].decryptKey(keyPair)
|
||||
if key != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil, ErrNoKeyFound
|
||||
}
|
||||
|
||||
// NewAuthEnvelopedData creates AuthEnvelopedData from an EncryptedContentInfo with mac and given RecipientInfos.
|
||||
func NewAuthEnvelopedData(eci *EncryptedContentInfo, reciInfos []RecipientInfo, mac []byte) AuthEnvelopedData {
|
||||
version := 0
|
||||
|
||||
ed := AuthEnvelopedData{
|
||||
Version: version,
|
||||
RecipientInfos: reciInfos,
|
||||
AECI: *eci,
|
||||
MAC: mac,
|
||||
}
|
||||
|
||||
return ed
|
||||
}
|
||||
|
||||
func authcontentInfo(ed AuthEnvelopedData) (ci ContentInfo, err error) {
|
||||
|
||||
der, err := asn.Marshal(ed)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ci = ContentInfo{
|
||||
ContentType: oid.AuthEnvelopedData,
|
||||
Content: asn1.RawValue{
|
||||
Class: asn1.ClassContextSpecific,
|
||||
Tag: 0,
|
||||
Bytes: der,
|
||||
IsCompound: true,
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ContentInfo marshals AuthEnvelopedData and returns ContentInfo.
|
||||
func (ed AuthEnvelopedData) ContentInfo() (ContentInfo, error) {
|
||||
nilCI := *new(ContentInfo)
|
||||
|
||||
der, err := asn.Marshal(ed)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nilCI, err
|
||||
}
|
||||
|
||||
return ContentInfo{
|
||||
ContentType: oid.AuthEnvelopedData,
|
||||
Content: asn1.RawValue{
|
||||
Class: asn1.ClassContextSpecific,
|
||||
Tag: 0,
|
||||
Bytes: der,
|
||||
IsCompound: true,
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// AuthEnvelopedDataContent unmarshals ContentInfo and returns AuthEnvelopedData if
|
||||
// content type is AuthEnvelopedData.
|
||||
func (ci ContentInfo) AuthEnvelopedDataContent() (*AuthEnvelopedData, error) {
|
||||
if !ci.ContentType.Equal(oid.AuthEnvelopedData) {
|
||||
return nil, ErrWrongType
|
||||
}
|
||||
|
||||
ed := new(AuthEnvelopedData)
|
||||
if rest, err := asn.Unmarshal(ci.Content.Bytes, ed); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) > 0 {
|
||||
return nil, ErrTrailingData
|
||||
}
|
||||
|
||||
return ed, nil
|
||||
}
|
55
cms/protocol/contentinfo.go
Normal file
55
cms/protocol/contentinfo.go
Normal file
@ -0,0 +1,55 @@
|
||||
// Package protocol implemets parts of cryptographic message syntax RFC 5652.
|
||||
// This package is mostly for handling of the asn1 sturctures of cms. For
|
||||
// de/encryption and signing/verfiying use to package cms.
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
|
||||
asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1"
|
||||
"github.com/InfiniteLoopSpace/go_S-MIME/b64"
|
||||
)
|
||||
|
||||
// ContentInfo ::= SEQUENCE {
|
||||
// contentType ContentType,
|
||||
// content [0] EXPLICIT ANY DEFINED BY contentType }
|
||||
//
|
||||
// ContentType ::= OBJECT IDENTIFIER
|
||||
type ContentInfo struct {
|
||||
ContentType asn1.ObjectIdentifier
|
||||
Content asn1.RawValue `asn1:"explicit,tag:0"`
|
||||
}
|
||||
|
||||
// ParseContentInfo parses DER-encoded ASN.1 data and returns ContentInfo.
|
||||
func ParseContentInfo(der []byte) (ci ContentInfo, err error) {
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var rest []byte
|
||||
if rest, err = asn.Unmarshal(der, &ci); err != nil {
|
||||
return
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
fmt.Println(ErrTrailingData)
|
||||
//err = ErrTrailingData
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DER returns the DER-encoded ASN.1 data.
|
||||
func (ci ContentInfo) DER() ([]byte, error) {
|
||||
return asn.Marshal(ci)
|
||||
}
|
||||
|
||||
// Base64 encodes the DER-encoded ASN.1 data in base64 for use in S/MIME.
|
||||
func (ci ContentInfo) Base64() ([]byte, error) {
|
||||
der, err := ci.DER()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b64.EncodeBase64(der)
|
||||
}
|
40
cms/protocol/eci.go
Normal file
40
cms/protocol/eci.go
Normal file
@ -0,0 +1,40 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
//EncryptedContentInfo ::= SEQUENCE {
|
||||
// contentType ContentType,
|
||||
// contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
|
||||
// encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
|
||||
type EncryptedContentInfo struct {
|
||||
EContentType asn1.ObjectIdentifier
|
||||
ContentEncryptionAlgorithm pkix.AlgorithmIdentifier
|
||||
EContent []byte `asn1:"optional,implicit,tag:0"`
|
||||
}
|
||||
|
||||
// NewEncryptedContentInfo encrypts the conent with the contentEncryptionAlgorithm and retuns
|
||||
// the EncryptedContentInfo, the key and the MAC.
|
||||
func NewEncryptedContentInfo(contentType asn1.ObjectIdentifier, contentEncryptionAlg asn1.ObjectIdentifier, content []byte) (eci EncryptedContentInfo, key, mac []byte, err error) {
|
||||
|
||||
encAlg := &oid.EncryptionAlgorithm{
|
||||
EncryptionAlgorithmIdentifier: contentEncryptionAlg,
|
||||
}
|
||||
|
||||
ciphertext, err := encAlg.Encrypt(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
eci = EncryptedContentInfo{
|
||||
EContentType: contentType,
|
||||
ContentEncryptionAlgorithm: encAlg.ContentEncryptionAlgorithmIdentifier,
|
||||
EContent: ciphertext,
|
||||
}
|
||||
|
||||
return eci, encAlg.Key, encAlg.MAC, nil
|
||||
}
|
34
cms/protocol/enci.go
Normal file
34
cms/protocol/enci.go
Normal file
@ -0,0 +1,34 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
// EncapsulatedContentInfo ::= SEQUENCE {
|
||||
// eContentType ContentType,
|
||||
// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
|
||||
type EncapsulatedContentInfo struct {
|
||||
EContentType asn1.ObjectIdentifier `` // ContentType ::= OBJECT IDENTIFIER
|
||||
EContent []byte `asn1:"optional,explicit,tag:0"` //
|
||||
}
|
||||
|
||||
// NewDataEncapsulatedContentInfo creates a new EncapsulatedContentInfo of type
|
||||
// id-data.
|
||||
func NewDataEncapsulatedContentInfo(data []byte) (EncapsulatedContentInfo, error) {
|
||||
return NewEncapsulatedContentInfo(oid.Data, data)
|
||||
}
|
||||
|
||||
// NewEncapsulatedContentInfo creates a new EncapsulatedContentInfo.
|
||||
func NewEncapsulatedContentInfo(contentType asn1.ObjectIdentifier, content []byte) (EncapsulatedContentInfo, error) {
|
||||
return EncapsulatedContentInfo{
|
||||
EContentType: contentType,
|
||||
EContent: content,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsTypeData checks if the EContentType is id-data.
|
||||
func (eci EncapsulatedContentInfo) IsTypeData() bool {
|
||||
return eci.EContentType.Equal(oid.Data)
|
||||
}
|
121
cms/protocol/envelopeddata.go
Normal file
121
cms/protocol/envelopeddata.go
Normal file
@ -0,0 +1,121 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/asn1"
|
||||
"log"
|
||||
|
||||
asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1"
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
//EnvelopedData ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
|
||||
// recipientInfos RecipientInfos,
|
||||
// encryptedContentInfo EncryptedContentInfo,
|
||||
// unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
|
||||
type EnvelopedData struct {
|
||||
Version int
|
||||
OriginatorInfo asn1.RawValue `asn1:"optional,tag:0"`
|
||||
RecipientInfos []RecipientInfo `asn1:"set,choice"`
|
||||
ECI EncryptedContentInfo ``
|
||||
UnprotectedAttrs []Attribute `asn1:"set,optional,tag:1"`
|
||||
}
|
||||
|
||||
// Decrypt decrypts the EnvelopedData with the given keyPair and retuns the plaintext.
|
||||
func (ed *EnvelopedData) Decrypt(keyPairs []tls.Certificate) (plain []byte, err error) {
|
||||
|
||||
// Find the right key
|
||||
var key []byte
|
||||
for i := range keyPairs {
|
||||
key, err = ed.decryptKey(keyPairs[i])
|
||||
switch err {
|
||||
case ErrNoKeyFound:
|
||||
continue
|
||||
case nil:
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
if key == nil {
|
||||
return nil, ErrNoKeyFound
|
||||
}
|
||||
|
||||
encAlg := &oid.EncryptionAlgorithm{
|
||||
Key: key,
|
||||
ContentEncryptionAlgorithmIdentifier: ed.ECI.ContentEncryptionAlgorithm,
|
||||
}
|
||||
|
||||
plain, err = encAlg.Decrypt(ed.ECI.EContent)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ed *EnvelopedData) decryptKey(keyPair tls.Certificate) (key []byte, err error) {
|
||||
|
||||
for i := range ed.RecipientInfos {
|
||||
|
||||
key, err = ed.RecipientInfos[i].decryptKey(keyPair)
|
||||
if key != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil, ErrNoKeyFound
|
||||
}
|
||||
|
||||
// EnvelopedDataContent returns EnvelopedData if ContentType is EnvelopedData.
|
||||
func (ci ContentInfo) EnvelopedDataContent() (*EnvelopedData, error) {
|
||||
if !ci.ContentType.Equal(oid.EnvelopedData) {
|
||||
return nil, ErrWrongType
|
||||
}
|
||||
|
||||
//var Ed interface{}
|
||||
ed := new(EnvelopedData)
|
||||
if rest, err := asn.Unmarshal(ci.Content.Bytes, ed); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) > 0 {
|
||||
return nil, ErrTrailingData
|
||||
}
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
// ContentInfo returns new ContentInfo with ContentType EnvelopedData.
|
||||
func (ed EnvelopedData) ContentInfo() (ContentInfo, error) {
|
||||
nilCI := *new(ContentInfo)
|
||||
|
||||
der, err := asn.Marshal(ed)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nilCI, err
|
||||
}
|
||||
|
||||
return ContentInfo{
|
||||
ContentType: oid.EnvelopedData,
|
||||
Content: asn1.RawValue{
|
||||
Class: asn1.ClassContextSpecific,
|
||||
Tag: 0,
|
||||
Bytes: der,
|
||||
IsCompound: true,
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// NewEnvelopedData creates a new EnvelopedData from the given data.
|
||||
func NewEnvelopedData(eci *EncryptedContentInfo, reciInfos []RecipientInfo) EnvelopedData {
|
||||
version := 0
|
||||
|
||||
ed := EnvelopedData{
|
||||
Version: version,
|
||||
RecipientInfos: reciInfos,
|
||||
ECI: *eci,
|
||||
}
|
||||
|
||||
return ed
|
||||
}
|
39
cms/protocol/error.go
Normal file
39
cms/protocol/error.go
Normal file
@ -0,0 +1,39 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ASN1Error is an error from parsing ASN.1 structures.
|
||||
type ASN1Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (err ASN1Error) Error() string {
|
||||
return fmt.Sprintf("cms/protocol: ASN.1 Error — %s", err.Message)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrWrongType is returned by methods that make assumptions about types.
|
||||
// Helper methods are defined for accessing CHOICE and ANY feilds. These
|
||||
// helper methods get the value of the field, assuming it is of a given type.
|
||||
// This error is returned if that assumption is wrong and the field has a
|
||||
// different type.
|
||||
ErrWrongType = errors.New("cms/protocol: wrong choice or any type")
|
||||
|
||||
// ErrNoCertificate is returned when a requested certificate cannot be found.
|
||||
ErrNoCertificate = errors.New("no certificate found")
|
||||
|
||||
// ErrNoKeyFound is returned when a requested certificate cannot be found.
|
||||
ErrNoKeyFound = errors.New("no key for decryption found")
|
||||
|
||||
// ErrUnsupported is returned when an unsupported type or version
|
||||
// is encountered.
|
||||
ErrUnsupported = ASN1Error{"unsupported type or version"}
|
||||
|
||||
// ErrTrailingData is returned when extra data is found after parsing an ASN.1
|
||||
// structure.
|
||||
ErrTrailingData = ASN1Error{"unexpected trailing data"}
|
||||
)
|
77
cms/protocol/issuerserialnumber.go
Normal file
77
cms/protocol/issuerserialnumber.go
Normal file
@ -0,0 +1,77 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// IssuerAndSerialNumber ::= SEQUENCE {
|
||||
// issuer Name,
|
||||
// serialNumber CertificateSerialNumber }
|
||||
//
|
||||
// CertificateSerialNumber ::= INTEGER
|
||||
type IssuerAndSerialNumber struct {
|
||||
Issuer asn1.RawValue
|
||||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
// NewIssuerAndSerialNumber creates a IssuerAndSerialNumber SID for the given
|
||||
// cert.
|
||||
func NewIssuerAndSerialNumber(cert *x509.Certificate) (sid IssuerAndSerialNumber, err error) {
|
||||
sid = IssuerAndSerialNumber{
|
||||
SerialNumber: new(big.Int).Set(cert.SerialNumber),
|
||||
}
|
||||
|
||||
if _, err = asn1.Unmarshal(cert.RawIssuer, &sid.Issuer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RawValue returns the RawValue of the IssuerAndSerialNumber.
|
||||
func (ias *IssuerAndSerialNumber) RawValue() (rv asn1.RawValue, err error) {
|
||||
var der []byte
|
||||
if der, err = asn1.Marshal(*ias); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = asn1.Unmarshal(der, &rv); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Equal returns true if ias and ias2 agree.
|
||||
func (ias *IssuerAndSerialNumber) Equal(ias2 IssuerAndSerialNumber) bool {
|
||||
|
||||
if bytes.Compare(ias.Issuer.Bytes, ias2.Issuer.Bytes) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if ias.SerialNumber.Cmp(ias2.SerialNumber) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IASstring retuns the ias of the cert as hex encoded string.
|
||||
func IASstring(cert *x509.Certificate) (iasString string, err error) {
|
||||
ias, err := NewIssuerAndSerialNumber(cert)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rv, err := ias.RawValue()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
iasString = fmt.Sprintf("%x", rv.Bytes)
|
||||
return
|
||||
}
|
212
cms/protocol/reciepientinfo.go
Normal file
212
cms/protocol/reciepientinfo.go
Normal file
@ -0,0 +1,212 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
//RecipientInfo ::= CHOICE {
|
||||
// ktri KeyTransRecipientInfo,
|
||||
// kari [1] KeyAgreeRecipientInfo,
|
||||
// kekri [2] KEKRecipientInfo,
|
||||
// pwri [3] PasswordRecipientInfo,
|
||||
// ori [4] OtherRecipientInfo }
|
||||
type RecipientInfo struct {
|
||||
KTRI KeyTransRecipientInfo `asn1:"optional"`
|
||||
KARI KeyAgreeRecipientInfo `asn1:"optional,tag:1"` //KeyAgreeRecipientInfo
|
||||
KEKRI asn1.RawValue `asn1:"optional,tag:2"`
|
||||
PWRI asn1.RawValue `asn1:"optional,tag:3"`
|
||||
ORI asn1.RawValue `asn1:"optional,tag:4"`
|
||||
}
|
||||
|
||||
func (recInfo *RecipientInfo) decryptKey(keyPair tls.Certificate) (key []byte, err error) {
|
||||
|
||||
return recInfo.KTRI.decryptKey(keyPair)
|
||||
|
||||
}
|
||||
|
||||
//KeyTransRecipientInfo ::= SEQUENCE {
|
||||
// version CMSVersion, -- always set to 0 or 2
|
||||
// rid RecipientIdentifier,
|
||||
// keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||||
// encryptedKey EncryptedKey }
|
||||
type KeyTransRecipientInfo struct {
|
||||
Version int
|
||||
Rid RecipientIdentifier `asn1:"choice"`
|
||||
KeyEncryptionAlgorithm pkix.AlgorithmIdentifier
|
||||
EncryptedKey []byte
|
||||
}
|
||||
|
||||
func (ktri *KeyTransRecipientInfo) decryptKey(keyPair tls.Certificate) (key []byte, err error) {
|
||||
|
||||
ias, err := NewIssuerAndSerialNumber(keyPair.Leaf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ski := keyPair.Leaf.SubjectKeyId
|
||||
|
||||
//version is the syntax version number. If the SignerIdentifier is
|
||||
//the CHOICE issuerAndSerialNumber, then the version MUST be 1. If
|
||||
//the SignerIdentifier is subjectKeyIdentifier, then the version
|
||||
//MUST be 3.
|
||||
switch ktri.Version {
|
||||
case 0:
|
||||
if ias.Equal(ktri.Rid.IAS) {
|
||||
alg := oid.PublicKeyAlgorithmToEncrytionAlgorithm[keyPair.Leaf.PublicKeyAlgorithm].Algorithm
|
||||
if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(alg) {
|
||||
|
||||
decrypter := keyPair.PrivateKey.(crypto.Decrypter)
|
||||
return decrypter.Decrypt(rand.Reader, ktri.EncryptedKey, nil)
|
||||
|
||||
}
|
||||
log.Println("Key encrytion algorithm not matching")
|
||||
}
|
||||
case 2:
|
||||
if bytes.Equal(ski, ktri.Rid.SKI) {
|
||||
alg := oid.PublicKeyAlgorithmToEncrytionAlgorithm[keyPair.Leaf.PublicKeyAlgorithm].Algorithm
|
||||
if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(alg) {
|
||||
if alg.Equal(oid.EncryptionAlgorithmRSA) {
|
||||
return rsa.DecryptPKCS1v15(rand.Reader, keyPair.PrivateKey.(*rsa.PrivateKey), ktri.EncryptedKey)
|
||||
}
|
||||
log.Println("Unsupported key encrytion algorithm")
|
||||
}
|
||||
log.Println("Key encrytion algorithm not matching")
|
||||
}
|
||||
default:
|
||||
fmt.Println(ktri.Version)
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
//RecipientIdentifier ::= CHOICE {
|
||||
// issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
// subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
||||
type RecipientIdentifier struct {
|
||||
IAS IssuerAndSerialNumber `asn1:"optional"`
|
||||
SKI []byte `asn1:"optional,tag:0"`
|
||||
}
|
||||
|
||||
// NewRecipientInfo creates RecipientInfo for giben recipient and key.
|
||||
func NewRecipientInfo(recipient *x509.Certificate, key []byte) RecipientInfo {
|
||||
version := 0 //issuerAndSerialNumber
|
||||
|
||||
rid := RecipientIdentifier{}
|
||||
|
||||
switch version {
|
||||
case 0:
|
||||
ias, err := NewIssuerAndSerialNumber(recipient)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rid.IAS = ias
|
||||
case 2:
|
||||
rid.SKI = recipient.SubjectKeyId
|
||||
}
|
||||
|
||||
kea := oid.PublicKeyAlgorithmToEncrytionAlgorithm[recipient.PublicKeyAlgorithm]
|
||||
if _, ok := oid.PublicKeyAlgorithmToEncrytionAlgorithm[recipient.PublicKeyAlgorithm]; !ok {
|
||||
log.Fatal("NewRecipientInfo: PublicKeyAlgorithm not supported")
|
||||
}
|
||||
|
||||
encrypted, _ := encryptKey(key, recipient)
|
||||
|
||||
info := RecipientInfo{
|
||||
KTRI: KeyTransRecipientInfo{
|
||||
Version: version,
|
||||
Rid: rid,
|
||||
KeyEncryptionAlgorithm: kea,
|
||||
EncryptedKey: encrypted,
|
||||
}}
|
||||
return info
|
||||
}
|
||||
|
||||
func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) {
|
||||
if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil {
|
||||
return rsa.EncryptPKCS1v15(rand.Reader, pub, key)
|
||||
}
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// ErrUnsupportedAlgorithm is returned if the algorithm is unsupported.
|
||||
var ErrUnsupportedAlgorithm = errors.New("cms: cannot decrypt data: unsupported algorithm")
|
||||
|
||||
//KeyAgreeRecipientInfo ::= SEQUENCE {
|
||||
// version CMSVersion, -- always set to 3
|
||||
// originator [0] EXPLICIT OriginatorIdentifierOrKey,
|
||||
// ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
|
||||
// keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||||
// recipientEncryptedKeys RecipientEncryptedKeys }
|
||||
type KeyAgreeRecipientInfo struct {
|
||||
Version int
|
||||
Originator OriginatorIdentifierOrKey `asn1:"explicit,choice,tag:0"`
|
||||
UKM []byte `asn1:"explicit,optional,tag:1"`
|
||||
KeyEncryptionAlgorithm pkix.AlgorithmIdentifier ``
|
||||
RecipientEncryptedKeys []RecipientEncryptedKey `asn1:"sequence"` //RecipientEncryptedKeys ::= SEQUENCE OF RecipientEncryptedKey
|
||||
}
|
||||
|
||||
//OriginatorIdentifierOrKey ::= CHOICE {
|
||||
// issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
// subjectKeyIdentifier [0] SubjectKeyIdentifier,
|
||||
// originatorKey [1] OriginatorPublicKey }
|
||||
type OriginatorIdentifierOrKey struct {
|
||||
IAS IssuerAndSerialNumber `asn1:"optional"`
|
||||
SKI []byte `asn1:"optional,tag:0"`
|
||||
OriginatorKey OriginatorPublicKey `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
//OriginatorPublicKey ::= SEQUENCE {
|
||||
// algorithm AlgorithmIdentifier,
|
||||
// publicKey BIT STRING
|
||||
type OriginatorPublicKey struct {
|
||||
Algorithm pkix.AlgorithmIdentifier
|
||||
PublicKey asn1.BitString
|
||||
}
|
||||
|
||||
//RecipientEncryptedKey ::= SEQUENCE {
|
||||
// rid KeyAgreeRecipientIdentifier,
|
||||
// encryptedKey EncryptedKey }
|
||||
type RecipientEncryptedKey struct {
|
||||
RID KeyAgreeRecipientIdentifier `asn1:"choice"`
|
||||
EncryptedKey []byte
|
||||
}
|
||||
|
||||
//KeyAgreeRecipientIdentifier ::= CHOICE {
|
||||
// issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
// rKeyId [0] IMPLICIT RecipientKeyIdentifier }
|
||||
type KeyAgreeRecipientIdentifier struct {
|
||||
IAS IssuerAndSerialNumber `asn1:"optional"`
|
||||
RKeyID RecipientKeyIdentifier `asn1:"optional,tag:0"`
|
||||
}
|
||||
|
||||
//RecipientKeyIdentifier ::= SEQUENCE {
|
||||
// subjectKeyIdentifier SubjectKeyIdentifier,
|
||||
// date GeneralizedTime OPTIONAL,
|
||||
// other OtherKeyAttribute OPTIONAL }
|
||||
type RecipientKeyIdentifier struct {
|
||||
SubjectKeyIdentifier []byte //SubjectKeyIdentifier ::= OCTET STRING
|
||||
Date time.Time `asn1:"optional"`
|
||||
Other OtherKeyAttribute `asn1:"optional"`
|
||||
}
|
||||
|
||||
//OtherKeyAttribute ::= SEQUENCE {
|
||||
// keyAttrId OBJECT IDENTIFIER,
|
||||
// keyAttr ANY DEFINED BY keyAttrId OPTIONAL }
|
||||
type OtherKeyAttribute struct {
|
||||
KeyAttrID asn1.ObjectIdentifier
|
||||
KeyAttr asn1.RawValue `asn1:"optional"`
|
||||
}
|
422
cms/protocol/signeddata.go
Normal file
422
cms/protocol/signeddata.go
Normal file
@ -0,0 +1,422 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1"
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
// SignedDataContent returns SignedData if ContentType is SignedData.
|
||||
func (ci ContentInfo) SignedDataContent() (*SignedData, error) {
|
||||
if !ci.ContentType.Equal(oid.SignedData) {
|
||||
return nil, ErrWrongType
|
||||
}
|
||||
|
||||
sd := new(SignedData)
|
||||
if rest, err := asn.Unmarshal(ci.Content.Bytes, sd); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) > 0 {
|
||||
return nil, ErrTrailingData
|
||||
}
|
||||
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
// SignedData ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// digestAlgorithms DigestAlgorithmIdentifiers,
|
||||
// encapContentInfo EncapsulatedContentInfo,
|
||||
// certificates [0] IMPLICIT CertificateSet OPTIONAL,
|
||||
// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
|
||||
// signerInfos SignerInfos }
|
||||
type SignedData struct {
|
||||
Version int `` // CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
|
||||
DigestAlgorithms []pkix.AlgorithmIdentifier `asn1:"set"` //DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier //DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
EncapContentInfo EncapsulatedContentInfo `` //
|
||||
Certificates []asn1.RawValue `asn1:"optional,set,tag:0"` // CertificateSet ::= SET OF CertificateChoices
|
||||
CRLs []RevocationInfoChoice `asn1:"optional,set,tag:1"` // RevocationInfoChoices ::= SET OF RevocationInfoChoice
|
||||
SignerInfos []SignerInfo `asn1:"set"` // SignerInfos ::= SET OF SignerInfo
|
||||
}
|
||||
|
||||
// CertificateChoices ::= CHOICE {
|
||||
// certificate Certificate,
|
||||
// extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
|
||||
// v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
|
||||
// v2AttrCert [2] IMPLICIT AttributeCertificateV2,
|
||||
// other [3] IMPLICIT OtherCertificateFormat }
|
||||
type CertificateChoices struct {
|
||||
Cert x509.Certificate `asn1:"optional"`
|
||||
V2AttrCert asn1.RawValue `asn1:"optional,tag:2"`
|
||||
Other OtherCertificateFormat `asn1:"optional,tag:3"`
|
||||
}
|
||||
|
||||
// OtherCertificateFormat ::= SEQUENCE {
|
||||
// otherCertFormat OBJECT IDENTIFIER,
|
||||
// otherCert ANY DEFINED BY otherCertFormat }
|
||||
type OtherCertificateFormat struct {
|
||||
OtherCertFormat asn1.ObjectIdentifier
|
||||
OtherCert asn1.RawValue
|
||||
}
|
||||
|
||||
// RevocationInfoChoice ::= CHOICE {
|
||||
// crl CertificateList,
|
||||
// other [1] IMPLICIT OtherRevocationInfoFormat }
|
||||
type RevocationInfoChoice struct {
|
||||
Crl pkix.CertificateList `asn1:"optional"`
|
||||
Other OtherRevocationInfoFormat `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
// OtherRevocationInfoFormat ::= SEQUENCE {
|
||||
// otherRevInfoFormat OBJECT IDENTIFIER,
|
||||
// otherRevInfo ANY DEFINED BY otherRevInfoFormat }
|
||||
type OtherRevocationInfoFormat struct {
|
||||
OtherRevInfoFormat asn1.ObjectIdentifier
|
||||
OtherRevInfo asn1.RawValue
|
||||
}
|
||||
|
||||
// NewSignedData creates a new SignedData.
|
||||
func NewSignedData(eci EncapsulatedContentInfo) (*SignedData, error) {
|
||||
// The version is picked based on which CMS features are used. We only use
|
||||
// version 1 features, except for supporting non-data econtent.
|
||||
version := 1
|
||||
if !eci.IsTypeData() {
|
||||
version = 3
|
||||
}
|
||||
|
||||
return &SignedData{
|
||||
Version: version,
|
||||
DigestAlgorithms: []pkix.AlgorithmIdentifier{},
|
||||
EncapContentInfo: eci,
|
||||
SignerInfos: []SignerInfo{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddSignerInfo adds a SignerInfo to the SignedData.
|
||||
func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate) (err error) {
|
||||
|
||||
for _, cert := range keypPair.Certificate {
|
||||
if err = sd.AddCertificate(cert); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
signer := keypPair.PrivateKey.(crypto.Signer)
|
||||
|
||||
cert := keypPair.Leaf
|
||||
|
||||
ias, err := NewIssuerAndSerialNumber(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sid := SignerIdentifier{ias, nil}
|
||||
|
||||
digestAlgorithm := digestAlgorithmForPublicKey(cert.PublicKey)
|
||||
signatureAlgorithm, ok := oid.PublicKeyAlgorithmToSignatureAlgorithm[keypPair.Leaf.PublicKeyAlgorithm]
|
||||
if !ok {
|
||||
return errors.New("unsupported certificate public key algorithm")
|
||||
}
|
||||
|
||||
si := SignerInfo{
|
||||
Version: 1,
|
||||
SID: sid,
|
||||
DigestAlgorithm: digestAlgorithm,
|
||||
SignedAttrs: nil,
|
||||
SignatureAlgorithm: signatureAlgorithm,
|
||||
Signature: nil,
|
||||
UnsignedAttrs: nil,
|
||||
}
|
||||
|
||||
// Get the message
|
||||
content := sd.EncapContentInfo.EContent
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if content == nil {
|
||||
return errors.New("already detached")
|
||||
}
|
||||
|
||||
// Digest the message.
|
||||
hash, err := si.Hash()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md := hash.New()
|
||||
if _, err = md.Write(content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build our SignedAttributes
|
||||
mdAttr, err := NewAttribute(oid.AttributeMessageDigest, md.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctAttr, err := NewAttribute(oid.AttributeContentType, sd.EncapContentInfo.EContentType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sTAttr, err := NewAttribute(oid.AttributeSigningTime, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
si.SignedAttrs = append(si.SignedAttrs, mdAttr, ctAttr, sTAttr)
|
||||
|
||||
sm, err := asn.MarshalWithParams(si.SignedAttrs, `set`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
smd := hash.New()
|
||||
if _, errr := smd.Write(sm); errr != nil {
|
||||
return errr
|
||||
}
|
||||
if si.Signature, err = signer.Sign(rand.Reader, smd.Sum(nil), hash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sd.addDigestAlgorithm(si.DigestAlgorithm)
|
||||
|
||||
sd.SignerInfos = append(sd.SignerInfos, si)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// algorithmsForPublicKey takes an opinionated stance on what algorithms to use
|
||||
// for the given public key.
|
||||
func digestAlgorithmForPublicKey(pub crypto.PublicKey) pkix.AlgorithmIdentifier {
|
||||
if ecPub, ok := pub.(*ecdsa.PublicKey); ok {
|
||||
switch ecPub.Curve {
|
||||
case elliptic.P384():
|
||||
return pkix.AlgorithmIdentifier{Algorithm: oid.DigestAlgorithmSHA384}
|
||||
case elliptic.P521():
|
||||
return pkix.AlgorithmIdentifier{Algorithm: oid.DigestAlgorithmSHA512}
|
||||
}
|
||||
}
|
||||
|
||||
return pkix.AlgorithmIdentifier{Algorithm: oid.DigestAlgorithmSHA256}
|
||||
}
|
||||
|
||||
// ClearCertificates removes all certificates.
|
||||
func (sd *SignedData) ClearCertificates() {
|
||||
sd.Certificates = []asn1.RawValue{}
|
||||
}
|
||||
|
||||
// AddCertificate adds a *x509.Certificate.
|
||||
func (sd *SignedData) AddCertificate(cert []byte) error {
|
||||
for _, existing := range sd.Certificates {
|
||||
if bytes.Equal(existing.Bytes, cert) {
|
||||
return errors.New("certificate already added")
|
||||
}
|
||||
}
|
||||
|
||||
var rv asn1.RawValue
|
||||
if _, err := asn.Unmarshal(cert, &rv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sd.Certificates = append(sd.Certificates, rv)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addDigestAlgorithm adds a new AlgorithmIdentifier if it doesn't exist yet.
|
||||
func (sd *SignedData) addDigestAlgorithm(algo pkix.AlgorithmIdentifier) {
|
||||
for _, existing := range sd.DigestAlgorithms {
|
||||
if existing.Algorithm.Equal(algo.Algorithm) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sd.DigestAlgorithms = append(sd.DigestAlgorithms, algo)
|
||||
}
|
||||
|
||||
// X509Certificates gets the certificates, assuming that they're X.509 encoded.
|
||||
func (sd *SignedData) X509Certificates() (map[string]*x509.Certificate, error) {
|
||||
// Certificates field is optional. Handle missing value.
|
||||
if sd.Certificates == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
certs := map[string]*x509.Certificate{}
|
||||
|
||||
// Empty set
|
||||
if len(sd.Certificates) == 0 {
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
for _, raw := range sd.Certificates {
|
||||
if raw.Class != asn1.ClassUniversal || raw.Tag != asn1.TagSequence {
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
|
||||
x509, err := x509.ParseCertificate(raw.FullBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iasString, err := IASstring(x509)
|
||||
certs[iasString] = x509
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// ContentInfo returns the SignedData wrapped in a ContentInfo packet.
|
||||
func (sd *SignedData) ContentInfo() (ContentInfo, error) {
|
||||
var nilCI ContentInfo
|
||||
|
||||
der, err := asn.Marshal(*sd)
|
||||
if err != nil {
|
||||
return nilCI, err
|
||||
}
|
||||
|
||||
return ContentInfo{
|
||||
ContentType: oid.SignedData,
|
||||
Content: asn1.RawValue{
|
||||
Class: asn1.ClassContextSpecific,
|
||||
Tag: 0,
|
||||
Bytes: der,
|
||||
IsCompound: true,
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// Verify checks the signature
|
||||
func (sd *SignedData) Verify(Opts x509.VerifyOptions, detached []byte) (chains [][][]*x509.Certificate, err error) {
|
||||
certs, _ := sd.X509Certificates()
|
||||
|
||||
opts := Opts
|
||||
|
||||
for _, c := range certs {
|
||||
opts.Intermediates.AddCert(c)
|
||||
intermediates, fetchErr := fetchIntermediates(c.IssuingCertificateURL)
|
||||
for _, e := range fetchErr {
|
||||
fmt.Printf("Error while fetching intermediates: %s\n", e)
|
||||
}
|
||||
|
||||
for _, i := range intermediates {
|
||||
opts.Intermediates.AddCert(i)
|
||||
}
|
||||
}
|
||||
|
||||
eContent := detached
|
||||
if eContent == nil {
|
||||
eContent = sd.EncapContentInfo.EContent
|
||||
}
|
||||
|
||||
for _, signer := range sd.SignerInfos {
|
||||
//Find and check signer Certificate:
|
||||
sidxxx, _ := signer.SID.IAS.RawValue()
|
||||
sid := fmt.Sprintf("%x", sidxxx.Bytes)
|
||||
|
||||
cert, exist := certs[sid]
|
||||
|
||||
if !exist {
|
||||
err = errors.New("Could not find a Certificate for signer with sid : " + sid)
|
||||
return
|
||||
}
|
||||
|
||||
var chain [][]*x509.Certificate
|
||||
chain, err = cert.Verify(Opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signedMessage := eContent
|
||||
if signer.SignedAttrs != nil {
|
||||
|
||||
//Hash message:
|
||||
var hash crypto.Hash
|
||||
hash, err = signer.Hash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
md := hash.New()
|
||||
|
||||
_, err = md.Write(eContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := md.Sum(nil)
|
||||
|
||||
var messageDigestAttr []byte
|
||||
messageDigestAttr, err = signer.GetMessageDigestAttribute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(messageDigestAttr, h) {
|
||||
err = errors.New("Signed hash does not match the hash of the message")
|
||||
return
|
||||
}
|
||||
|
||||
signedMessage, err = asn.MarshalWithParams(signer.SignedAttrs, `set`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = cert.CheckSignature(signer.X509SignatureAlgorithm(), signedMessage, signer.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
chains = append(chains, chain)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func fetchIntermediates(urls []string) (certificates []*x509.Certificate, errs []error) {
|
||||
for _, url := range urls {
|
||||
var resp *http.Response
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
issuerCert, err := x509.ParseCertificate(issuerBytes)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
//Prevent infinite loop
|
||||
if len(certificates) > 50 {
|
||||
err = errors.New("To many issuers")
|
||||
errs = append(errs, err)
|
||||
return
|
||||
}
|
||||
certificates = append(certificates, issuerCert)
|
||||
|
||||
//Recusively fetch issuers
|
||||
issuers, fetchErrs := fetchIntermediates(issuerCert.IssuingCertificateURL)
|
||||
certificates = append(certificates, issuers...)
|
||||
errs = append(errs, fetchErrs...)
|
||||
}
|
||||
return
|
||||
}
|
153
cms/protocol/signerinfo.go
Normal file
153
cms/protocol/signerinfo.go
Normal file
@ -0,0 +1,153 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"time"
|
||||
|
||||
asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1"
|
||||
oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
|
||||
)
|
||||
|
||||
// SignerInfo ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// sid SignerIdentifier,
|
||||
// digestAlgorithm DigestAlgorithmIdentifier,
|
||||
// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
|
||||
// signatureAlgorithm SignatureAlgorithmIdentifier,
|
||||
// signature SignatureValue,
|
||||
// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
|
||||
type SignerInfo struct {
|
||||
Version int `` // CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
|
||||
SID SignerIdentifier `asn1:"choice"` //
|
||||
DigestAlgorithm pkix.AlgorithmIdentifier `` // DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
SignedAttrs []Attribute `asn1:"set,optional,tag:0"` // SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
SignatureAlgorithm pkix.AlgorithmIdentifier `` // SignatureAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
Signature []byte `` // SignatureValue ::= OCTET STRING
|
||||
UnsignedAttrs []Attribute `asn1:"set,optional,tag:1"` // UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
}
|
||||
|
||||
//SignerIdentifier ::= CHOICE {
|
||||
// issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
// subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
||||
type SignerIdentifier struct {
|
||||
IAS IssuerAndSerialNumber `asn1:"optional"`
|
||||
SKI []byte `asn1:"optional,tag:0"`
|
||||
}
|
||||
|
||||
// FindCertificate finds this SignerInfo's certificate in a slice of
|
||||
// certificates.
|
||||
func (si SignerInfo) FindCertificate(certs []*x509.Certificate) (*x509.Certificate, error) {
|
||||
switch si.Version {
|
||||
case 1: // SID is issuer and serial number
|
||||
isn := si.SID.IAS
|
||||
|
||||
for _, cert := range certs {
|
||||
if bytes.Equal(cert.RawIssuer, isn.Issuer.FullBytes) && isn.SerialNumber.Cmp(cert.SerialNumber) == 0 {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
case 3: // SID is SubjectKeyIdentifier
|
||||
ski := si.SID.SKI
|
||||
|
||||
for _, cert := range certs {
|
||||
for _, ext := range cert.Extensions {
|
||||
if oid.SubjectKeyIdentifier.Equal(ext.Id) {
|
||||
if bytes.Equal(ski, ext.Value) {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
|
||||
return nil, ErrNoCertificate
|
||||
}
|
||||
|
||||
// Hash gets the crypto.Hash associated with this SignerInfo's DigestAlgorithm.
|
||||
// 0 is returned for unrecognized algorithms.
|
||||
func (si SignerInfo) Hash() (crypto.Hash, error) {
|
||||
algo := si.DigestAlgorithm.Algorithm.String()
|
||||
hash := oid.DigestAlgorithmToHash[algo]
|
||||
if hash == 0 || !hash.Available() {
|
||||
return 0, ErrUnsupported
|
||||
}
|
||||
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// X509SignatureAlgorithm gets the x509.SignatureAlgorithm that should be used
|
||||
// for verifying this SignerInfo's signature.
|
||||
func (si SignerInfo) X509SignatureAlgorithm() x509.SignatureAlgorithm {
|
||||
var (
|
||||
sigOID = si.SignatureAlgorithm.Algorithm.String()
|
||||
digestOID = si.DigestAlgorithm.Algorithm.String()
|
||||
)
|
||||
|
||||
return oid.SignatureAlgorithms[sigOID][digestOID]
|
||||
}
|
||||
|
||||
// GetContentTypeAttribute gets the signed ContentType attribute from the
|
||||
// SignerInfo.
|
||||
func (si SignerInfo) GetContentTypeAttribute() (asn1.ObjectIdentifier, error) {
|
||||
var sa Attributes
|
||||
sa = si.SignedAttrs
|
||||
rv, err := sa.GetOnlyAttributeValueBytes(oid.AttributeContentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ct asn1.ObjectIdentifier
|
||||
if rest, err := asn.Unmarshal(rv.FullBytes, &ct); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) > 0 {
|
||||
return nil, ErrTrailingData
|
||||
}
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
// GetMessageDigestAttribute gets the signed MessageDigest attribute from the
|
||||
// SignerInfo.
|
||||
func (si SignerInfo) GetMessageDigestAttribute() ([]byte, error) {
|
||||
var sa Attributes
|
||||
sa = si.SignedAttrs
|
||||
rv, err := sa.GetOnlyAttributeValueBytes(oid.AttributeMessageDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Class != asn1.ClassUniversal || rv.Tag != asn1.TagOctetString {
|
||||
return nil, ASN1Error{"bad class or tag"}
|
||||
}
|
||||
|
||||
return rv.Bytes, nil
|
||||
}
|
||||
|
||||
// GetSigningTimeAttribute gets the signed SigningTime attribute from the
|
||||
// SignerInfo.
|
||||
func (si SignerInfo) GetSigningTimeAttribute() (time.Time, error) {
|
||||
var t time.Time
|
||||
|
||||
var sa Attributes
|
||||
sa = si.SignedAttrs
|
||||
rv, err := sa.GetOnlyAttributeValueBytes(oid.AttributeSigningTime)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if rv.Class != asn1.ClassUniversal || (rv.Tag != asn1.TagUTCTime && rv.Tag != asn1.TagGeneralizedTime) {
|
||||
return t, ASN1Error{"bad class or tag"}
|
||||
}
|
||||
|
||||
if rest, err := asn.Unmarshal(rv.FullBytes, &t); err != nil {
|
||||
return t, err
|
||||
} else if len(rest) > 0 {
|
||||
return t, ErrTrailingData
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user