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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed XML")
|
||||
return nil, err
|
||||
}
|
||||
defer closeDoc(parsedDoc)
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
|
||||
|
||||
c.Assert(Cert, Not(IsNil))
|
||||
|
||||
/* XXX
|
||||
err = Verify(Cert, actualPlaintextString, DsigOptions{
|
||||
XMLID: []XMLIDOption{{
|
||||
ElementName: "Assertion",
|
||||
@@ -114,7 +113,6 @@ Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
|
||||
AttributeName: "ID",
|
||||
}}})
|
||||
c.Assert(err, IsNil)
|
||||
*/
|
||||
}
|
||||
|
||||
func (testSuite *DecryptTest) TestDecryptWithPadding(c *C) {
|
||||
|
||||
113
encrypt.go
113
encrypt.go
@@ -1,123 +1,30 @@
|
||||
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;
|
||||
// }
|
||||
// // Note: the xmlSecKeyData*Id itentifiers are macros, so we need to wrap them
|
||||
// // 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 (
|
||||
"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
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,10 @@
|
||||
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) {
|
||||
// onError(file, line, funcName, errorObject, errorSubject, reason, msg);
|
||||
// }
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/crewjam/go-xmlsec/xmldsig"
|
||||
"github.com/crewjam/go-xmlsec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -33,7 +33,7 @@ func main() {
|
||||
buf, err := ioutil.ReadAll(os.Stdin)
|
||||
|
||||
if *doSign {
|
||||
signedBuf, err := xmldsig.Sign(key, buf, xmldsig.Options{})
|
||||
signedBuf, err := xmlsec.Sign(key, buf, xmlsec.DsigOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
os.Exit(1)
|
||||
@@ -42,8 +42,8 @@ func main() {
|
||||
}
|
||||
|
||||
if *doVerify {
|
||||
err := xmldsig.Verify(key, buf, xmldsig.Options{})
|
||||
if err == xmldsig.ErrVerificationFailed {
|
||||
err := xmlsec.Verify(key, buf, xmlsec.DsigOptions{})
|
||||
if err == xmlsec.ErrVerificationFailed {
|
||||
fmt.Println("signature is not correct")
|
||||
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