Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Manifest support according to the W3 standard #84

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,73 @@ func validate(root *x509.Certificate, el *etree.Element) {
}
```

### Working with Manifest

```go
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](https://github.com/russellhaering/gosaml2)
Expand Down
2 changes: 1 addition & 1 deletion etreeutils/canonicalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func transformExcC14n(ctx, declared NSContext, el *etree.Element, inclusiveNames
}

visiblyUtilizedPrefixes := map[string]struct{}{
el.Space: struct{}{},
el.Space: {},
}

filteredAttrs := []etree.Attr{}
Expand Down
6 changes: 2 additions & 4 deletions etreeutils/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package etreeutils

import (
"errors"

"fmt"

"sort"

"github.com/beevik/etree"
Expand Down Expand Up @@ -302,7 +300,7 @@ func NSFindOne(el *etree.Element, namespace, tag string) (*etree.Element, error)
func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
var found *etree.Element

err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
err := NSFindIterateCtx(ctx, el, namespace, tag, func(_ NSContext, el *etree.Element) error {
found = el
return ErrTraversalHalted
})
Expand Down Expand Up @@ -376,7 +374,7 @@ func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, e
func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
var found *etree.Element

err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(_ NSContext, el *etree.Element) error {
found = el
return ErrTraversalHalted
})
Expand Down
6 changes: 3 additions & 3 deletions etreeutils/unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
// NSUnmarshalElement unmarshals the passed etree Element into the value pointed to by
// v using encoding/xml in the context of the passed NSContext. If v implements
// ElementKeeper, SetUnderlyingElement will be called on v with a reference to el.
func NSUnmarshalElement(ctx NSContext, el *etree.Element, v interface{}) error {
func NSUnmarshalElement(ctx NSContext, root, el *etree.Element, v interface{}) error {
detatched, err := NSDetatch(ctx, el)
if err != nil {
return err
Expand All @@ -29,7 +29,7 @@ func NSUnmarshalElement(ctx NSContext, el *etree.Element, v interface{}) error {

switch v := v.(type) {
case ElementKeeper:
v.SetUnderlyingElement(el)
v.SetUnderlyingElement(root, el)
}

return nil
Expand All @@ -38,6 +38,6 @@ func NSUnmarshalElement(ctx NSContext, el *etree.Element, v interface{}) error {
// ElementKeeper should be implemented by types which will be passed to
// UnmarshalElement, but wish to keep a reference
type ElementKeeper interface {
SetUnderlyingElement(*etree.Element)
SetUnderlyingElement(root, el *etree.Element)
UnderlyingElement() *etree.Element
}
146 changes: 146 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package dsig

import (
"crypto"
"crypto/x509"
"testing"

"github.com/beevik/etree"
"github.com/stretchr/testify/require"
)

func TestDocumentedExample(t *testing.T) {

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

dataValue := elementToSign.CreateElement("XData")
dataValue.CreateAttr("kind", "test")
dataValue.SetText("zip: 586a6289e2ff09b0826dd1daeab5237735a3a728afc48d11976bbed1fbaeaf0a")

// Sign the element
signedElement, err := ctx.SignEnveloped(elementToSign)
require.NoError(t, err)

// Validate
_, certData, err := ctx.KeyStore.GetKeyPair()
require.NoError(t, err)

cert, err := x509.ParseCertificate(certData)
require.NoError(t, err)

// Construct a signing context with one or more roots of trust.
vctx := NewDefaultValidationContext(&MemoryX509CertificateStore{
Roots: []*x509.Certificate{cert},
})

// It is important to only use the returned validated element.
// See: https://www.w3.org/TR/xmldsig-bestpractices/#check-what-is-signed
validated, err := vctx.Validate(signedElement)
require.NoError(t, err)
require.NotEmpty(t, validated)
}

func TestManifestExample(t *testing.T) {

// Generate a key and self-signed certificate for signing
randomKeyStore := RandomKeyStoreForTest()
ctx := NewDefaultSigningContext(randomKeyStore)

test := []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")
err := ctx.AddManifestRef(sig, "package", crypto.SHA256, test)
require.NoError(t, err)

// Sign the signature
signed, err := ctx.SignManifest(sig)
require.NoError(t, err)

// Validate
_, certData, err := ctx.KeyStore.GetKeyPair()
require.NoError(t, err)

cert, err := x509.ParseCertificate(certData)
require.NoError(t, err)

// Construct a signing context with one or more roots of trust.
vctx := NewDefaultValidationContext(&MemoryX509CertificateStore{
Roots: []*x509.Certificate{cert},
})

// It is important to only use the returned validated element.
// See: https://www.w3.org/TR/xmldsig-bestpractices/#check-what-is-signed
manifest, err := vctx.ValidateManifest(signed)

require.NoError(t, err)
require.NotEmpty(t, manifest)
}

func TestMissingManifest(t *testing.T) {

// Generate a key and self-signed certificate for signing
randomKeyStore := RandomKeyStoreForTest()
ctx := NewDefaultSigningContext(randomKeyStore)

sig := ctx.CreateSignature("id1234")

// Sign the signature
_, err := ctx.SignManifest(sig)
require.Error(t, err)
}

func TestRecursiveSigning(t *testing.T) {

// Generate a key and self-signed certificate for signing
randomKeyStore := RandomKeyStoreForTest()
ctx := NewDefaultSigningContext(randomKeyStore)

test := []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")
err := ctx.AddManifestRef(sig, "package", crypto.SHA256, test)
require.NoError(t, err)

// Sign the signature
signed, err := ctx.SignManifest(sig)
require.NoError(t, err)

list := &etree.Element{Tag: "Signatures"}
list.AddChild(signed)

// create second layer
signed, err = ctx.SignEnveloped(list)
require.NoError(t, err)

// Validate
_, certData, err := ctx.KeyStore.GetKeyPair()
require.NoError(t, err)

cert, err := x509.ParseCertificate(certData)
require.NoError(t, err)

// Construct a signing context with one or more roots of trust.
vctx := NewDefaultValidationContext(&MemoryX509CertificateStore{
Roots: []*x509.Certificate{cert},
})

// It is important to only use the returned validated element.
// See: https://www.w3.org/TR/xmldsig-bestpractices/#check-what-is-signed
manifest, err := vctx.ValidateManifest(signed)

require.NoError(t, err)
require.NotEmpty(t, manifest)
require.Equal(t, len(manifest.References), 1)

hash, digest, err := vctx.DecodeRef(&manifest.References[0])

require.NoError(t, err)
require.Equal(t, digest, test)
require.Equal(t, hash, crypto.SHA256)
}
Loading