progress: enable xmldsig and do other cleanups
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
113
encrypt.go
113
encrypt.go
@@ -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
@@ -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);
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -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
157
xmldsig.go
Normal 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
280
xmldsig_test.go
Normal file
File diff suppressed because one or more lines are too long
215
xmlenc.go
215
xmlenc.go
@@ -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
151
xmlsec.go
Normal 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)))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user