goxmldsig

repository·main·Indexed 41 days ago

https://github.com/russellhaering/goxmldsig

A pure Go implementation of XML Digital Signatures, primarily designed to support SAML 2.0 implementations without external command-line dependencies. It provides tools for signing and validating XML elements using SigningContext and ValidationContext, supports various canonicalization algorithms (C14N10 Exclusive, C14N11 Inclusive, C14N10 Recursive), and includes flexible keystore interfaces for managing X.509 certificates and RSA keys.

Tokens
7K
Snippets
28
Records
31
Agent score
51%

What's inside goxmldsig

  1. How SigningContext manages keys and certificates

    main

    The SigningContext uses two mutually exclusive patterns for accessing cryptographic material:

    1. KeyStore Pattern: When KeyStore (an X509KeyStore) is provided, the context retrieves the key pair and the certificate chain directly from the store during the signing process. This is useful for managing multiple keys or complex certificate chains.
    2. Direct Signer Pattern: When NewSigningContext is used, the context relies on a provided crypto.Signer for the actual signing operation and a pre-provided slice of [][]byte for the certificate chain. In this mode, the KeyStore field must be nil.

    Internally, the context uses these sources to populate the <KeyInfo> and <X509Data> sections of the generated XML signature.

  2. Validate an XML signature

    main

    To validate an XML signature, construct a ValidationContext using a certificate store (such as MemoryX509CertificateStore) containing your trusted root certificates. Use the Validate method on the element you wish to verify.

    Important: Only use the *etree.Element returned by the Validate method for further processing to ensure you are working with the verified content.

    // 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)
    }
  3. Sign an XML element

    main

    To sign an XML element, use a SigningContext. You must provide a KeyStore to the context. The SignEnveloped method performs the signing and returns a new *etree.Element containing the signature.

    Important: Do not modify the signed element (including pretty-printing) after signing, as this will invalidate the signature.

    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)
    }
  4. Include Signature types in your application

    main

    To use XML digital signatures within your own data structures, include the types.Signature struct from the github.com/russellhaering/goxmldsig/types package.

    import (
        sigtypes "github.com/russellhaering/goxmldsig/types"
    )
    
    type AppHdr struct {
        ...
        Signature *sigtypes.Signature
    }
  5. Handle signature validation errors

    main

    When calling Validate, you may encounter several specific error conditions:

    • ErrMissingSignature: No signature was found that references the top-level element provided.
    • ErrInvalidSignature: The signature structure is invalid (e.g., incorrect number of children in the Signature element).
    • Other errors may include certificate verification failures (e.g., "Could not verify certificate against trusted certs" or "Cert is not valid at this time") or digest mismatches ("Signature could not be verified").
  6. Generate a random key store for testing with RandomKeyStoreForTest

    main

    RandomKeyStoreForTest is a helper function that returns a X509KeyStore containing a freshly generated 1024-bit RSA key and a self-signed certificate. This is intended for use in unit tests to avoid managing real cryptographic material.

    // Returns a X509KeyStore with a generated RSA key and self-signed cert
    ks := dsig.RandomKeyStoreForTest()
    privateKey, cert, err := ks.GetKeyPair()
  7. Use X509KeyStore for signing keys

    main

    The X509KeyStore interface is used to retrieve the RSA private key and the associated certificate required for XML digital signatures. Implement this interface if you need to provide keys from a custom source (like a HSM or a file system) to the goxmldsig signing processes.

    type X509KeyStore interface {
    	GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error)
    }
  8. Use the Clock type for time-based operations

    main

    The Clock type provides a wrapper around clockwork.Clock to facilitate time-based operations like Now(), Sleep(), and After(). A key feature of this implementation is that it defaults to a real system clock if a nil *Clock is used, allowing it to be added to existing structs without breaking all existing instantiations.

    Methods available on *Clock:

    • Now() time.Time: Returns the current time.
    • Sleep(d time.Duration): Suspends execution for the specified duration.
    • After(d time.Duration) <-chan time.Time: Returns a channel that sends a signal after the specified duration.
    // Example of using the Clock methods
    var c *dsig.Clock // nil clock defaults to real clock
    
    now := c.Now()
    c.Sleep(time.Second)
    <-c.After(time.Minute)
  9. Initialize a SigningContext for XML signatures

    main

    The SigningContext is the primary object used to manage the state and configuration required to create XML digital signatures. You can initialize it in two ways depending on how you manage your keys:

    1. Using an X509KeyStore: Use NewDefaultSigningContext(ks X509KeyStore) if you want the context to manage your keys and certificate chains via a keystore implementation.
    2. Using a crypto.Signer: Use NewSigningContext(signer crypto.Signer, certs [][]byte) if you already have a signer (like an rsa.PrivateKey or ecdsa.PrivateKey) and a slice of ASN.1 DER-encoded X.509 certificates.

    Note: If you use NewSigningContext, the KeyStore field in the resulting struct should remain nil and should not be used.

    // Option 1: Using a KeyStore
    ks := // ... your X509KeyStore implementation
    ctx := dsig.NewDefaultSigningContext(ks)
    
    // Option 2: Using an existing signer and certs
    signer := // ... an object implementing crypto.Signer
    certs := [][]byte{ /* DER encoded certs */ }
    ctx, err := dsig.NewSigningContext(signer, certs)
  10. Initialize a ValidationContext for XML signature verification

    main

    To verify XML digital signatures, you must use a ValidationContext. This context manages the trusted certificate store, the ID attribute used to identify signed elements, and the clock used for validity checks. Use NewDefaultValidationContext to create a context with default settings (using DefaultIdAttr as the ID attribute).

    // Assuming you have implemented an X509CertificateStore
    ctx := dsig.NewDefaultValidationContext(myCertificateStore)
  11. Create a C14N11 Inclusive Canonicalizer

    main

    The C14N11 canonicalizer implements the XML 1.1 inclusive canonicalization algorithm. This is used when the element should be canonicalized including its full namespace context.

    Use MakeC14N11Canonicalizer() for standard C14N11, or MakeC14N11WithCommentsCanonicalizer() to include XML comments.

    // Standard C14N11
    canon := dsig.MakeC14N11Canonicalizer()
    
    // C14N11 including comments
    canonWithComments := dsig.MakeC14N11WithCommentsCanonicalizer()
    
    bytes, err := canon.Canonicalize(element)