add support for specifying xml:id
This commit is contained in:
@@ -39,7 +39,26 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDoc(buf []byte) (*C.xmlDoc, error) {
|
// 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])),
|
ctx := C.xmlCreateMemoryParserCtxt((*C.char)(unsafe.Pointer(&buf[0])),
|
||||||
C.int(len(buf)))
|
C.int(len(buf)))
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
@@ -58,9 +77,50 @@ func newDoc(buf []byte) (*C.xmlDoc, error) {
|
|||||||
if doc == nil {
|
if doc == nil {
|
||||||
return nil, errors.New("parse failed")
|
return nil, errors.New("parse failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, idattr := range opts.XMLID {
|
||||||
|
if err := addIDAttr(C.xmlDocGetRootElement(doc),
|
||||||
|
idattr.AttributeName, idattr.ElementName, idattr.ElementNamespace); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return doc, nil
|
return doc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addIDAttr(node *C.xmlNode, attrName, nodeName, nsHref string) error {
|
||||||
|
// process children first because it does not matter much but does simplify code
|
||||||
|
cur := C.xmlSecGetNextElementNode(node.children)
|
||||||
|
for {
|
||||||
|
if cur == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := addIDAttr(cur, attrName, nodeName, nsHref); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cur = C.xmlSecGetNextElementNode(cur.next)
|
||||||
|
}
|
||||||
|
|
||||||
|
if C.GoString((*C.char)(unsafe.Pointer(node.name))) != nodeName {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if nsHref != "" && node.ns != nil && C.GoString((*C.char)(unsafe.Pointer(node.ns.href))) != nsHref {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 nil
|
||||||
|
}
|
||||||
|
|
||||||
func dumpDoc(doc *C.xmlDoc) []byte {
|
func dumpDoc(doc *C.xmlDoc) []byte {
|
||||||
var buffer *C.xmlChar
|
var buffer *C.xmlChar
|
||||||
var bufferSize C.int
|
var bufferSize C.int
|
||||||
@@ -81,7 +141,7 @@ func closeDoc(doc *C.xmlDoc) {
|
|||||||
// the XML-DSIG standard. docStr is a template document meaning
|
// the XML-DSIG standard. docStr is a template document meaning
|
||||||
// that it contains a `Signature` element in the
|
// that it contains a `Signature` element in the
|
||||||
// http://www.w3.org/2000/09/xmldsig# namespace.
|
// http://www.w3.org/2000/09/xmldsig# namespace.
|
||||||
func Sign(key []byte, doc []byte) ([]byte, error) {
|
func Sign(key []byte, doc []byte, opts Options) ([]byte, error) {
|
||||||
ctx := C.xmlSecDSigCtxCreate(nil)
|
ctx := C.xmlSecDSigCtxCreate(nil)
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return nil, errors.New("failed to create signature context")
|
return nil, errors.New("failed to create signature context")
|
||||||
@@ -97,7 +157,7 @@ func Sign(key []byte, doc []byte) ([]byte, error) {
|
|||||||
return nil, errors.New("failed to load pem key")
|
return nil, errors.New("failed to load pem key")
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedDoc, err := newDoc(doc)
|
parsedDoc, err := newDoc(doc, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -129,8 +189,8 @@ const (
|
|||||||
// Verify checks that the signature in docStr is valid according
|
// Verify checks that the signature in docStr is valid according
|
||||||
// to the XML-DSIG specification. publicKey is the public part of
|
// to the XML-DSIG specification. publicKey is the public part of
|
||||||
// the key used to sign docStr. If the signature is not correct,
|
// the key used to sign docStr. If the signature is not correct,
|
||||||
// this function returns ErrVarificationFailed.
|
// this function returns ErrVerificationFailed.
|
||||||
func Verify(publicKey []byte, doc []byte) error {
|
func Verify(publicKey []byte, doc []byte, opts Options) error {
|
||||||
keysMngr := C.xmlSecKeysMngrCreate()
|
keysMngr := C.xmlSecKeysMngrCreate()
|
||||||
if keysMngr == nil {
|
if keysMngr == nil {
|
||||||
return fmt.Errorf("xmlSecKeysMngrCreate failed")
|
return fmt.Errorf("xmlSecKeysMngrCreate failed")
|
||||||
@@ -168,7 +228,7 @@ func Verify(publicKey []byte, doc []byte) error {
|
|||||||
}
|
}
|
||||||
defer C.xmlSecDSigCtxDestroy(dsigCtx)
|
defer C.xmlSecDSigCtxDestroy(dsigCtx)
|
||||||
|
|
||||||
parsedDoc, err := newDoc(doc)
|
parsedDoc, err := newDoc(doc, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ fBjXssrERn05kpBcrRfzou4r3DCgQFPhjxga</X509Certificate>
|
|||||||
</Signature>
|
</Signature>
|
||||||
</Envelope>
|
</Envelope>
|
||||||
`
|
`
|
||||||
actualSignedString, err := Sign(key, docStr)
|
actualSignedString, err := Sign(key, docStr, Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("sign: %s", err)
|
t.Errorf("sign: %s", err)
|
||||||
return
|
return
|
||||||
@@ -162,7 +162,7 @@ fBjXssrERn05kpBcrRfzou4r3DCgQFPhjxga</X509Certificate>
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("marshal: %s", err)
|
t.Errorf("marshal: %s", err)
|
||||||
}
|
}
|
||||||
actualSignedString, err = Sign(key, docStr)
|
actualSignedString, err = Sign(key, docStr, Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("sign: %s", err)
|
t.Errorf("sign: %s", err)
|
||||||
return
|
return
|
||||||
@@ -198,13 +198,13 @@ vCfqu9yXDYKVOBI57F0Efg==</SignatureValue>
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Verify(cert, actualSignedString); err != nil {
|
if err := Verify(cert, actualSignedString, Options{}); err != nil {
|
||||||
t.Errorf("verify: %s", err)
|
t.Errorf("verify: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
brokenDoc := strings.Replace(expectedSignedString, "Hello", "Goodbye", 1)
|
brokenDoc := strings.Replace(expectedSignedString, "Hello", "Goodbye", 1)
|
||||||
err = Verify(cert, []byte(brokenDoc))
|
err = Verify(cert, []byte(brokenDoc), Options{})
|
||||||
if err != ErrVerificationFailed {
|
if err != ErrVerificationFailed {
|
||||||
t.Errorf("verify: expected verification failed, got %s", err)
|
t.Errorf("verify: expected verification failed, got %s", err)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user