/goxmldsig

Pure Go implementation of XML Digital Signatures

Primary LanguageGoApache License 2.0Apache-2.0

goxmldsig

Build Status GoDoc

XML Digital Signatures implemented in pure Go.

Installation

Install goxmldsig using go get:

$ go get github.com/russellhaering/goxmldsig

Usage

Signing

package main

import (
    "github.com/beevik/etree"
    "github.com/russellhaering/goxmldsig"
)

func main() {
    // Generate a key and self-signed certificate for signing
    randomKeyStore := dsig.RandomKeyStoreForTest()
    ctx := dsig.NewDefaultSigningContext(randomKeyStore)
    elementToSign := &etree.Element{
        Tag: "ExampleElement",
    }
    elementToSign.CreateAttr("ID", "id1234")

    // Sign the element
    signedElement, err := ctx.SignEnveloped(elementToSign)
    if err != nil {
        panic(err)
    }

    // Serialize the signed element. It is important not to modify the element
    // after it has been signed - even pretty-printing the XML will invalidate
    // the signature.
    doc := etree.NewDocument()
    doc.SetRoot(signedElement)
    str, err := doc.WriteToString()
    if err != nil {
        panic(err)
    }

    println(str)
}

Signature Validation

// Validate an element against a root certificate
func validate(root *x509.Certificate, el *etree.Element) {
    // Construct a signing context with one or more roots of trust.
    ctx := dsig.NewDefaultValidationContext(&dsig.MemoryX509CertificateStore{
        Roots: []*x509.Certificate{root},
    })

    // It is important to only use the returned validated element.
    // See: https://www.w3.org/TR/xmldsig-bestpractices/#check-what-is-signed
    validated, err := ctx.Validate(el)
    if err != nil {
        panic(err)
    }

    doc := etree.NewDocument()
    doc.SetRoot(validated)
    str, err := doc.WriteToString()
    if err != nil {
        panic(err)
    }

    println(str)
}

Working with Manifest

package main

import (
    "crypto"
    "github.com/beevik/etree"
    "github.com/austdev/goxmldsig"
    "github.com/austdev/goxmldsig/types"
)

func main() {
    // Generate a key and self-signed certificate for signing
    randomKeyStore := dsig.RandomKeyStoreForTest()
    ctx := dsig.NewDefaultSigningContext(randomKeyStore)

    digest := []byte{0x45, 0xf1, 0xab, 0xd7, 0x8a, 0x6f, 0x92, 0xe6, 0xa4, 0xb6, 0x8e, 0xba, 0x8f, 0xe7, 0x91, 0x96, 0xe0, 0xb2, 0x16, 0xd6, 0x0b, 0x82, 0x1b, 0x00, 0x45, 0xfa, 0xb8, 0xad, 0xd4, 0xfa, 0xff, 0xf9}

    sig := ctx.CreateSignature("id1234")

    // Get SHA256 hash of "package" data and add it as a reference
    err := ctx.AddManifestRef(sig, "package", crypto.SHA256, digest)
    if err != nil {
        panic(err)
    }

    // Sign the signature
    signed, err := ctx.SignManifest(sig)
    if err != nil {
        panic(err)
    }

    // Serialize the signature.
    doc := etree.NewDocument()
    doc.SetRoot(signed)
    str, err := doc.WriteToString()
    if err != nil {
        panic(err)
    }

    println(str)
}

// Validate a signature against a root certificate
func validate(root *x509.Certificate, sig *etree.Element) {
    // Construct a signing context with one or more roots of trust.
    ctx := dsig.NewDefaultValidationContext(&dsig.MemoryX509CertificateStore{
        Roots: []*x509.Certificate{root},
    })

    manifest, err := ctx.ValidateManifest(signed)
    if err != nil {
        panic(err)
    }

    for idx := range manifest.References {
        ref := &manifest.References[idx]
        // Pass raw data of "package" for validating
        err := ctx.VerifyReference(ref, test_data)
        if err != nil {
            panic(err)
        }
    }
}

Limitations

This library was created in order to implement SAML 2.0 without needing to execute a command line tool to create and validate signatures. It currently only implements the subset of relevant standards needed to support that implementation, but I hope to make it more complete over time. Contributions are welcome.