package xmlsec import ( "errors" "unsafe" ) /* #include #include #include #include #include #include #include #include void xmlSecFindNodes(const xmlListPtr found, const xmlNodePtr parent, const xmlChar *name, const xmlChar *ns) { xmlNodePtr cur; xmlNodePtr ret; xmlSecAssert2(name != NULL, NULL); cur = parent; while(cur != NULL) { if(cur->children != NULL) { xmlSecFindNodes(found, cur->children, name, ns); } if((cur->type == XML_ELEMENT_NODE) && xmlSecCheckNodeName(cur, name, ns)) { xmlListPushFront(found, cur); } cur = cur->next; } } */ import "C" // SignatureOptions represents additional, less commonly used, options for Sign and // Verify type SignatureOptions 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 } // XMLIDOption represents the definition of an XML reference element // (See http://www.w3.org/TR/xml-id/) type XMLIDOption struct { ElementName string ElementNamespace string AttributeName string } // Sign returns a version of doc signed with key according to // the XMLDSIG standard. doc is a template document meaning // that it contains an `http://www.w3.org/2000/09/xmldsig#Signature` // element whose properties define how and what to sign. func Sign(key []byte, doc []byte, opts SignatureOptions) ([]byte, error) { startProcessingXML() defer stopProcessingXML() parsedDoc, err := newDoc(doc, opts.XMLID) if err != nil { return nil, err } defer closeDoc(parsedDoc) // #nosec found := C.xmlListCreate(nil, nil) defer func() { C.xmlListDelete(found) }() C.xmlSecFindNodes( found, C.xmlDocGetRootElement(parsedDoc), (*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeSignature)), (*C.xmlChar)(unsafe.Pointer(&C.xmlSecDSigNs))) c := C.xmlListSize(found) if c == 0 { return nil, errors.New("cannot find start node") } for C.xmlListEmpty(found) == 0 { link := C.xmlListFront(found) if link == nil { return nil, errors.New("Link is null") } node := (C.xmlNodePtr)(C.xmlLinkGetData(link)) if node != nil { ctx := C.xmlSecDSigCtxCreate(nil) if ctx == nil { return nil, errors.New("failed to create signature context") } defer C.xmlSecDSigCtxDestroy(ctx) // #nosec 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") } if rv := C.xmlSecDSigCtxSign(ctx, node); rv < 0 { return nil, errors.New("failed to sign") } } C.xmlListPopFront(found) } return dumpDoc(parsedDoc), nil } // ErrVerificationFailed is returned from Verify when the signature is incorrect var ErrVerificationFailed = errors.New("signature verification failed") // values returned from xmlSecDSigCtxVerify const ( xmlSecDSigStatusUnknown = 0 xmlSecDSigStatusSucceeded = 1 xmlSecDSigStatusInvalid = 2 ) // Verify checks that the signature in doc is valid according // to the XMLDSIG specification. publicKey is the public part of // the key used to sign doc. If the signature is not correct, // this function returns ErrVerificationFailed. func Verify(publicKey []byte, doc []byte, opts SignatureOptions) error { startProcessingXML() defer stopProcessingXML() keysMngr := C.xmlSecKeysMngrCreate() if keysMngr == nil { return mustPopError() } defer C.xmlSecKeysMngrDestroy(keysMngr) if rv := C.xmlSecCryptoAppDefaultKeysMngrInit(keysMngr); rv < 0 { return mustPopError() } // #nosec key := C.xmlSecCryptoAppKeyLoadMemory( (*C.xmlSecByte)(unsafe.Pointer(&publicKey[0])), C.xmlSecSize(len(publicKey)), C.xmlSecKeyDataFormatCertPem, nil, nil, nil) if key == nil { return mustPopError() } // #nosec if rv := C.xmlSecCryptoAppKeyCertLoadMemory(key, (*C.xmlSecByte)(unsafe.Pointer(&publicKey[0])), C.xmlSecSize(len(publicKey)), C.xmlSecKeyDataFormatCertPem); rv < 0 { C.xmlSecKeyDestroy(key) return mustPopError() } if rv := C.xmlSecCryptoAppDefaultKeysMngrAdoptKey(keysMngr, key); rv < 0 { return mustPopError() } parsedDoc, err := newDoc(doc, opts.XMLID) if err != nil { return err } defer closeDoc(parsedDoc) // #nosec found := C.xmlListCreate(nil, nil) defer func() { C.xmlListDelete(found) }() C.xmlSecFindNodes( found, C.xmlDocGetRootElement(parsedDoc), (*C.xmlChar)(unsafe.Pointer(&C.xmlSecNodeSignature)), (*C.xmlChar)(unsafe.Pointer(&C.xmlSecDSigNs))) c := C.xmlListSize(found) if c == 0 { return errors.New("cannot find start node") } for C.xmlListEmpty(found) == 0 { link := C.xmlListFront(found) if link == nil { break } node := (C.xmlNodePtr)(C.xmlLinkGetData(link)) if node != nil { ctx := C.xmlSecDSigCtxCreate(keysMngr) if ctx == nil { return mustPopError() } defer C.xmlSecDSigCtxDestroy(ctx) if rv := C.xmlSecDSigCtxVerify(ctx, node); rv < 0 { return ErrVerificationFailed } if ctx.status != xmlSecDSigStatusSucceeded { return ErrVerificationFailed } } C.xmlListPopFront(found) } return nil }