Added high level functions for cryptographic message syntax.

This commit is contained in:
InfiniteLoopSpace 2018-11-16 14:04:26 +01:00
parent 70660b5e14
commit b8968e6c58
2 changed files with 427 additions and 0 deletions

240
cms/cms.go Normal file
View 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
View 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!")
}
}