From 83593a6168e211a15153280f85c5954605a1c828 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Wed, 21 Oct 2015 15:07:57 -0400 Subject: [PATCH] initial (broken) implementation of xmlenc --- README.md | 45 ++++++ xmldsig/signature.go | 19 ++- xmlenc/model.go | 26 ++++ xmlenc/xmlenc.go | 351 ++++++++++++++++++++++++++++++++++++++++++ xmlenc/xmlenc_test.go | 98 ++++++++++++ 5 files changed, 532 insertions(+), 7 deletions(-) create mode 100644 xmlenc/model.go create mode 100644 xmlenc/xmlenc.go create mode 100644 xmlenc/xmlenc_test.go diff --git a/README.md b/README.md index 221ef31..3d329e3 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ A (partial) wrapper for [xmlsec](https://www.aleksey.com/xmlsec). +# Signing (xmldsig) + ## Signing Example key, _ := ioutil.ReadFile("saml.key") @@ -21,3 +23,46 @@ A (partial) wrapper for [xmlsec](https://www.aleksey.com/xmlsec). if err == xmldsig.ErrVerificationFailed { os.Exit(1) } + +# Encryption (xmlenc) + +## Encryption Example + + ctx := xmlenc.Context{} + cert, _ := ioutil.ReadFile("saml.cert.pem") + err := ctx.AddCert(cert) + tmplDoc := []byte(`` + + + + Hello, World! + + + + + + + + + + + + + + + + + + + `) + ciphertext, err := ctx.Encrypt(docStr, []byte("Hello, World!")) + +## Decryption Example + + ctx := xmlenc.Context{} + key, _ := ioutil.ReadFile("saml.key.pem") + err := ctx.AddKey(key) + plaintext, err := ctx.Decrypt(ciphertext) diff --git a/xmldsig/signature.go b/xmldsig/signature.go index f98b8d7..9a1734c 100644 --- a/xmldsig/signature.go +++ b/xmldsig/signature.go @@ -23,13 +23,18 @@ type Method struct { type Signature struct { XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Signature"` - CanonicalizationMethod Method `xml:"SignedInfo>CanonicalizationMethod"` - SignatureMethod Method `xml:"SignedInfo>SignatureMethod"` - ReferenceTransforms []Method `xml:"SignedInfo>Reference>Transforms>Transform"` - DigestMethod Method `xml:"SignedInfo>Reference>DigestMethod"` - DigestValue string `xml:"SignedInfo>Reference>DigestValue"` - SignatureValue string `xml:"SignatureValue"` - KeyName string `xml:"KeyInfo>KeyName"` + CanonicalizationMethod Method `xml:"SignedInfo>CanonicalizationMethod"` + SignatureMethod Method `xml:"SignedInfo>SignatureMethod"` + ReferenceTransforms []Method `xml:"SignedInfo>Reference>Transforms>Transform"` + DigestMethod Method `xml:"SignedInfo>Reference>DigestMethod"` + DigestValue string `xml:"SignedInfo>Reference>DigestValue"` + SignatureValue string `xml:"SignatureValue"` + KeyName string `xml:"KeyInfo>KeyName"` + X509Certificate *SignatureX509Data `xml:"KeyInfo>X509Data,omitempty"` +} + +type SignatureX509Data struct { + X509Certificate string `xml:"X509Certificate,omitempty"` } // DefaultSignature populates a default Signature that uses c14n and SHA1. diff --git a/xmlenc/model.go b/xmlenc/model.go new file mode 100644 index 0000000..dfcdd67 --- /dev/null +++ b/xmlenc/model.go @@ -0,0 +1,26 @@ +package xmlenc + +import ( + "encoding/xml" +) + +type EncryptedData struct { + XMLName xml.Name `xml:"http://www.w3.org/2001/04/xmlenc#"` + EncryptionMethod EncryptionMethod + KeyInfo KeyInfo + CipherDataValue string `xml:"CipherData>CipherValue"` +} + +type EncryptionMethod struct { + Algorithm string `xml:",attr"` +} + +type KeyInfo struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"` + KeyName string `xml:"KeyName"` + X509Data *X509Data `xml:"X509Data,omitempty"` +} + +type X509Data struct { + X509Certificate string `xml:"X509Certificate,omitempty"` +} diff --git a/xmlenc/xmlenc.go b/xmlenc/xmlenc.go new file mode 100644 index 0000000..d12f913 --- /dev/null +++ b/xmlenc/xmlenc.go @@ -0,0 +1,351 @@ +package xmlenc + +import ( + "errors" + "fmt" + "unsafe" +) + +// Note: on mac you need: brew install libxmlsec1 libxml2 + +// #cgo pkg-config: xmlsec1 +// #include +// #include +// #include +// #include +// #include +// +// static inline xmlSecKeyDataId MY_xmlSecKeyDataDesId() { +// return xmlSecOpenSSLKeyDataDesGetKlass(); +// } +// static inline xmlSecKeyDataId MY_xmlSecKeyDataDsaId() { +// return xmlSecOpenSSLKeyDataDsaGetKlass(); +// } +// static inline xmlSecKeyDataId MY_xmlSecKeyDataEcdsaId() { +// return xmlSecOpenSSLKeyDataEcdsaGetKlass(); +// } +// static inline xmlSecKeyDataId MY_xmlSecKeyDataRsaId() { +// return xmlSecOpenSSLKeyDataRsaGetKlass(); +// } +// +import "C" + +// #cgo pkg-config: libxml-2.0 +// #include +// #include +// #include +// // Macro wrapper function +// static inline void MY_xmlFree(void *p) { +// xmlFree(p); +// } +// +import "C" + +func init() { + C.xmlInitParser() + + if rv := C.xmlSecInit(); rv < 0 { + panic("xmlsec failed to initialize") + } + + if rv := C.xmlSecCryptoAppInit(nil); rv < 0 { + panic("xmlsec crypto initialization failed.") + } + if rv := C.xmlSecCryptoInit(); rv < 0 { + panic("xmlsec crypto initialization failed.") + } +} + +func newDoc(buf []byte) (*C.xmlDoc, error) { + ctx := C.xmlCreateMemoryParserCtxt((*C.char)(unsafe.Pointer(&buf[0])), + C.int(len(buf))) + if ctx == nil { + return nil, errors.New("error creating parser") + } + defer C.xmlFreeParserCtxt(ctx) + + //C.xmlCtxtUseOptions(ctx, C.int(p.Options)) + C.xmlParseDocument(ctx) + + if ctx.wellFormed == C.int(0) { + return nil, errors.New("malformed XML") + } + + doc := ctx.myDoc + if doc == nil { + return nil, errors.New("parse failed") + } + return doc, nil +} + +func dumpDoc(doc *C.xmlDoc) []byte { + var buffer *C.xmlChar + var bufferSize C.int + C.xmlDocDumpMemory(doc, &buffer, &bufferSize) + rv := C.GoStringN((*C.char)(unsafe.Pointer(buffer)), bufferSize) + C.MY_xmlFree(unsafe.Pointer(buffer)) + + // TODO(ross): this is totally nasty un-idiomatic, but I'm + // tired of googling how to copy a []byte from a char* + return []byte(rv) +} + +func closeDoc(doc *C.xmlDoc) { + C.xmlFreeDoc(doc) +} + +type Context struct { + ctx *C.xmlSecEncCtx + keysMngr *C.xmlSecKeysMngr +} + +func (c *Context) Close() { + if c.ctx != nil { + C.xmlSecEncCtxDestroy(c.ctx) + c.ctx = nil + } + + if c.keysMngr != nil { + C.xmlSecKeysMngrDestroy(c.keysMngr) + c.keysMngr = nil + } +} + +func (c *Context) init() error { + if c.ctx != nil { + return nil + } + + c.keysMngr = C.xmlSecKeysMngrCreate() + if c.keysMngr == nil { + return errors.New("xmlSecKeysMngrCreate failed") + } + + if rv := C.xmlSecCryptoAppDefaultKeysMngrInit(c.keysMngr); rv < 0 { + return fmt.Errorf("xmlSecCryptoAppDefaultKeysMngrInit failed: %d", rv) + } + + c.ctx = C.xmlSecEncCtxCreate(c.keysMngr) + if c.ctx == nil { + return errors.New("xmlSecEncCtxCreate failed") + } + + return nil +} + +const ( + DES = iota + DSA + ECDSA + RSA +) + +func (c *Context) AddKey(data []byte) error { + if err := c.init(); err != nil { + return err + } + + key := C.xmlSecCryptoAppKeyLoadMemory( + (*C.xmlSecByte)(unsafe.Pointer(&data[0])), + C.uint(len(data)), + C.xmlSecKeyDataFormatPem, + nil, + nil, + nil) + if key == nil { + return errors.New("xmlSecCryptoAppKeyLoadMemory failed") + } + + name := "k" + C.xmlSecKeySetName(key, (*C.xmlChar)(unsafe.Pointer(C.CString(name)))) + + if rv := C.xmlSecCryptoAppDefaultKeysMngrAdoptKey(c.keysMngr, key); rv < 0 { + return errors.New("xmlSecCryptoAppDefaultKeysMngrAdoptKey failed") + } + + return nil +} + +func (c *Context) AddCert(data []byte) error { + if err := c.init(); err != nil { + return err + } + + /* + var xmlSecKeyType C.xmlSecKeyDataId + switch keyType { + case DES: + xmlSecKeyType = C.MY_xmlSecKeyDataDesId() + case DSA: + xmlSecKeyType = C.MY_xmlSecKeyDataDsaId() + case ECDSA: + xmlSecKeyType = C.MY_xmlSecKeyDataEcdsaId() + case RSA: + xmlSecKeyType = C.MY_xmlSecKeyDataRsaId() + default: + return errors.New("unknown key type") + } + */ + + if rv := C.xmlSecCryptoAppKeysMngrCertLoadMemory(c.keysMngr, + (*C.xmlSecByte)(unsafe.Pointer(&data[0])), + C.uint(len(data)), + C.xmlSecKeyDataFormatCertPem, // https://www.aleksey.com/xmlsec/api/xmlsec-keysdata.html#XMLSECKEYDATAFORMAT + C.xmlSecKeyDataTypePublic); rv < 0 { + return errors.New("xmlSecCryptoAppKeysMngrCertLoadMemory failed") + } + + return nil +} + +// Encrypt encrypts `plaintext` according to the template `tmplDoc`. +func (c *Context) Encrypt(tmplDoc []byte, plaintext []byte) ([]byte, error) { + if err := c.init(); err != nil { + return nil, err + } + + parsedDoc, err := newDoc(tmplDoc) + if err != nil { + return nil, err + } + defer closeDoc(parsedDoc) + + tmplNode := C.xmlSecFindNode(C.xmlDocGetRootElement(parsedDoc), + (*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeEncryptedData)), + (*C.xmlChar)(unsafe.Pointer(&C.xmlSecEncNs))) + if tmplNode == nil { + return nil, errors.New("cannot find start node") + } + + // TODO(ross): actually use the requested cipher + c.ctx.encKey = C.xmlSecKeyGenerateByName( + (*C.xmlChar)(unsafe.Pointer(C.CString("aes"))), + 128, C.xmlSecKeyDataTypeSession) + if c.ctx.encKey == nil { + return nil, errors.New("failed to generate session key") + } + + if rv := C.xmlSecEncCtxXmlEncrypt(c.ctx, tmplNode, + C.xmlDocGetRootElement(parsedDoc)); rv < 0 { + return nil, errors.New("cannot encrypt") + } + + return dumpDoc(parsedDoc), nil +} +func (c *Context) Decrypt(doc []byte) ([]byte, error) { + if err := c.init(); err != nil { + return nil, err + } + + return nil, nil +} + +/* +func Decrypt(key []byte, doc []byte) ([]byte, error) { + + parsedDoc, err := newDoc(doc) + if err != nil { + return nil, err + } + defer closeDoc(parsedDoc) + + node := C.xmlSecFindNode(C.xmlDocGetRootElement(parsedDoc), + (*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeEncryptedKey)), + (*C.xmlChar)(unsafe.Pointer(&C.xmlSecEncNs))) + if node == nil { + return nil, errors.New("cannot find start node") + } + + ctx, err := newContext(key) + if err != nil { + return nil, err + } + defer closeContext(ctx) + + ctx.mode = C.xmlEncCtxModeEncryptedKey + + if rv := C.xmlSecEncCtxDecrypt(ctx, node); rv < 0 { + return nil, errors.New("cannot decrypt") + } + if ctx.result == nil { + return nil, errors.New("cannot decrypt") + } + + ctx2 := C.xmlSecEncCtxCreate(nil) + if ctx2 == nil { + return nil, errors.New("failed to create signature context") + } + + ctx2.encKey = C.xmlSecKeyReadMemory(C.MY_xmlSecKeyDataDesId(), + C.xmlSecBufferGetData(ctx.result), + C.xmlSecBufferGetSize(ctx.result)) + if ctx2.encKey == nil { + return nil, errors.New("cannot load session key") + } + ctx2.mode = C.xmlEncCtxModeEncryptedData + + node2 := C.xmlSecFindNode(C.xmlDocGetRootElement(parsedDoc), + (*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeEncryptedData)), + (*C.xmlChar)(unsafe.Pointer(&C.xmlSecEncNs))) + if node2 == nil { + return nil, errors.New("cannot find start node") + } + + if rv := C.xmlSecEncCtxDecrypt(ctx2, node2); rv < 0 { + return nil, errors.New("cannot decrypt") + } + if ctx2.result == nil { + return nil, errors.New("cannot decrypt") + } + + log.Panic("%s", string(dumpDoc(parsedDoc))) + + // Apparently we can either have replaced the document or not, so if we + // have return it with dump. + if ctx2.resultReplaced != 0 { + return dumpDoc(parsedDoc), nil + } else { + sz := C.xmlSecBufferGetSize(ctx2.result) + buf := C.xmlSecBufferGetData(ctx2.result) + rv := C.GoStringN((*C.char)(unsafe.Pointer(buf)), C.int(sz)) // TODO(ross): eliminate double copy + return []byte(rv), nil + } +} + +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +// x +*/ diff --git a/xmlenc/xmlenc_test.go b/xmlenc/xmlenc_test.go new file mode 100644 index 0000000..c600333 --- /dev/null +++ b/xmlenc/xmlenc_test.go @@ -0,0 +1,98 @@ +package xmlenc + +import ( + "log" + "testing" + + . "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { TestingT(t) } + +type EncryptTest struct{} + +var _ = Suite(&EncryptTest{}) + +func (s *EncryptTest) TestEncrypt(c *C) { + key := []byte(`-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9 +IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+ +PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQAB +AoGAD4/Z4LWVWV6D1qMIp1Gzr0ZmdWTE1SPdZ7Ej8glGnCzPdguCPuzbhGXmIg0V +J5D+02wsqws1zd48JSMXXM8zkYZVwQYIPUsNn5FetQpwxDIMPmhHg+QNBgwOnk8J +K2sIjjLPL7qY7Itv7LT7Gvm5qSOkZ33RCgXcgz+okEIQMYkCQQDzbTOyDL0c5WQV +6A2k06T/azdhUdGXF9C0+WkWSfNaovmTgRXh1G+jMlr82Snz4p4/STt7P/XtyWzF +3pkVgZr3AkEA7nPjXwHlttNEMo6AtxHd47nizK2NUN803ElIUT8P9KSCoERmSXq6 +6PDekGNic4ldpsSvOeYCk8MAYoDBy9kvVwJBAMLgX4xg6lzhv7hR5+pWjTb1rIY6 +rCHbrPfU264+UZXz9v2BT/VUznLF81WMvStD9xAPHpFS6R0OLghSZhdzhI0CQQDL +8Duvfxzrn4b9QlmduV8wLERoT6rEVxKLsPVz316TGrxJvBZLk/cV0SRZE1cZf4uk +XSWMfEcJ/0Zt+LdG1CqjAkEAqwLSglJ9Dy3HpgMz4vAAyZWzAxvyA1zW0no9GOLc +PQnYaNUN/Fy2SYtETXTb0CQ9X1rt8ffkFP7ya+5TC83aMg== +-----END RSA PRIVATE KEY-----`) + log.Printf("%s", string(key)) + cert := []byte(`-----BEGIN CERTIFICATE----- +MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC +Tk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYD +VQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG +9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4 +MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xi +ZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2Zl +aWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5v +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LO +NoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHIS +KOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d +1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8 +BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7n +bK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2Qar +Q4/67OZfHd7R+POBXhophSMv1ZOo +-----END CERTIFICATE-----`) + log.Printf("%s", string(cert)) + + docStr := []byte(` + + + + Hello, World! + + + + + + + + + + + + + + + + + + +`) + + ctx := Context{} + err := ctx.AddCert(cert) + c.Assert(err, IsNil) + + err = ctx.AddKey(key) + c.Assert(err, IsNil) + + actualEncryptedString, err := ctx.Encrypt(docStr, []byte("Hello, World!")) + c.Assert(err, IsNil) + log.Printf("%s", actualEncryptedString) + + // expectedEncryptedString := "XXX" + //c.Assert(string(actualEncryptedString), Equals, expectedEncryptedString) + + plaintext, err := ctx.Decrypt(actualEncryptedString) + c.Assert(err, IsNil) + log.Printf("plaintext=%s", plaintext) + //c.Assert(string(plaintext), Equals, "Hello, World!") +}