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