refactor:
- merge xmldsig and xmlenc into a single package - implement thread-safe error capture - implement encryption using libxmlsec, discarding the go implementation
This commit is contained in:
76
decrypt.go
Normal file
76
decrypt.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package xmlsec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #cgo pkg-config: xmlsec1
|
||||
// #include <xmlsec/xmlsec.h>
|
||||
// #include <xmlsec/xmltree.h>
|
||||
// #include <xmlsec/xmlenc.h>
|
||||
// #include <xmlsec/templates.h>
|
||||
// #include <xmlsec/crypto.h>
|
||||
import "C"
|
||||
|
||||
// #cgo pkg-config: libxml-2.0
|
||||
// #include <libxml/parser.h>
|
||||
// #include <libxml/parserInternals.h>
|
||||
// #include <libxml/xmlmemory.h>
|
||||
import "C"
|
||||
|
||||
func DecryptXML(privateKey []byte, doc []byte) ([]byte, error) {
|
||||
startProcessingXML()
|
||||
defer stopProcessingXML()
|
||||
|
||||
keysMngr := C.xmlSecKeysMngrCreate()
|
||||
if keysMngr == nil {
|
||||
return nil, popError()
|
||||
}
|
||||
defer C.xmlSecKeysMngrDestroy(keysMngr)
|
||||
|
||||
if rv := C.xmlSecCryptoAppDefaultKeysMngrInit(keysMngr); rv < 0 {
|
||||
return nil, popError()
|
||||
}
|
||||
|
||||
key := C.xmlSecCryptoAppKeyLoadMemory(
|
||||
(*C.xmlSecByte)(unsafe.Pointer(&privateKey[0])),
|
||||
C.xmlSecSize(len(privateKey)),
|
||||
C.xmlSecKeyDataFormatPem,
|
||||
nil, nil, nil)
|
||||
if key == nil {
|
||||
return nil, popError()
|
||||
}
|
||||
|
||||
if rv := C.xmlSecCryptoAppDefaultKeysMngrAdoptKey(keysMngr, key); rv < 0 {
|
||||
return nil, popError()
|
||||
}
|
||||
|
||||
parsedDoc, err := newDoc(doc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed XML")
|
||||
}
|
||||
defer closeDoc(parsedDoc)
|
||||
|
||||
// create encryption context
|
||||
encCtx := C.xmlSecEncCtxCreate(keysMngr)
|
||||
if encCtx == nil {
|
||||
return nil, popError()
|
||||
}
|
||||
defer C.xmlSecEncCtxDestroy(encCtx)
|
||||
|
||||
encDataNode := C.xmlSecFindNode(C.xmlDocGetRootElement(parsedDoc),
|
||||
(*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeEncryptedData)),
|
||||
(*C.xmlChar)(unsafe.Pointer(&C.xmlSecEncNs)))
|
||||
if encDataNode == nil {
|
||||
return nil, fmt.Errorf("xmlSecFindNode cannot find EncryptedData node")
|
||||
}
|
||||
|
||||
// decrypt the data
|
||||
if rv := C.xmlSecEncCtxDecrypt(encCtx, encDataNode); rv < 0 {
|
||||
return nil, popError()
|
||||
}
|
||||
encDataNode = nil // the template is inserted in the doc, so we don't own it
|
||||
|
||||
return dumpDoc(parsedDoc), nil
|
||||
}
|
||||
267
decrypt_test.go
Normal file
267
decrypt_test.go
Normal file
File diff suppressed because one or more lines are too long
322
encrypt.go
Normal file
322
encrypt.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package xmlsec
|
||||
|
||||
// Note: on mac you need: brew install libxmlsec1 libxml2
|
||||
|
||||
// #cgo pkg-config: xmlsec1
|
||||
// #include <xmlsec/xmlsec.h>
|
||||
// #include <xmlsec/xmltree.h>
|
||||
// #include <xmlsec/xmlenc.h>
|
||||
// #include <xmlsec/errors.h>
|
||||
// #include <xmlsec/templates.h>
|
||||
// #include <xmlsec/crypto.h>
|
||||
//
|
||||
// static inline xmlSecKeyDataId MY_xmlSecKeyDataAesId(void) {
|
||||
// return xmlSecKeyDataAesId;
|
||||
// }
|
||||
//
|
||||
// static inline xmlSecTransformId MY_xmlSecTransformAes128CbcId(void) {
|
||||
// return xmlSecTransformAes128CbcId;
|
||||
// }
|
||||
//
|
||||
// static inline xmlSecTransformId MY_xmlSecTransformRsaOaepId(void) {
|
||||
// return xmlSecTransformRsaOaepId;
|
||||
// }
|
||||
//
|
||||
// static inline xmlSecKeyDataId MY_xmlSecKeyDataDesId(void) {
|
||||
// return xmlSecKeyDataDesId;
|
||||
// }
|
||||
// static inline xmlSecTransformId MY_xmlSecTransformAes192CbcId(void) {
|
||||
// return xmlSecTransformAes192CbcId;
|
||||
// }
|
||||
// static inline xmlSecTransformId MY_xmlSecTransformAes256CbcId(void) {
|
||||
// return xmlSecTransformAes256CbcId;
|
||||
// }
|
||||
// static inline xmlSecTransformId MY_xmlSecTransformDes3CbcId(void) {
|
||||
// return xmlSecTransformDes3CbcId;
|
||||
// }
|
||||
// static inline xmlSecTransformId MY_xmlSecTransformRsaPkcs1Id(void) {
|
||||
// return xmlSecTransformRsaPkcs1Id;
|
||||
// }
|
||||
//
|
||||
import "C"
|
||||
|
||||
// #cgo pkg-config: libxml-2.0
|
||||
// #include <libxml/parser.h>
|
||||
// #include <libxml/parserInternals.h>
|
||||
// #include <libxml/xmlmemory.h>
|
||||
// // Macro wrapper function
|
||||
// static inline void MY_xmlFree(void *p) {
|
||||
// xmlFree(p);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// void onError_cgo(char *file, int line, char *funcName, char *errorObject, char *errorSubject, int reason, char *msg);
|
||||
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.")
|
||||
}
|
||||
|
||||
C.xmlSecErrorsSetCallback((C.xmlSecErrorsCallback)(unsafe.Pointer(C.onError_cgo)))
|
||||
}
|
||||
|
||||
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 closeDoc(doc *C.xmlDoc) {
|
||||
C.xmlFreeDoc(doc)
|
||||
}
|
||||
|
||||
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 constXmlChar(s string) *C.xmlChar {
|
||||
return (*C.xmlChar)(unsafe.Pointer(C.CString(s)))
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultAlgorithm = iota
|
||||
Aes128Cbc
|
||||
Aes192Cbc
|
||||
Aes256Cbc
|
||||
Des3Cbc
|
||||
DsaSha1
|
||||
Sha1
|
||||
Sha256
|
||||
Sha384
|
||||
Sha512
|
||||
RsaOaep
|
||||
RsaPkcs1
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
SessionCipher int
|
||||
Cipher int
|
||||
DigestAlgorithm int
|
||||
}
|
||||
|
||||
// XmlEncrypt encrypts the XML document to publicKey.
|
||||
func XmlEncrypt(publicKey, doc []byte, opts Options) ([]byte, error) {
|
||||
startProcessingXML()
|
||||
defer stopProcessingXML()
|
||||
|
||||
keysMngr := C.xmlSecKeysMngrCreate()
|
||||
if keysMngr == nil {
|
||||
return nil, fmt.Errorf("xmlSecKeysMngrCreate failed")
|
||||
}
|
||||
defer C.xmlSecKeysMngrDestroy(keysMngr)
|
||||
|
||||
if rv := C.xmlSecCryptoAppDefaultKeysMngrInit(keysMngr); rv < 0 {
|
||||
return nil, fmt.Errorf("xmlSecCryptoAppDefaultKeysMngrInit failed")
|
||||
}
|
||||
|
||||
key := C.xmlSecCryptoAppKeyLoadMemory(
|
||||
(*C.xmlSecByte)(unsafe.Pointer(&publicKey[0])),
|
||||
C.xmlSecSize(len(publicKey)),
|
||||
C.xmlSecKeyDataFormatCertPem,
|
||||
nil, nil, nil)
|
||||
if key == nil {
|
||||
return nil, fmt.Errorf("xmlSecCryptoAppKeyLoadMemory failed")
|
||||
}
|
||||
|
||||
if rv := C.xmlSecCryptoAppKeyCertLoadMemory(key,
|
||||
(*C.xmlSecByte)(unsafe.Pointer(&publicKey[0])),
|
||||
C.xmlSecSize(len(publicKey)),
|
||||
C.xmlSecKeyDataFormatCertPem); rv < 0 {
|
||||
C.xmlSecKeyDestroy(key)
|
||||
return nil, fmt.Errorf("xmlSecCryptoAppKeyCertLoad failed")
|
||||
}
|
||||
|
||||
if rv := C.xmlSecCryptoAppDefaultKeysMngrAdoptKey(keysMngr, key); rv < 0 {
|
||||
return nil, fmt.Errorf("xmlSecCryptoAppDefaultKeysMngrAdoptKey failed")
|
||||
}
|
||||
|
||||
parsedDoc, err := newDoc(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closeDoc(parsedDoc)
|
||||
|
||||
var sessionCipherTransform C.xmlSecTransformId
|
||||
switch opts.SessionCipher {
|
||||
case DefaultAlgorithm:
|
||||
sessionCipherTransform = C.MY_xmlSecTransformAes256CbcId()
|
||||
case Aes256Cbc:
|
||||
sessionCipherTransform = C.MY_xmlSecTransformAes256CbcId()
|
||||
case Aes192Cbc:
|
||||
sessionCipherTransform = C.MY_xmlSecTransformAes192CbcId()
|
||||
case Aes128Cbc:
|
||||
sessionCipherTransform = C.MY_xmlSecTransformAes128CbcId()
|
||||
case Des3Cbc:
|
||||
sessionCipherTransform = C.MY_xmlSecTransformDes3CbcId()
|
||||
default:
|
||||
return nil, fmt.Errorf("XXX")
|
||||
}
|
||||
|
||||
// create encryption template to encrypt XML file and replace
|
||||
// its content with encryption result
|
||||
encDataNode := C.xmlSecTmplEncDataCreate(parsedDoc, sessionCipherTransform,
|
||||
nil, (*C.xmlChar)(unsafe.Pointer(&C.xmlSecTypeEncElement)), nil, nil)
|
||||
if encDataNode == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplEncDataCreate failed")
|
||||
}
|
||||
defer func() {
|
||||
if encDataNode != nil {
|
||||
C.xmlFreeNode(encDataNode)
|
||||
encDataNode = nil
|
||||
}
|
||||
}()
|
||||
|
||||
// we want to put encrypted data in the <enc:CipherValue/> node
|
||||
if C.xmlSecTmplEncDataEnsureCipherValue(encDataNode) == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplEncDataEnsureCipherValue failed")
|
||||
}
|
||||
|
||||
// add <dsig:KeyInfo/>
|
||||
keyInfoNode := C.xmlSecTmplEncDataEnsureKeyInfo(encDataNode, nil)
|
||||
if keyInfoNode == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplEncDataEnsureKeyInfo failed")
|
||||
}
|
||||
|
||||
// add <enc:EncryptedKey/> to store the encrypted session key
|
||||
var cipherTransform C.xmlSecTransformId
|
||||
switch opts.Cipher {
|
||||
case DefaultAlgorithm:
|
||||
cipherTransform = C.MY_xmlSecTransformRsaOaepId()
|
||||
case RsaOaep:
|
||||
cipherTransform = C.MY_xmlSecTransformRsaOaepId()
|
||||
case RsaPkcs1:
|
||||
cipherTransform = C.MY_xmlSecTransformRsaPkcs1Id()
|
||||
}
|
||||
encKeyNode := C.xmlSecTmplKeyInfoAddEncryptedKey(keyInfoNode, cipherTransform, nil, nil, nil)
|
||||
if encKeyNode == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplKeyInfoAddEncryptedKey failed")
|
||||
}
|
||||
|
||||
// we want to put encrypted key in the <enc:CipherValue/> node
|
||||
if C.xmlSecTmplEncDataEnsureCipherValue(encKeyNode) == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplEncDataEnsureCipherValue failed")
|
||||
}
|
||||
|
||||
// add <dsig:KeyInfo/> and <dsig:KeyName/> nodes to <enc:EncryptedKey/>
|
||||
keyInfoNode2 := C.xmlSecTmplEncDataEnsureKeyInfo(encKeyNode, nil)
|
||||
if keyInfoNode2 == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplEncDataEnsureKeyInfo failed")
|
||||
}
|
||||
// Add a DigestMethod element to the encryption method node
|
||||
{
|
||||
encKeyMethod := C.xmlSecTmplEncDataGetEncMethodNode(encKeyNode)
|
||||
var ns = constXmlChar("http://www.w3.org/2000/09/xmldsig#")
|
||||
var strDigestMethod = constXmlChar("DigestMethod")
|
||||
var strAlgorithm = constXmlChar("Algorithm")
|
||||
var algorithm *C.xmlChar
|
||||
switch opts.DigestAlgorithm {
|
||||
case Sha512:
|
||||
algorithm = constXmlChar("http://www.w3.org/2001/04/xmlenc#sha512")
|
||||
case Sha384:
|
||||
algorithm = constXmlChar("http://www.w3.org/2001/04/xmldsig-more#sha384")
|
||||
case Sha256:
|
||||
algorithm = constXmlChar("http://www.w3.org/2001/04/xmlenc#sha256")
|
||||
case Sha1:
|
||||
algorithm = constXmlChar("http://www.w3.org/2000/09/xmldsig#sha1")
|
||||
case DefaultAlgorithm:
|
||||
algorithm = constXmlChar("http://www.w3.org/2000/09/xmldsig#sha1")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown digest algorithm %d", opts.DigestAlgorithm)
|
||||
}
|
||||
node := C.xmlSecAddChild(encKeyMethod, strDigestMethod, ns)
|
||||
C.xmlSetProp(node, strAlgorithm, algorithm)
|
||||
}
|
||||
|
||||
// add our certificate to KeyInfoNode
|
||||
x509dataNode := C.xmlSecTmplKeyInfoAddX509Data(keyInfoNode2)
|
||||
if x509dataNode == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplKeyInfoAddX509Data failed")
|
||||
}
|
||||
if dataNode := C.xmlSecTmplX509DataAddCertificate(x509dataNode); dataNode == nil {
|
||||
return nil, fmt.Errorf("xmlSecTmplX509DataAddCertificate failed")
|
||||
}
|
||||
|
||||
// create encryption context
|
||||
var encCtx = C.xmlSecEncCtxCreate(keysMngr)
|
||||
if encCtx == nil {
|
||||
return nil, fmt.Errorf("xmlSecEncCtxCreate failed")
|
||||
}
|
||||
defer C.xmlSecEncCtxDestroy(encCtx)
|
||||
|
||||
// generate a key of the appropriate type
|
||||
switch opts.SessionCipher {
|
||||
case DefaultAlgorithm:
|
||||
encCtx.encKey = C.xmlSecKeyGenerate(C.MY_xmlSecKeyDataAesId(), 256,
|
||||
C.xmlSecKeyDataTypeSession)
|
||||
case Aes128Cbc:
|
||||
encCtx.encKey = C.xmlSecKeyGenerate(C.MY_xmlSecKeyDataAesId(), 128,
|
||||
C.xmlSecKeyDataTypeSession)
|
||||
case Aes192Cbc:
|
||||
encCtx.encKey = C.xmlSecKeyGenerate(C.MY_xmlSecKeyDataAesId(), 192,
|
||||
C.xmlSecKeyDataTypeSession)
|
||||
case Aes256Cbc:
|
||||
encCtx.encKey = C.xmlSecKeyGenerate(C.MY_xmlSecKeyDataAesId(), 256,
|
||||
C.xmlSecKeyDataTypeSession)
|
||||
case Des3Cbc:
|
||||
encCtx.encKey = C.xmlSecKeyGenerate(C.MY_xmlSecKeyDataDesId(), 192,
|
||||
C.xmlSecKeyDataTypeSession)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown cipher type %d", opts.SessionCipher)
|
||||
}
|
||||
if encCtx.encKey == nil {
|
||||
return nil, fmt.Errorf("xmlSecKeyGenerate failed")
|
||||
}
|
||||
|
||||
// encrypt the data
|
||||
if rv := C.xmlSecEncCtxXmlEncrypt(encCtx, encDataNode, C.xmlDocGetRootElement(parsedDoc)); rv < 0 {
|
||||
return nil, fmt.Errorf("xmlSecEncCtxXmlEncrypt failed")
|
||||
}
|
||||
encDataNode = nil // the template is inserted in the doc, so we don't own it
|
||||
|
||||
return dumpDoc(parsedDoc), nil
|
||||
}
|
||||
104
encrypt_test.go
Normal file
104
encrypt_test.go
Normal file
File diff suppressed because one or more lines are too long
76
error.go
Normal file
76
error.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package xmlsec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/crewjam/errset"
|
||||
)
|
||||
|
||||
// #include <pthread.h>
|
||||
import "C"
|
||||
|
||||
var globalErrors = map[uintptr]errset.ErrSet{}
|
||||
|
||||
type Error struct {
|
||||
FileName string
|
||||
Line int
|
||||
FuncName string
|
||||
Object string
|
||||
Subject string
|
||||
Reason int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"func=%s:file=%s:line=%d:obj=%s:subj=%s:error=%d:%s",
|
||||
e.FuncName,
|
||||
e.FileName,
|
||||
e.Line,
|
||||
e.Object,
|
||||
e.Subject,
|
||||
e.Reason,
|
||||
e.Message)
|
||||
}
|
||||
|
||||
//export onError
|
||||
func onError(file *C.char, line C.int, funcName *C.char, errorObject *C.char, errorSubject *C.char, reason C.int, msg *C.char) {
|
||||
err := Error{
|
||||
FuncName: C.GoString(funcName),
|
||||
FileName: C.GoString(file),
|
||||
Line: int(line),
|
||||
Object: C.GoString(errorObject),
|
||||
Subject: C.GoString(errorSubject),
|
||||
Reason: int(reason),
|
||||
Message: C.GoString(msg)}
|
||||
threadID := uintptr(unsafe.Pointer(C.pthread_self()))
|
||||
globalErrors[threadID] = append(globalErrors[threadID], err)
|
||||
}
|
||||
|
||||
// startProcessingXML is called whenever we enter a function exported by this package.
|
||||
// It locks the current goroutine to the current thread and establishes a thread-local
|
||||
// error object.
|
||||
func startProcessingXML() {
|
||||
runtime.LockOSThread()
|
||||
threadID := uintptr(unsafe.Pointer(C.pthread_self()))
|
||||
globalErrors[threadID] = errset.ErrSet{}
|
||||
}
|
||||
|
||||
// stopProcessingXML unlocks the goroutine-thread lock and deletes the current
|
||||
// error stack.
|
||||
func stopProcessingXML() {
|
||||
runtime.UnlockOSThread()
|
||||
threadID := uintptr(unsafe.Pointer(C.pthread_self()))
|
||||
delete(globalErrors, threadID)
|
||||
}
|
||||
|
||||
// popError returns the global error for the current thread and resets it to
|
||||
// an empty error. Returns nil if no errors have occurred.
|
||||
func popError() error {
|
||||
threadID := uintptr(unsafe.Pointer(C.pthread_self()))
|
||||
rv := globalErrors[threadID].ReturnValue()
|
||||
globalErrors[threadID] = errset.ErrSet{}
|
||||
return rv
|
||||
}
|
||||
6
error_thunk.go
Normal file
6
error_thunk.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package xmlsec
|
||||
|
||||
// void onError_cgo(char *file, int line, char *funcName, char *errorObject, char *errorSubject, int reason, char *msg) {
|
||||
// onError(file, line, funcName, errorObject, errorSubject, reason, msg);
|
||||
// }
|
||||
import "C"
|
||||
10
setup_test.go
Normal file
10
setup_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package xmlsec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Hook up gocheck into the "go test" runner.
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
@@ -1,4 +1,4 @@
|
||||
package xmldsig
|
||||
package xmlsec
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
@@ -1,248 +0,0 @@
|
||||
package xmldsig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Note: on mac you need: brew install libxmlsec1 libxml2
|
||||
|
||||
// #cgo pkg-config: xmlsec1
|
||||
// #include <xmlsec/xmlsec.h>
|
||||
// #include <xmlsec/xmltree.h>
|
||||
// #include <xmlsec/xmldsig.h>
|
||||
// #include <xmlsec/crypto.h>
|
||||
import "C"
|
||||
|
||||
// #cgo pkg-config: libxml-2.0
|
||||
// #include <libxml/parser.h>
|
||||
// #include <libxml/parserInternals.h>
|
||||
// #include <libxml/xmlmemory.h>
|
||||
// // 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.")
|
||||
}
|
||||
}
|
||||
|
||||
// Options represents additional, less commonly used, options for Sign and
|
||||
// Verify
|
||||
type Options struct {
|
||||
// Specify the name of ID attributes for specific elements. This
|
||||
// may be required if the signed document contains Reference elements
|
||||
// that define which parts of the document are to be signed.
|
||||
//
|
||||
// https://www.aleksey.com/xmlsec/faq.html#section_3_2
|
||||
// http://www.w3.org/TR/xml-id/
|
||||
// http://xmlsoft.org/html/libxml-valid.html#xmlAddID
|
||||
XMLID []XMLIDOption
|
||||
}
|
||||
|
||||
type XMLIDOption struct {
|
||||
ElementName string
|
||||
ElementNamespace string
|
||||
AttributeName string
|
||||
}
|
||||
|
||||
func newDoc(buf []byte, opts Options) (*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")
|
||||
}
|
||||
|
||||
for _, idattr := range opts.XMLID {
|
||||
addIDAttr(C.xmlDocGetRootElement(doc),
|
||||
idattr.AttributeName, idattr.ElementName, idattr.ElementNamespace)
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func addIDAttr(node *C.xmlNode, attrName, nodeName, nsHref string) {
|
||||
// process children first because it does not matter much but does simplify code
|
||||
cur := C.xmlSecGetNextElementNode(node.children)
|
||||
for {
|
||||
if cur == nil {
|
||||
break
|
||||
}
|
||||
addIDAttr(cur, attrName, nodeName, nsHref)
|
||||
cur = C.xmlSecGetNextElementNode(cur.next)
|
||||
}
|
||||
|
||||
if C.GoString((*C.char)(unsafe.Pointer(node.name))) != nodeName {
|
||||
return
|
||||
}
|
||||
if nsHref != "" && node.ns != nil && C.GoString((*C.char)(unsafe.Pointer(node.ns.href))) != nsHref {
|
||||
return
|
||||
}
|
||||
|
||||
// the attribute with name equal to attrName should exist
|
||||
for attr := node.properties; attr != nil; attr = attr.next {
|
||||
if C.GoString((*C.char)(unsafe.Pointer(attr.name))) == attrName {
|
||||
id := C.xmlNodeListGetString(node.doc, attr.children, 1)
|
||||
if id == nil {
|
||||
continue
|
||||
}
|
||||
C.xmlAddID(nil, node.doc, id, attr)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Sign returns a version of docStr signed with key according to
|
||||
// the XML-DSIG standard. docStr is a template document meaning
|
||||
// that it contains a `Signature` element in the
|
||||
// http://www.w3.org/2000/09/xmldsig# namespace.
|
||||
func Sign(key []byte, doc []byte, opts Options) ([]byte, error) {
|
||||
ctx := C.xmlSecDSigCtxCreate(nil)
|
||||
if ctx == nil {
|
||||
return nil, errors.New("failed to create signature context")
|
||||
}
|
||||
defer C.xmlSecDSigCtxDestroy(ctx)
|
||||
|
||||
ctx.signKey = C.xmlSecCryptoAppKeyLoadMemory(
|
||||
(*C.xmlSecByte)(unsafe.Pointer(&key[0])),
|
||||
C.xmlSecSize(len(key)),
|
||||
C.xmlSecKeyDataFormatPem,
|
||||
nil, nil, nil)
|
||||
if ctx.signKey == nil {
|
||||
return nil, errors.New("failed to load pem key")
|
||||
}
|
||||
|
||||
parsedDoc, err := newDoc(doc, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closeDoc(parsedDoc)
|
||||
|
||||
node := C.xmlSecFindNode(C.xmlDocGetRootElement(parsedDoc),
|
||||
(*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeSignature)),
|
||||
(*C.xmlChar)(unsafe.Pointer(&C.xmlSecDSigNs)))
|
||||
if node == nil {
|
||||
return nil, errors.New("cannot find start node")
|
||||
}
|
||||
|
||||
if rv := C.xmlSecDSigCtxSign(ctx, node); rv < 0 {
|
||||
return nil, errors.New("failed to sign")
|
||||
}
|
||||
|
||||
return dumpDoc(parsedDoc), nil
|
||||
}
|
||||
|
||||
// ErrVerificationFailed is returned from Verify when the signature is incorrect
|
||||
var ErrVerificationFailed = errors.New("signature verification failed")
|
||||
|
||||
const (
|
||||
xmlSecDSigStatusUnknown = 0
|
||||
xmlSecDSigStatusSucceeded = 1
|
||||
xmlSecDSigStatusInvalid = 2
|
||||
)
|
||||
|
||||
// Verify checks that the signature in docStr is valid according
|
||||
// to the XML-DSIG specification. publicKey is the public part of
|
||||
// the key used to sign docStr. If the signature is not correct,
|
||||
// this function returns ErrVerificationFailed.
|
||||
func Verify(publicKey []byte, doc []byte, opts Options) error {
|
||||
keysMngr := C.xmlSecKeysMngrCreate()
|
||||
if keysMngr == nil {
|
||||
return fmt.Errorf("xmlSecKeysMngrCreate failed")
|
||||
}
|
||||
defer C.xmlSecKeysMngrDestroy(keysMngr)
|
||||
|
||||
if rv := C.xmlSecCryptoAppDefaultKeysMngrInit(keysMngr); rv < 0 {
|
||||
return fmt.Errorf("xmlSecCryptoAppDefaultKeysMngrInit failed")
|
||||
}
|
||||
|
||||
key := C.xmlSecCryptoAppKeyLoadMemory(
|
||||
(*C.xmlSecByte)(unsafe.Pointer(&publicKey[0])),
|
||||
C.xmlSecSize(len(publicKey)),
|
||||
C.xmlSecKeyDataFormatCertPem,
|
||||
nil, nil, nil)
|
||||
if key == nil {
|
||||
return fmt.Errorf("xmlSecCryptoAppKeyLoadMemory failed")
|
||||
}
|
||||
|
||||
if rv := C.xmlSecCryptoAppKeyCertLoadMemory(key,
|
||||
(*C.xmlSecByte)(unsafe.Pointer(&publicKey[0])),
|
||||
C.xmlSecSize(len(publicKey)),
|
||||
C.xmlSecKeyDataFormatCertPem); rv < 0 {
|
||||
C.xmlSecKeyDestroy(key)
|
||||
return fmt.Errorf("xmlSecCryptoAppKeyCertLoad failed")
|
||||
}
|
||||
|
||||
if rv := C.xmlSecCryptoAppDefaultKeysMngrAdoptKey(keysMngr, key); rv < 0 {
|
||||
return fmt.Errorf("xmlSecCryptoAppDefaultKeysMngrAdoptKey failed")
|
||||
}
|
||||
|
||||
dsigCtx := C.xmlSecDSigCtxCreate(keysMngr)
|
||||
if dsigCtx == nil {
|
||||
return fmt.Errorf("xmlSecDSigCtxCreate failed")
|
||||
}
|
||||
defer C.xmlSecDSigCtxDestroy(dsigCtx)
|
||||
|
||||
parsedDoc, err := newDoc(doc, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDoc(parsedDoc)
|
||||
|
||||
node := C.xmlSecFindNode(C.xmlDocGetRootElement(parsedDoc),
|
||||
(*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeSignature)),
|
||||
(*C.xmlChar)(unsafe.Pointer(&C.xmlSecDSigNs)))
|
||||
if node == nil {
|
||||
return errors.New("cannot find start node")
|
||||
}
|
||||
|
||||
if rv := C.xmlSecDSigCtxVerify(dsigCtx, node); rv < 0 {
|
||||
return ErrVerificationFailed
|
||||
}
|
||||
|
||||
if dsigCtx.status != xmlSecDSigStatusSucceeded {
|
||||
return ErrVerificationFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
package xmlenc
|
||||
package xmlsec
|
||||
|
||||
/*
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
@@ -211,3 +212,4 @@ func decryptEncryptedKey(key []byte, encryptedKey *encryptedKey) ([]byte, error)
|
||||
|
||||
return sessionKeyPlaintext, nil
|
||||
}
|
||||
*/
|
||||
@@ -1,15 +1,12 @@
|
||||
package xmlenc
|
||||
package xmlsec
|
||||
|
||||
/*
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Hook up gocheck into the "go test" runner.
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
type XmlencTest struct {
|
||||
Key []byte
|
||||
DocStr []byte
|
||||
@@ -202,3 +199,4 @@ func (testSuite *XmlencTest) TestInvalid(c *C) {
|
||||
_, err = Decrypt(testSuite.Key, docStr)
|
||||
c.Assert(err, ErrorMatches, "unsupported encryption method: http://www.w3.org/2001/04/xmlenc#aes999-cbc")
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user