Install goxmldsig
mainInstall the goxmldsig package using go get:
$ go get github.com/russellhaering/goxmldsigrepository·main·Indexed 41 days ago
https://github.com/russellhaering/goxmldsigA 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.
Install the goxmldsig package using go get:
$ go get github.com/russellhaering/goxmldsigThe SigningContext uses two mutually exclusive patterns for accessing cryptographic material:
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.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.
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)
}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)
}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
}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)."Could not verify certificate against trusted certs" or "Cert is not valid at this time") or digest mismatches ("Signature could not be verified").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()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)
}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)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:
X509KeyStore: Use NewDefaultSigningContext(ks X509KeyStore) if you want the context to manage your keys and certificate chains via a keystore implementation.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)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)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)