progress: enable xmldsig and do other cleanups

This commit is contained in:
Ross Kinder
2015-12-23 11:31:54 -05:00
parent 45f8aa9eeb
commit b390a3e30a
10 changed files with 655 additions and 381 deletions

View File

@@ -48,7 +48,7 @@ func DecryptXML(privateKey []byte, doc []byte) ([]byte, error) {
parsedDoc, err := newDoc(doc) parsedDoc, err := newDoc(doc)
if err != nil { if err != nil {
return nil, fmt.Errorf("malformed XML") return nil, err
} }
defer closeDoc(parsedDoc) defer closeDoc(parsedDoc)

View File

@@ -106,7 +106,6 @@ Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
c.Assert(Cert, Not(IsNil)) c.Assert(Cert, Not(IsNil))
/* XXX
err = Verify(Cert, actualPlaintextString, DsigOptions{ err = Verify(Cert, actualPlaintextString, DsigOptions{
XMLID: []XMLIDOption{{ XMLID: []XMLIDOption{{
ElementName: "Assertion", ElementName: "Assertion",
@@ -114,7 +113,6 @@ Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
AttributeName: "ID", AttributeName: "ID",
}}}) }}})
c.Assert(err, IsNil) c.Assert(err, IsNil)
*/
} }
func (testSuite *DecryptTest) TestDecryptWithPadding(c *C) { func (testSuite *DecryptTest) TestDecryptWithPadding(c *C) {

View File

@@ -1,123 +1,30 @@
package xmlsec package xmlsec
// Note: on mac you need: brew install libxmlsec1 libxml2
// #cgo pkg-config: xmlsec1 // #cgo pkg-config: xmlsec1
// #include <xmlsec/xmlsec.h> // #include <xmlsec/xmlsec.h>
// #include <xmlsec/xmltree.h> // #include <xmlsec/xmltree.h>
// #include <xmlsec/xmlenc.h> // #include <xmlsec/xmlenc.h>
// #include <xmlsec/errors.h>
// #include <xmlsec/templates.h> // #include <xmlsec/templates.h>
// #include <xmlsec/crypto.h> // #include <xmlsec/crypto.h>
// //
// static inline xmlSecKeyDataId MY_xmlSecKeyDataAesId(void) { // // Note: the xmlSecKeyData*Id itentifiers are macros, so we need to wrap them
// return xmlSecKeyDataAesId; // // here to make them callable from go.
// } // static inline xmlSecKeyDataId MY_xmlSecKeyDataAesId(void) { return xmlSecKeyDataAesId; }
// static inline xmlSecKeyDataId MY_xmlSecKeyDataDesId(void) { return xmlSecKeyDataDesId; }
// static inline xmlSecTransformId MY_xmlSecTransformAes128CbcId(void) { return xmlSecTransformAes128CbcId; }
// 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_xmlSecTransformRsaOaepId(void) { return xmlSecTransformRsaOaepId; }
// static inline xmlSecTransformId MY_xmlSecTransformRsaPkcs1Id(void) { return xmlSecTransformRsaPkcs1Id; }
// //
// 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 "C"
import ( import (
"errors"
"fmt" "fmt"
"unsafe" "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 ( const (
DefaultAlgorithm = iota DefaultAlgorithm = iota
Aes128Cbc Aes128Cbc

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,10 @@
package xmlsec package xmlsec
// onError_cgo is a C function that can be passed to xmlSecErrorsSetCallback which
// in turn invokes the go function onError which captures the error.
// For reasons I do not completely understand, it must be defined in a different
// file from onError
// void onError_cgo(char *file, int line, char *funcName, char *errorObject, char *errorSubject, int reason, char *msg) { // 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); // onError(file, line, funcName, errorObject, errorSubject, reason, msg);
// } // }

View File

@@ -6,7 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/crewjam/go-xmlsec/xmldsig" "github.com/crewjam/go-xmlsec"
) )
func main() { func main() {
@@ -33,7 +33,7 @@ func main() {
buf, err := ioutil.ReadAll(os.Stdin) buf, err := ioutil.ReadAll(os.Stdin)
if *doSign { if *doSign {
signedBuf, err := xmldsig.Sign(key, buf, xmldsig.Options{}) signedBuf, err := xmlsec.Sign(key, buf, xmlsec.DsigOptions{})
if err != nil { if err != nil {
fmt.Printf("%s\n", err) fmt.Printf("%s\n", err)
os.Exit(1) os.Exit(1)
@@ -42,8 +42,8 @@ func main() {
} }
if *doVerify { if *doVerify {
err := xmldsig.Verify(key, buf, xmldsig.Options{}) err := xmlsec.Verify(key, buf, xmlsec.DsigOptions{})
if err == xmldsig.ErrVerificationFailed { if err == xmlsec.ErrVerificationFailed {
fmt.Println("signature is not correct") fmt.Println("signature is not correct")
os.Exit(1) os.Exit(1)
} }

157
xmldsig.go Normal file
View File

@@ -0,0 +1,157 @@
package xmlsec
import (
"errors"
"fmt"
"unsafe"
)
// #cgo pkg-config: xmlsec1
// #include <xmlsec/xmlsec.h>
// #include <xmlsec/xmltree.h>
// #include <xmlsec/xmlenc.h>
// #include <xmlsec/xmldsig.h>
// #include <xmlsec/errors.h>
// #include <xmlsec/crypto.h>
import "C"
// DsigOptions represents additional, less commonly used, options for Sign and
// Verify
type DsigOptions 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
}
// 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 DsigOptions) ([]byte, error) {
startProcessingXML()
defer stopProcessingXML()
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 := newDoc2(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 DsigOptions) error {
startProcessingXML()
defer stopProcessingXML()
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 := newDoc2(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
}

280
xmldsig_test.go Normal file

File diff suppressed because one or more lines are too long

215
xmlenc.go
View File

@@ -1,215 +0,0 @@
package xmlsec
/*
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"encoding/xml"
"errors"
"fmt"
"hash"
"io"
)
type method struct {
Algorithm string `xml:",attr"`
}
type encryptedData struct {
XMLName string `xml:"http://www.w3.org/2001/04/xmlenc# EncryptedData"`
ID string `xml:"Id,attr"`
Type string `xml:",attr"`
EncryptionMethod method `xml:"EncryptionMethod"`
KeyInfo keyInfo `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
CipherData *cipherData
}
type keyInfo struct {
XMLName string `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
EncryptedKey *encryptedKey `xml:"http://www.w3.org/2001/04/xmlenc# EncryptedKey"`
X509Data x509Data `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
}
type encryptedKey struct {
XMLName string `xml:"http://www.w3.org/2001/04/xmlenc# EncryptedKey"`
EncryptionMethod *encryptionMethod
KeyInfo *keyInfo
CipherData *cipherData `xml:"http://www.w3.org/2001/04/xmlenc# CipherData"`
}
type encryptionMethod struct {
Algorithm string `xml:",attr"`
DigestMethod method `xml:"http://www.w3.org/2000/09/xmldsig# DigestMethod"`
}
type x509Data struct {
XMLName string `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
X509Certificate string
}
type cipherData struct {
XMLName string `xml:"http://www.w3.org/2001/04/xmlenc# CipherData"`
CipherValue string `xml:"CipherValue"`
}
var ErrNoEncryptedDataFound = errors.New("no EncryptedData elements found")
// Decrypt searches the serialized XML document `doc` looking for
// an EncryptedData element. When found, it decrypts the element
// and returns the plaintext of the encrypted section.
//
// Key is a PEM-encoded RSA private key, or a binary TDES key or a
// binary AES key, depending on the encryption type in use.
func Decrypt(key []byte, doc []byte) ([]byte, error) {
decoder := xml.NewDecoder(bytes.NewReader(doc))
for {
t, err := decoder.Token()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if startElement, ok := t.(xml.StartElement); ok {
if startElement.Name.Space == "http://www.w3.org/2001/04/xmlenc#" && startElement.Name.Local == "EncryptedData" {
d := encryptedData{}
if err := decoder.DecodeElement(&d, &startElement); err != nil {
return nil, err
}
plaintext, err := decryptEncryptedData(key, &d)
if err != nil {
return nil, err
}
return plaintext, nil
}
}
}
return nil, ErrNoEncryptedDataFound
}
// decryptEncryptedData decrypts the EncryptedData element and returns the
// plaintext.
func decryptEncryptedData(key []byte, d *encryptedData) ([]byte, error) {
if d.KeyInfo.EncryptedKey != nil {
var err error
key, err = decryptEncryptedKey(key, d.KeyInfo.EncryptedKey)
if err != nil {
return nil, err
}
}
iv := []byte{}
ciphertext, err := base64.StdEncoding.DecodeString(d.CipherData.CipherValue)
if err != nil {
return nil, err
}
var blockCipher cipher.Block
switch d.EncryptionMethod.Algorithm {
case "http://www.w3.org/2001/04/xmlenc#tripledes-cbc":
blockCipher, err = des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
iv = ciphertext[:des.BlockSize]
ciphertext = ciphertext[des.BlockSize:]
case "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
"http://www.w3.org/2001/04/xmlenc#aes192-cbc",
"http://www.w3.org/2001/04/xmlenc#aes256-cbc":
blockCipher, err = aes.NewCipher(key)
if err != nil {
return nil, err
}
iv = ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
default:
return nil, fmt.Errorf("unsupported encryption method: %s", d.EncryptionMethod.Algorithm)
}
mode := cipher.NewCBCDecrypter(blockCipher, iv)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
// Remove any padding from the end of the message.
//
// Per http://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html
// On decryption, just take the last byte and, after sanity checking
// it, strip that many bytes from the end of the decrypted cipher text.
paddingByteCount := int(plaintext[len(plaintext)-1])
if paddingByteCount < 1 || paddingByteCount > len(plaintext) || paddingByteCount > blockCipher.BlockSize() {
return nil, fmt.Errorf("invalid padding")
}
plaintext = plaintext[:len(plaintext)-paddingByteCount]
return plaintext, nil
}
// decryptEncryptedKey returns the plaintext version of the EncryptedKey which is
// encrypted using RSA-PKCS1v15 or RSA-OAEP-MGF1P and assuming the `key` is
// a PEM-encoded RSA private key.
func decryptEncryptedKey(key []byte, encryptedKey *encryptedKey) ([]byte, error) {
// All the supported encryption schemes are based on RSA, so `key` must be an
// RSA key. (c.f. http://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html
// in the "Key Transport" section)
pemBlock, _ := pem.Decode(key)
if pemBlock == nil {
return nil, fmt.Errorf("Cannot parse key as PEM encoded RSA private key")
}
rsaPriv, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, err
}
// The only supported/required algorithm is SHA1
// (c.f. http://www.w3.org/TR/2001/PR-xmldsig-core-20010820/ section "Algorithms")
//
// TODO(ross): if RSA-PKCS1v15 is used, do we need to specify the digest algorithm?
var hashFunc hash.Hash
switch encryptedKey.EncryptionMethod.DigestMethod.Algorithm {
case "http://www.w3.org/2000/09/xmldsig#sha1":
hashFunc = sha1.New()
default:
return nil, fmt.Errorf("unsupported digest method: %s",
encryptedKey.EncryptionMethod.DigestMethod.Algorithm)
}
sessionKeyCiphertext, err := base64.StdEncoding.DecodeString(encryptedKey.CipherData.CipherValue)
if err != nil {
return nil, err
}
var sessionKeyPlaintext []byte
switch encryptedKey.EncryptionMethod.Algorithm {
case "http://www.w3.org/2001/04/xmlenc#rsa-1_5":
sessionKeyPlaintext, err = rsa.DecryptPKCS1v15(rand.Reader, rsaPriv,
sessionKeyCiphertext)
if err != nil {
return nil, err
}
case "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p":
sessionKeyPlaintext, err = rsa.DecryptOAEP(hashFunc, rand.Reader,
rsaPriv, sessionKeyCiphertext, nil)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported encryption method: %s",
encryptedKey.EncryptionMethod.Algorithm)
}
return sessionKeyPlaintext, nil
}
*/

151
xmlsec.go Normal file
View File

@@ -0,0 +1,151 @@
package xmlsec
import (
"errors"
"unsafe"
)
// Note: on mac you need:
// brew install libxmlsec1 libxml2
// brew link libxml2 --force
// #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>
import "C"
// #cgo pkg-config: libxml-2.0
// #include <libxml/parser.h>
// #include <libxml/parserInternals.h>
// #include <libxml/xmlmemory.h>
//
// // xmlFree is a macro, so we need to wrap it in order to be able to call
// // it from go code.
// static inline void MY_xmlFree(void *p) {
// xmlFree(p);
// }
import "C"
// 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 newDoc2(buf []byte, opts DsigOptions) (*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.xmlCtxtUseDsigOptions(ctx, C.int(p.DsigOptions))
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 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)))
}