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!")
+}