Add support for signing E-Mails and fixed tests.
This commit is contained in:
		@@ -139,7 +139,7 @@ func (cms *CMS) Decrypt(contentInfo []byte) (plain []byte, err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sign signs the data and returns returns DER-encoded ASN.1 ContentInfo.
 | 
			
		||||
func (cms *CMS) Sign(data []byte) (der []byte, err error) {
 | 
			
		||||
func (cms *CMS) Sign(data []byte, detachedSignature ...bool) (der []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	enci, err := protocol.NewDataEncapsulatedContentInfo(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -162,6 +162,10 @@ func (cms *CMS) Sign(data []byte) (der []byte, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(detachedSignature) > 0 && detachedSignature[0] {
 | 
			
		||||
		sd.EncapContentInfo.EContent = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ci, err := sd.ContentInfo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,7 @@ func TestSignVerify(t *testing.T) {
 | 
			
		||||
func TestEncryptOpenSSL(t *testing.T) {
 | 
			
		||||
	message := []byte("Hallo Welt!")
 | 
			
		||||
 | 
			
		||||
	der, err := openssl.Encrypt(message, leaf.Certificate)
 | 
			
		||||
	der, err := openssl.Encrypt(message, leaf.Certificate, "-outform", "DER")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -129,7 +129,7 @@ func TestDecryptOpenSSL(t *testing.T) {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err := openssl.Decrypt(ciphertext, leaf.PrivateKey)
 | 
			
		||||
	plain, err := openssl.Decrypt(ciphertext, leaf.PrivateKey, "-inform", "DER")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -142,7 +142,7 @@ func TestDecryptOpenSSL(t *testing.T) {
 | 
			
		||||
func TestSignOpenSSL(t *testing.T) {
 | 
			
		||||
	message := []byte("Hallo Welt")
 | 
			
		||||
 | 
			
		||||
	sig, err := openssl.SignDetached(message, leaf.Certificate, leaf.PrivateKey, intermediate.Certificate)
 | 
			
		||||
	sig, err := openssl.SignDetached(message, leaf.Certificate, leaf.PrivateKey, []*x509.Certificate{intermediate.Certificate}, "-outform", "DER")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -176,7 +176,7 @@ func TestVerifyOpenSSL(t *testing.T) {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sig, err := openssl.Verify(der, root.Certificate)
 | 
			
		||||
	sig, err := openssl.Verify(der, root.Certificate, "-inform", "DER")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								mime/mime.go
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								mime/mime.go
									
									
									
									
									
								
							@@ -4,6 +4,7 @@ package mime
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -35,13 +36,26 @@ func (m *MIME) Body() []byte {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Gets the full message
 | 
			
		||||
func (m *MIME) Full() []byte {
 | 
			
		||||
func (m *MIME) Full(sep ...[]byte) []byte {
 | 
			
		||||
 | 
			
		||||
	var sep []byte
 | 
			
		||||
	sep = append(sep, m.interm.Line...)
 | 
			
		||||
	sep = append(sep, m.interm.endOfLine...)
 | 
			
		||||
	if len(sep) == 0 {
 | 
			
		||||
		return m.FullLines().bytes(nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return append(append(m.Header(), sep...), m.Body()...)
 | 
			
		||||
	return m.FullLines().bytes(sep[0])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Gets the full message as Lines
 | 
			
		||||
func (m *MIME) FullLines() (full Lines) {
 | 
			
		||||
 | 
			
		||||
	full = append(full, m.headerFld...)
 | 
			
		||||
	if m.interm.Line == nil && m.interm.endOfLine == nil {
 | 
			
		||||
		m.interm = Line{nil, LF}
 | 
			
		||||
	}
 | 
			
		||||
	full = append(full, m.interm)
 | 
			
		||||
	full = append(full, m.body...)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Adds a header field to the header of the message
 | 
			
		||||
@@ -62,11 +76,16 @@ func (m *MIME) AddHeaderField(key, value []byte) {
 | 
			
		||||
//Removes a header fild from the header of the message
 | 
			
		||||
func (m *MIME) DeleteHeaderField(key []byte) {
 | 
			
		||||
 | 
			
		||||
	for i := len(m.headerFld) - 1; i >= 0; i-- {
 | 
			
		||||
		colonInd := bytes.Index(m.headerFld[i].Line, []byte(":"))
 | 
			
		||||
		k := m.headerFld[i].Line[:colonInd]
 | 
			
		||||
		if bytes.Equal(bytes.ToLower(k), bytes.ToLower(key)) {
 | 
			
		||||
	for i := 0; i < len(m.headerFld); i++ { //i := range m.headerFld {
 | 
			
		||||
		keyAndField := bytes.SplitN(m.headerFld[i].Line, []byte(":"), 2)
 | 
			
		||||
 | 
			
		||||
		if len(keyAndField) == 2 && bytes.Equal(bytes.ToLower(keyAndField[0]), bytes.ToLower(key)) {
 | 
			
		||||
 | 
			
		||||
			m.headerFld = append(m.headerFld[:i], m.headerFld[i+1:]...)
 | 
			
		||||
			for i < len(m.headerFld) && isContinuedLine(m.headerFld[i].Line) {
 | 
			
		||||
				m.headerFld = append(m.headerFld[:i], m.headerFld[i+1:]...)
 | 
			
		||||
			}
 | 
			
		||||
			i--
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -76,15 +95,18 @@ func (m *MIME) DeleteHeaderField(key []byte) {
 | 
			
		||||
func (m *MIME) GetHeaderField(key []byte) (values [][]byte) {
 | 
			
		||||
 | 
			
		||||
	for i := range m.headerFld {
 | 
			
		||||
		colonInd := bytes.Index(m.headerFld[i].Line, []byte(":"))
 | 
			
		||||
		if colonInd < 1 {
 | 
			
		||||
			fmt.Printf("%q\n", (m.headerFld[i].Line))
 | 
			
		||||
		}
 | 
			
		||||
		k := m.headerFld[i].Line[:colonInd]
 | 
			
		||||
		if bytes.Equal(bytes.ToLower(k), bytes.ToLower(key)) {
 | 
			
		||||
			if colonInd+2 < len(m.headerFld[i].Line) {
 | 
			
		||||
				values = append(values, m.headerFld[i].Line[colonInd+2:])
 | 
			
		||||
		keyAndField := bytes.SplitN(m.headerFld[i].Line, []byte(":"), 2)
 | 
			
		||||
 | 
			
		||||
		value := []byte{}
 | 
			
		||||
		if len(keyAndField) == 2 && bytes.Equal(bytes.ToLower(keyAndField[0]), bytes.ToLower(key)) {
 | 
			
		||||
 | 
			
		||||
			value = append(value, keyAndField[1][1:]...)
 | 
			
		||||
			for k := 1; i+k < len(m.headerFld) && isContinuedLine(m.headerFld[i+k].Line); k++ {
 | 
			
		||||
				value = append(value, m.headerFld[i+k-1].endOfLine...)
 | 
			
		||||
				value = append(value, m.headerFld[i+k].Line...)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			values = append(values, value)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -181,24 +203,9 @@ func Parse(raw []byte) (m MIME) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseMIME(rawLines Lines) (m MIME) {
 | 
			
		||||
	var headerField Line
 | 
			
		||||
 | 
			
		||||
	for i := range rawLines {
 | 
			
		||||
 | 
			
		||||
		if len(rawLines[i].Line) > 0 && isContinuedLine(rawLines[i].Line) && i > 0 && !isEmpty(rawLines[i].Line) {
 | 
			
		||||
			headerField.Line = append(headerField.Line, headerField.endOfLine...)
 | 
			
		||||
			headerField.Line = append(headerField.Line, rawLines[i].Line...)
 | 
			
		||||
			headerField.endOfLine = rawLines[i].endOfLine
 | 
			
		||||
		} else {
 | 
			
		||||
 | 
			
		||||
			if i > 0 {
 | 
			
		||||
				m.headerFld = append(m.headerFld, headerField)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//Next headerField
 | 
			
		||||
			headerField = rawLines[i]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Empty line seprates header and body
 | 
			
		||||
		if isEmpty(rawLines[i].Line) {
 | 
			
		||||
			m.interm = rawLines[i]
 | 
			
		||||
@@ -206,6 +213,8 @@ func parseMIME(rawLines Lines) (m MIME) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		m.headerFld = append(m.headerFld, rawLines[i])
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
@@ -241,7 +250,7 @@ func (l Lines) splitLine(sep []byte) (newL Lines) {
 | 
			
		||||
			for i := 0; i < len(split)-1; i++ {
 | 
			
		||||
				newL = append(newL, Line{split[i], sep})
 | 
			
		||||
			}
 | 
			
		||||
			newL = append(newL, Line{split[len(split)-1], nil})
 | 
			
		||||
			newL = append(newL, Line{split[len(split)-1], line.endOfLine})
 | 
			
		||||
		} else {
 | 
			
		||||
			newL = append(newL, line)
 | 
			
		||||
		}
 | 
			
		||||
@@ -284,3 +293,28 @@ func isContinuedLine(s []byte) bool {
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetMultipartBody makes a mulitpart messages with given parts and contentType
 | 
			
		||||
func (m *MIME) SetMultipartBody(contentType string, parts ...MIME) {
 | 
			
		||||
 | 
			
		||||
	body := Lines{}
 | 
			
		||||
 | 
			
		||||
	// Generate boundary
 | 
			
		||||
	bndry := make([]byte, 30)
 | 
			
		||||
	rand.Read(bndry)
 | 
			
		||||
	boundary := fmt.Sprintf("%x", bndry)
 | 
			
		||||
 | 
			
		||||
	// Fix header
 | 
			
		||||
	m.DeleteHeaderField([]byte("Content-Disposition"))
 | 
			
		||||
	m.DeleteHeaderField([]byte("Content-Transfer-Encoding"))
 | 
			
		||||
	m.SetHeaderField([]byte("Content-Type"), []byte(contentType+"; boundary="+boundary))
 | 
			
		||||
 | 
			
		||||
	for i := range parts {
 | 
			
		||||
		body = append(body, Line{[]byte("\n--" + boundary), LF})
 | 
			
		||||
		body = append(body, parts[i].FullLines()...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body = append(body, Line{[]byte("--" + boundary + "--"), LF})
 | 
			
		||||
 | 
			
		||||
	m.body = body
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,33 +14,39 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//Encrypt a message with openssl
 | 
			
		||||
func Encrypt(in []byte, cert *x509.Certificate) (der []byte, err error) {
 | 
			
		||||
func Encrypt(in []byte, cert *x509.Certificate, opts ...string) (der []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	tmp, err := ioutil.TempFile("", "example")
 | 
			
		||||
	defer os.Remove(tmp.Name())
 | 
			
		||||
 | 
			
		||||
	pem.Encode(tmp, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
 | 
			
		||||
 | 
			
		||||
	der, err = openssl(in, "smime", "-outform", "DER", "-encrypt", "-aes128", tmp.Name())
 | 
			
		||||
	param := []string{"smime", "-encrypt", "-aes128"}
 | 
			
		||||
	param = append(param, opts...)
 | 
			
		||||
	param = append(param, tmp.Name())
 | 
			
		||||
	der, err = openssl(in, param...)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Decrypt a message with openssl
 | 
			
		||||
func Decrypt(in []byte, key crypto.PrivateKey) (plain []byte, err error) {
 | 
			
		||||
func Decrypt(in []byte, key crypto.PrivateKey, opts ...string) (plain []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	tmp, err := ioutil.TempFile("", "example")
 | 
			
		||||
	defer os.Remove(tmp.Name())
 | 
			
		||||
 | 
			
		||||
	pem.Encode(tmp, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey))})
 | 
			
		||||
 | 
			
		||||
	plain, err = openssl(in, "smime", "-inform", "DER", "-decrypt", "-inkey", tmp.Name())
 | 
			
		||||
	param := []string{"smime", "-decrypt"}
 | 
			
		||||
	param = append(param, opts...)
 | 
			
		||||
	param = append(param, []string{"-decrypt", "-inkey", tmp.Name()}...)
 | 
			
		||||
	plain, err = openssl(in, param...)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Create a detached signature with openssl
 | 
			
		||||
func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm ...*x509.Certificate) (plain []byte, err error) {
 | 
			
		||||
func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm []*x509.Certificate, opts ...string) (plain []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	tmpCert, err := ioutil.TempFile("", "example")
 | 
			
		||||
	defer os.Remove(tmpCert.Name())
 | 
			
		||||
@@ -59,13 +65,16 @@ func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, inte
 | 
			
		||||
		pem.Encode(tmpInterm, &pem.Block{Type: "CERTIFICATE", Bytes: i.Raw})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err = openssl(in, "smime", "-sign", "-nodetach", "-outform", "DER", "-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name())
 | 
			
		||||
	param := []string{"smime", "-sign", "-nodetach"}
 | 
			
		||||
	param = append(param, opts...)
 | 
			
		||||
	param = append(param, []string{"-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name()}...)
 | 
			
		||||
	plain, err = openssl(in, param...)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Create a signature with openssl
 | 
			
		||||
func Sign(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm ...*x509.Certificate) (plain []byte, err error) {
 | 
			
		||||
func Sign(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm []*x509.Certificate, opts ...string) (plain []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	tmpCert, err := ioutil.TempFile("", "example")
 | 
			
		||||
	defer os.Remove(tmpCert.Name())
 | 
			
		||||
@@ -84,20 +93,26 @@ func Sign(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm ...*x
 | 
			
		||||
		pem.Encode(tmpInterm, &pem.Block{Type: "CERTIFICATE", Bytes: i.Raw})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plain, err = openssl(in, "smime", "-sign", "-outform", "DER", "-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name())
 | 
			
		||||
	param := []string{"smime", "-sign"}
 | 
			
		||||
	param = append(param, opts...)
 | 
			
		||||
	param = append(param, []string{"-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name()}...)
 | 
			
		||||
	plain, err = openssl(in, param...)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Verify a signature with openssl
 | 
			
		||||
func Verify(in []byte, ca *x509.Certificate) (plain []byte, err error) {
 | 
			
		||||
func Verify(in []byte, ca *x509.Certificate, opts ...string) (plain []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	tmpCA, err := ioutil.TempFile("", "example")
 | 
			
		||||
	defer os.Remove(tmpCA.Name())
 | 
			
		||||
 | 
			
		||||
	pem.Encode(tmpCA, &pem.Block{Type: "CERTIFICATE", Bytes: ca.Raw})
 | 
			
		||||
 | 
			
		||||
	plain, err = openssl(in, "smime", "-verify", "-inform", "DER", "-CAfile", tmpCA.Name())
 | 
			
		||||
	param := []string{"smime", "-verify"}
 | 
			
		||||
	param = append(param, opts...)
 | 
			
		||||
	param = append(param, []string{"-CAfile", tmpCA.Name()}...)
 | 
			
		||||
	plain, err = openssl(in, param...)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,27 +5,25 @@ import (
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	pki "github.com/InfiniteLoopSpace/go_S-MIME/pki"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCA(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	pki.DefaultProvince = []string{"CO"}
 | 
			
		||||
	pki.DefaultLocality = []string{"Denver"}
 | 
			
		||||
	DefaultProvince = []string{"CO"}
 | 
			
		||||
	DefaultLocality = []string{"Denver"}
 | 
			
		||||
 | 
			
		||||
	// Create a root CA.
 | 
			
		||||
	root := pki.New(pki.IsCA, pki.Subject(pkix.Name{
 | 
			
		||||
	root := New(IsCA, Subject(pkix.Name{
 | 
			
		||||
		CommonName: "root.myorg.com",
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	// Create an intermediate CA under the root.
 | 
			
		||||
	intermediate := root.Issue(pki.IsCA, pki.Subject(pkix.Name{
 | 
			
		||||
	intermediate := root.Issue(IsCA, Subject(pkix.Name{
 | 
			
		||||
		CommonName: "intermediate.myorg.com",
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	// Create a leaf certificate under the intermediate.
 | 
			
		||||
	leaf := intermediate.Issue(pki.Subject(pkix.Name{
 | 
			
		||||
	leaf := intermediate.Issue(Subject(pkix.Name{
 | 
			
		||||
		CommonName: "leaf.myorg.com",
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/InfiniteLoopSpace/go_S-MIME/b64"
 | 
			
		||||
@@ -42,8 +43,11 @@ func (smime *SMIME) Decrypt(msg []byte) (plaintext []byte, err error) {
 | 
			
		||||
	mediaType, params, err := mail.ParseMediaType()
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(mediaType, "application/pkcs7-mime") {
 | 
			
		||||
		err = errors.New("Unsupported media type: Can not decrypt this mail")
 | 
			
		||||
		return
 | 
			
		||||
		if !strings.HasPrefix(mediaType, "application/x-pkcs7-mime") {
 | 
			
		||||
			err = errors.New("Unsupported media type: Can not decrypt this mail")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.Println("Found Content-Type \"application/x-pkcs7-mime\" used early implementations of S/MIME agents")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(params["smime-type"], "enveloped-data") {
 | 
			
		||||
@@ -147,13 +151,16 @@ func (smime *SMIME) Verify(msg []byte) (chains [][][]*x509.Certificate, err erro
 | 
			
		||||
	mediaType, params, err := mail.ParseMediaType()
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(mediaType, "multipart/signed") {
 | 
			
		||||
		err = errors.New("Unsupported media type: can not decrypt this mail")
 | 
			
		||||
		err = errors.New("Unsupported media type: can not verify the signature")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(params["protocol"], "application/pkcs7-signature") {
 | 
			
		||||
		err = errors.New("Unsupported smime type: can not decrypt this mail")
 | 
			
		||||
		return
 | 
			
		||||
		if !strings.HasPrefix(params["protocol"], "application/x-pkcs7-signature") {
 | 
			
		||||
			err = errors.New("Unsupported smime type: can not verify the signature")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.Println("Found Content-Type \"application/x-pkcs7-signature\" used early implementations of S/MIME agents")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parts, err := mail.MultipartGetParts()
 | 
			
		||||
@@ -170,8 +177,11 @@ func (smime *SMIME) Verify(msg []byte) (chains [][][]*x509.Certificate, err erro
 | 
			
		||||
	mediaType, params, err = signature.ParseMediaType()
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(mediaType, "application/pkcs7-signature") {
 | 
			
		||||
		err = errors.New("Unsupported media type: Can not decrypt this mail")
 | 
			
		||||
		return
 | 
			
		||||
		if !strings.HasPrefix(mediaType, "application/x-pkcs7-signature") {
 | 
			
		||||
			err = errors.New("Unsupported media type: Can not decrypt this mail")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.Println("Found Content-Type \"application/x-pkcs7-signature\" used early implementations of S/MIME agents")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contentTransferEncoding := signature.GetHeaderField([]byte("Content-Transfer-Encoding"))
 | 
			
		||||
@@ -198,3 +208,52 @@ func (smime *SMIME) Verify(msg []byte) (chains [][][]*x509.Certificate, err erro
 | 
			
		||||
 | 
			
		||||
	return smime.CMS.VerifyDetached(signatureDer, signedMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sign signs a mail and returns the signed message.
 | 
			
		||||
func (smime *SMIME) Sign(msg []byte) (signedMsg []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	mail := mime.Parse(msg)
 | 
			
		||||
 | 
			
		||||
	// Prepare the signed Part
 | 
			
		||||
	signedPart := mime.MIME{}
 | 
			
		||||
	signedPart.SetBody(mail.Body())
 | 
			
		||||
	contentType := mail.GetHeaderField([]byte("Content-Type"))
 | 
			
		||||
	if len(contentType) != 1 {
 | 
			
		||||
		err = errors.New("Message has no Content-Type")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	signedPart.SetHeaderField([]byte("Content-Type"), contentType[0])
 | 
			
		||||
	contentTransferEncoding := mail.GetHeaderField([]byte("Content-Transfer-Encoding"))
 | 
			
		||||
	if len(contentType) == 1 {
 | 
			
		||||
		signedPart.SetHeaderField([]byte("Content-Transfer-Encoding"), contentTransferEncoding[0])
 | 
			
		||||
	}
 | 
			
		||||
	contentDisposition := mail.GetHeaderField([]byte("Content-Disposition"))
 | 
			
		||||
	if len(contentType) == 1 {
 | 
			
		||||
		signedPart.SetHeaderField([]byte("Content-Disposition"), contentDisposition[0])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sign
 | 
			
		||||
	lines := mime.ParseLines(signedPart.Full())
 | 
			
		||||
	signatureDER, err := smime.CMS.Sign(lines.Bytes(mime.CRLF), true)
 | 
			
		||||
 | 
			
		||||
	// Encode signature
 | 
			
		||||
 | 
			
		||||
	signature := mime.MIME{}
 | 
			
		||||
	signature.SetHeaderField([]byte("Content-Type"), []byte("application/pkcs7-signature; name=smime.p7s"))
 | 
			
		||||
	signature.SetHeaderField([]byte("Content-Transfer-Encoding"), []byte("base64"))
 | 
			
		||||
	signature.SetHeaderField([]byte("Content-Disposition"), []byte("attachment; filename=smime.p7s"))
 | 
			
		||||
	signatureBASE64, err := b64.EncodeBase64(signatureDER)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	signature.SetBody(signatureBASE64)
 | 
			
		||||
 | 
			
		||||
	// Make multipart/signed message
 | 
			
		||||
	micAlg := "sha256"
 | 
			
		||||
	cntType := "multipart/signed;\n protocol=\"application/pkcs7-signature\";\n micalg=" + micAlg
 | 
			
		||||
 | 
			
		||||
	mail.SetMultipartBody(cntType, signedPart, signature)
 | 
			
		||||
 | 
			
		||||
	signedMsg = mail.Full()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,166 @@ package smime
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/InfiniteLoopSpace/go_S-MIME/openssl"
 | 
			
		||||
	"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 TestEnryptDecrypt(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	SMIME, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	plaintext := []byte(msg)
 | 
			
		||||
 | 
			
		||||
	ciphertext, err := SMIME.Encrypt(plaintext, []*x509.Certificate{leaf.Certificate})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("%s\n", ciphertext)
 | 
			
		||||
 | 
			
		||||
	plain, err := SMIME.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) {
 | 
			
		||||
	SMIME, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SMIME.CMS.Opts.Roots.AddCert(root.Certificate)
 | 
			
		||||
 | 
			
		||||
	msg := []byte(msg)
 | 
			
		||||
 | 
			
		||||
	der, err := SMIME.Sign(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = SMIME.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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SMIME, err := New(keyPair)
 | 
			
		||||
	plain, err := SMIME.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(msg)
 | 
			
		||||
 | 
			
		||||
	SMIME, _ := New()
 | 
			
		||||
	ciphertext, err := SMIME.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(msg)
 | 
			
		||||
 | 
			
		||||
	sig, err := openssl.Sign(message, leaf.Certificate, leaf.PrivateKey, []*x509.Certificate{intermediate.Certificate})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SMIME, err := New()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	SMIME.CMS.Opts.Roots.AddCert(root.Certificate)
 | 
			
		||||
 | 
			
		||||
	_, err = SMIME.Verify(sig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyOpenSSL(t *testing.T) {
 | 
			
		||||
	SMIME, err := New(keyPair)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SMIME.CMS.Opts.Roots.AddCert(root.Certificate)
 | 
			
		||||
 | 
			
		||||
	msg := []byte(msg)
 | 
			
		||||
 | 
			
		||||
	der, err := SMIME.Sign(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sig, err := openssl.Verify(der, root.Certificate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !bytes.Contains(msg, bytes.Replace(sig, []byte("\r"), nil, -1)) {
 | 
			
		||||
		t.Fatal("Signed message and message do not agree!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDecrypt(t *testing.T) {
 | 
			
		||||
	cert, err := tls.X509KeyPair([]byte(bobCert), []byte(bobRSAkey))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user