From b8968e6c5811acbc9ea10dcfda2b7735fbd68ee8 Mon Sep 17 00:00:00 2001 From: InfiniteLoopSpace <35842605+InfiniteLoopSpace@users.noreply.github.com> Date: Fri, 16 Nov 2018 14:04:26 +0100 Subject: [PATCH] Added high level functions for cryptographic message syntax. --- cms/cms.go | 240 ++++++++++++++++++++++++++++++++++++++++++++++++ cms/cms_test.go | 187 +++++++++++++++++++++++++++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 cms/cms.go create mode 100644 cms/cms_test.go diff --git a/cms/cms.go b/cms/cms.go new file mode 100644 index 0000000..e2cd2ef --- /dev/null +++ b/cms/cms.go @@ -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 +} diff --git a/cms/cms_test.go b/cms/cms_test.go new file mode 100644 index 0000000..378f4ee --- /dev/null +++ b/cms/cms_test.go @@ -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!") + } +}