...

Source file src/github.com/sassoftware/relic/lib/xmldsig/canonicalize.go

Documentation: github.com/sassoftware/relic/lib/xmldsig

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package xmldsig
    18  
    19  import (
    20  	"sort"
    21  
    22  	"github.com/beevik/etree"
    23  )
    24  
    25  // Canonicalize a document starting from the given element and return the
    26  // serialized bytes. Implements something vaguely like xml-exc-c14n. Namespaces
    27  // declared in parent nodes are pulled in, and namespaces not used in the
    28  // element where they are declared are pushed further down to the elements that
    29  // use them.
    30  //
    31  // This is not a standards-conforming implementation. Use at your own peril.
    32  func SerializeCanonical(oldroot *etree.Element) ([]byte, error) {
    33  	// make a deep copy before mangling things
    34  	root := oldroot.Copy()
    35  	// remake the document without any xml declarations etc.
    36  	doc := etree.NewDocument()
    37  	doc.SetRoot(root)
    38  	doc.WriteSettings.CanonicalEndTags = true
    39  	doc.WriteSettings.CanonicalText = true
    40  	doc.WriteSettings.CanonicalAttrVal = true
    41  	pullDown(oldroot, root)
    42  	walkAttributes(root)
    43  	return doc.WriteToBytes()
    44  }
    45  
    46  // if the attribute is a namespace declaration then return the namespace
    47  func getDecl(attr etree.Attr) (string, bool) {
    48  	if attr.Space == "" && attr.Key == "xmlns" {
    49  		return "", true
    50  	} else if attr.Space == "xmlns" {
    51  		return attr.Key, true
    52  	} else {
    53  		return "", false
    54  	}
    55  }
    56  
    57  // attribute name for declaring the given namespace
    58  func putDecl(space string) string {
    59  	if space == "" {
    60  		return "xmlns"
    61  	}
    62  	return "xmlns:" + space
    63  }
    64  
    65  func walkAttributes(elem *etree.Element) {
    66  	// remove unused spaces and push ones this element doesn't use down to child elements
    67  	for i := 0; i < len(elem.Attr); {
    68  		attr := elem.Attr[i]
    69  		if space, isDecl := getDecl(attr); isDecl && !usesSpace(elem, space) {
    70  			pushDown(elem, elem, space, putDecl(space), attr.Value)
    71  			elem.Attr = append(elem.Attr[:i], elem.Attr[i+1:]...)
    72  			continue
    73  		}
    74  		i++
    75  	}
    76  	sort.Slice(elem.Attr, func(i, j int) bool {
    77  		x := elem.Attr[i]
    78  		y := elem.Attr[j]
    79  		// default namespace node sorts first
    80  		if x.Space == "" && x.Key == "xmlns" {
    81  			return true
    82  		} else if y.Space == "" && y.Key == "xmlns" {
    83  			return false
    84  		}
    85  		// then all other namespace nodes
    86  		if x.Space == "xmlns" && y.Space != "xmlns" {
    87  			return true
    88  		} else if y.Space == "xmlns" && x.Space != "xmlns" {
    89  			return false
    90  		}
    91  		// then order by namespace and finally by key
    92  		if x.Space != y.Space {
    93  			return x.Space < y.Space
    94  		}
    95  		return x.Key < y.Key
    96  	})
    97  	for i := 0; i < len(elem.Child); {
    98  		token := elem.Child[i]
    99  		switch t := token.(type) {
   100  		case *etree.Element:
   101  			walkAttributes(t)
   102  		case *etree.CharData:
   103  			// keep
   104  		default:
   105  			// remove
   106  			elem.Child = append(elem.Child[:i], elem.Child[i+1:]...)
   107  			continue
   108  		}
   109  		i++
   110  	}
   111  }
   112  
   113  // does this element or its attributes reference the given namespace?
   114  func usesSpace(elem *etree.Element, space string) bool {
   115  	if elem.Space == space {
   116  		return true
   117  	} else if space == "" {
   118  		// if the element doesn't use the default namespace, then neither do the attributes
   119  		return false
   120  	}
   121  	for _, attr := range elem.Attr {
   122  		if attr.Space == space {
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  // if the root element used to be the child of another element, pull down
   130  // namespaces that were declared in its ancestors
   131  func pullDown(oldroot, newroot *etree.Element) {
   132  	spaces := make(map[string]string)
   133  	for p := oldroot.Parent(); p != nil; p = p.Parent() {
   134  		for _, attr := range p.Attr {
   135  			space, isDecl := getDecl(attr)
   136  			if !isDecl {
   137  				continue
   138  			}
   139  			if spaces[space] != "" {
   140  				continue
   141  			}
   142  			spaces[space] = attr.Value
   143  		}
   144  	}
   145  	for space, value := range spaces {
   146  		pushDown(nil, newroot, space, putDecl(space), value)
   147  	}
   148  }
   149  
   150  // add a namespace to child elements that need it
   151  func pushDown(top, elem *etree.Element, space, key, value string) {
   152  	if elem != top && elem.SelectAttr(key) != nil {
   153  		// redeclared here already
   154  		return
   155  	} else if usesSpace(elem, space) {
   156  		// used here, declare it here
   157  		elem.CreateAttr(key, value)
   158  	} else {
   159  		// recurse further
   160  		for _, elem := range elem.ChildElements() {
   161  			pushDown(top, elem, space, key, value)
   162  		}
   163  	}
   164  }
   165  

View as plain text