Added high level functions for cryptographic message syntax.
This commit is contained in:
		
							
								
								
									
										240
									
								
								cms/cms.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								cms/cms.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
// Package cms contains high level functions for cryptographic message syntax RFC 5652.
 | 
			
		||||
package cms
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/asn1"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	protocol "github.com/InfiniteLoopSpace/go_S-MIME/cms/protocol"
 | 
			
		||||
	oid "github.com/InfiniteLoopSpace/go_S-MIME/oid"
 | 
			
		||||
	timestamp "github.com/InfiniteLoopSpace/go_S-MIME/timestamp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CMS is an instance of cms to en/decrypt and sign/verfiy CMS data
 | 
			
		||||
// with the given keyPairs and options.
 | 
			
		||||
type CMS struct {
 | 
			
		||||
	Intermediate, roots        *x509.CertPool
 | 
			
		||||
	Opts                       x509.VerifyOptions
 | 
			
		||||
	ContentEncryptionAlgorithm asn1.ObjectIdentifier
 | 
			
		||||
	TimeStampServer            string
 | 
			
		||||
	TimeStamp                  bool
 | 
			
		||||
	keyPairs                   []tls.Certificate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New create a new instance of CMS with given keyPairs.
 | 
			
		||||
func New(cert ...tls.Certificate) (cms *CMS, err error) {
 | 
			
		||||
	root, err := x509.SystemCertPool()
 | 
			
		||||
	intermediate := x509.NewCertPool()
 | 
			
		||||
	cms = &CMS{
 | 
			
		||||
		Intermediate: intermediate,
 | 
			
		||||
		roots:        root,
 | 
			
		||||
		Opts: x509.VerifyOptions{
 | 
			
		||||
			Intermediates: intermediate,
 | 
			
		||||
			Roots:         root,
 | 
			
		||||
			CurrentTime:   time.Now(),
 | 
			
		||||
			KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
 | 
			
		||||
		},
 | 
			
		||||
		ContentEncryptionAlgorithm: oid.EncryptionAlgorithmAES128CBC,
 | 
			
		||||
		TimeStampServer:            "http://timestamp.digicert.com",
 | 
			
		||||
		TimeStamp:                  false,
 | 
			
		||||
	}
 | 
			
		||||
	cms.keyPairs = cert
 | 
			
		||||
 | 
			
		||||
	for i := range cms.keyPairs {
 | 
			
		||||
		cms.keyPairs[i].Leaf, err = x509.ParseCertificate(cms.keyPairs[i].Certificate[0])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Encrypt encrypts data for the recipients and returns DER-encoded ASN.1 ContentInfo.
 | 
			
		||||
func (cms *CMS) Encrypt(data []byte, recipients []*x509.Certificate) (der []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	eci, key, _, err := protocol.NewEncryptedContentInfo(oid.Data, cms.ContentEncryptionAlgorithm, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var reciInfos []protocol.RecipientInfo
 | 
			
		||||
 | 
			
		||||
	for _, recipient := range recipients {
 | 
			
		||||
		rInfo := protocol.NewRecipientInfo(recipient, key)
 | 
			
		||||
		reciInfos = append(reciInfos, rInfo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ed := protocol.NewEnvelopedData(&eci, reciInfos)
 | 
			
		||||
 | 
			
		||||
	ci, err := ed.ContentInfo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ci.DER()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AuthEncrypt AEAD-encrypts data for the recipients and returns DER-encoded ASN.1 ContentInfo.
 | 
			
		||||
func (cms *CMS) AuthEncrypt(data []byte, recipients []*x509.Certificate) (der []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	eci, key, mac, err := protocol.NewEncryptedContentInfo(oid.Data, oid.EncryptionAlgorithmAES128GCM, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var reciInfos []protocol.RecipientInfo
 | 
			
		||||
 | 
			
		||||
	for _, recipient := range recipients {
 | 
			
		||||
		rInfo := protocol.NewRecipientInfo(recipient, key)
 | 
			
		||||
		reciInfos = append(reciInfos, rInfo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ed := protocol.NewAuthEnvelopedData(&eci, reciInfos, mac)
 | 
			
		||||
 | 
			
		||||
	ci, err := ed.ContentInfo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ci.DER()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AuthDecrypt AEAD-decrypts DER-encoded ASN.1 ContentInfo and returns plaintext.
 | 
			
		||||
func (cms *CMS) AuthDecrypt(contentInfo []byte) (plain []byte, err error) {
 | 
			
		||||
	contInf, err := protocol.ParseContentInfo(contentInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ed, err := contInf.AuthEnvelopedDataContent()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err = ed.Decrypt(cms.keyPairs)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Decrypt decrypts DER-encoded ASN.1 ContentInfo and returns plaintext.
 | 
			
		||||
func (cms *CMS) Decrypt(contentInfo []byte) (plain []byte, err error) {
 | 
			
		||||
	contInf, err := protocol.ParseContentInfo(contentInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ed, err := contInf.EnvelopedDataContent()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err = ed.Decrypt(cms.keyPairs)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sign signs the data and returns returns DER-encoded ASN.1 ContentInfo.
 | 
			
		||||
func (cms *CMS) Sign(data []byte) (der []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	enci, err := protocol.NewDataEncapsulatedContentInfo(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sd, err := protocol.NewSignedData(enci)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range cms.keyPairs {
 | 
			
		||||
		sd.AddSignerInfo(cms.keyPairs[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cms.TimeStamp {
 | 
			
		||||
		err1 := AddTimestamps(sd, cms.TimeStampServer)
 | 
			
		||||
		if err1 != nil {
 | 
			
		||||
			log.Println(err1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ci, err := sd.ContentInfo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ci.DER()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify verifies the signature in contentInfo and returns returns DER-encoded ASN.1 ContentInfo.
 | 
			
		||||
func (cms *CMS) Verify(contentInfo []byte) (chains [][][]*x509.Certificate, err error) {
 | 
			
		||||
	ci, err := protocol.ParseContentInfo(contentInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sd, err := ci.SignedDataContent()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chains, err = sd.Verify(cms.Opts, nil)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyDetached verifies the detached signature of msg in contentInfo and returns returns DER-encoded ASN.1 ContentInfo.
 | 
			
		||||
func (cms *CMS) VerifyDetached(contentInfo, msg []byte) (chains [][][]*x509.Certificate, err error) {
 | 
			
		||||
 | 
			
		||||
	ci, err := protocol.ParseContentInfo(contentInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sd, err := ci.SignedDataContent()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chains, err = sd.Verify(cms.Opts, msg)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddTimestamps adds a timestamp to the SignedData using the RFC3161
 | 
			
		||||
// timestamping service at the given URL. This timestamp proves that the signed
 | 
			
		||||
// message existed the time of generation, allowing verifiers to have more trust
 | 
			
		||||
// in old messages signed with revoked keys.
 | 
			
		||||
func AddTimestamps(sd *protocol.SignedData, url string) (err error) {
 | 
			
		||||
	var attrs = make([]protocol.Attribute, len(sd.SignerInfos))
 | 
			
		||||
 | 
			
		||||
	// Fetch all timestamp tokens before adding any to sd. This avoids a partial
 | 
			
		||||
	// failure.
 | 
			
		||||
	for i := range attrs {
 | 
			
		||||
		hash, err := sd.SignerInfos[i].Hash()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		tsToken, err := timestamp.FetchTSToken(url, sd.SignerInfos[i].Signature, hash)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		attr, err := protocol.NewAttribute(oid.AttributeTimeStampToken, tsToken)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		attrs[i] = attr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range attrs {
 | 
			
		||||
		sd.SignerInfos[i].UnsignedAttrs = append(sd.SignerInfos[i].UnsignedAttrs, attrs[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										187
									
								
								cms/cms_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								cms/cms_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
package cms
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	openssl "github.com/InfiniteLoopSpace/go_S-MIME/openssl"
 | 
			
		||||
	pki "github.com/InfiniteLoopSpace/go_S-MIME/pki"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	root = pki.New(pki.IsCA, pki.Subject(pkix.Name{
 | 
			
		||||
		CommonName: "root.example.com",
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	intermediate = root.Issue(pki.IsCA, pki.Subject(pkix.Name{
 | 
			
		||||
		CommonName: "intermediate.example.com",
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	leaf = intermediate.Issue(pki.Subject(pkix.Name{
 | 
			
		||||
		CommonName: "leaf.example.com",
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	keyPair = tls.Certificate{
 | 
			
		||||
		Certificate: [][]byte{leaf.Certificate.Raw, intermediate.Certificate.Raw, root.Certificate.Raw},
 | 
			
		||||
		PrivateKey:  leaf.PrivateKey.(crypto.PrivateKey),
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAuthEnrypt(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	cms, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plaintext := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	ciphertext, err := cms.AuthEncrypt(plaintext, []*x509.Certificate{leaf.Certificate})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err := cms.AuthDecrypt(ciphertext)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !bytes.Equal(plaintext, plain) {
 | 
			
		||||
		t.Fatal("Encryption and decryption are not inverse")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnryptDecrypt(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	cms, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plaintext := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	ciphertext, err := cms.Encrypt(plaintext, []*x509.Certificate{leaf.Certificate})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err := cms.Decrypt(ciphertext)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !bytes.Equal(plaintext, plain) {
 | 
			
		||||
		t.Fatal("Encryption and decryption are not inverse")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSignVerify(t *testing.T) {
 | 
			
		||||
	cms, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cms.roots.AddCert(root.Certificate)
 | 
			
		||||
 | 
			
		||||
	msg := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	der, err := cms.Sign(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = cms.Verify(der)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEncryptOpenSSL(t *testing.T) {
 | 
			
		||||
	message := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	der, err := openssl.Encrypt(message, leaf.Certificate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cms, err := New(keyPair)
 | 
			
		||||
	plain, err := cms.Decrypt(der)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !bytes.Equal(message, plain) {
 | 
			
		||||
		t.Fatal("Encryption and decryption are not inverse")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDecryptOpenSSL(t *testing.T) {
 | 
			
		||||
	message := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	cms, _ := New()
 | 
			
		||||
	ciphertext, err := cms.Encrypt(message, []*x509.Certificate{leaf.Certificate})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err := openssl.Decrypt(ciphertext, leaf.PrivateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !bytes.Equal(message, plain) {
 | 
			
		||||
		t.Fatal("Encryption and decryption are not inverse")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSignOpenSSL(t *testing.T) {
 | 
			
		||||
	message := []byte("Hallo Welt")
 | 
			
		||||
 | 
			
		||||
	sig, err := openssl.SignDetached(message, leaf.Certificate, leaf.PrivateKey, intermediate.Certificate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cms, err := New()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	cms.roots.AddCert(root.Certificate)
 | 
			
		||||
 | 
			
		||||
	_, err = cms.Verify(sig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyOpenSSL(t *testing.T) {
 | 
			
		||||
	cms, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cms.TimeStamp = true
 | 
			
		||||
 | 
			
		||||
	cms.roots.AddCert(root.Certificate)
 | 
			
		||||
 | 
			
		||||
	msg := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	der, err := cms.Sign(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sig, err := openssl.Verify(der, root.Certificate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !bytes.Equal(msg, sig) {
 | 
			
		||||
		t.Fatal("Signed message and message do not agree!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user