Added high level functions for cryptographic message syntax.
This commit is contained in:
parent
70660b5e14
commit
b8968e6c58
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!")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user