vendor: revendor

This commit is contained in:
Eric Chiang
2017-01-09 14:51:47 -08:00
parent 78665074ed
commit 7ea2d24011
15 changed files with 3247 additions and 2 deletions

175
vendor/github.com/russellhaering/goxmldsig/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,175 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@@ -0,0 +1,251 @@
package dsig
import (
"sort"
"strings"
"github.com/beevik/etree"
)
// Canonicalizer is an implementation of a canonicalization algorithm.
type Canonicalizer interface {
Canonicalize(el *etree.Element) ([]byte, error)
Algorithm() AlgorithmID
}
type c14N10ExclusiveCanonicalizer struct {
InclusiveNamespaces map[string]struct{}
}
// MakeC14N10ExclusiveCanonicalizerWithPrefixList constructs an exclusive Canonicalizer
// from a PrefixList in NMTOKENS format (a white space separated list).
func MakeC14N10ExclusiveCanonicalizerWithPrefixList(prefixList string) Canonicalizer {
prefixes := strings.Fields(prefixList)
prefixSet := make(map[string]struct{}, len(prefixes))
for _, prefix := range prefixes {
prefixSet[prefix] = struct{}{}
}
return &c14N10ExclusiveCanonicalizer{
InclusiveNamespaces: prefixSet,
}
}
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
func (c *c14N10ExclusiveCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
scope := make(map[string]c14nSpace)
return canonicalSerialize(excCanonicalPrep(el, scope, c.InclusiveNamespaces))
}
func (c *c14N10ExclusiveCanonicalizer) Algorithm() AlgorithmID {
return CanonicalXML10ExclusiveAlgorithmId
}
type c14N11Canonicalizer struct{}
// MakeC14N11Canonicalizer constructs an inclusive canonicalizer.
func MakeC14N11Canonicalizer() Canonicalizer {
return &c14N11Canonicalizer{}
}
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
func (c *c14N11Canonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
scope := make(map[string]struct{})
return canonicalSerialize(canonicalPrep(el, scope))
}
func (c *c14N11Canonicalizer) Algorithm() AlgorithmID {
return CanonicalXML11AlgorithmId
}
func composeAttr(space, key string) string {
if space != "" {
return space + ":" + key
}
return key
}
type attrsByKey []etree.Attr
func (a attrsByKey) Len() int {
return len(a)
}
func (a attrsByKey) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a attrsByKey) Less(i, j int) bool {
// As I understand it: any "xmlns" attribute should come first, followed by any
// any "xmlns:prefix" attributes, presumably ordered by prefix. Lastly any other
// attributes in lexicographical order.
if a[i].Space == "" && a[i].Key == "xmlns" {
return true
}
if a[i].Space == "xmlns" {
if a[j].Space == "xmlns" {
return a[i].Key < a[j].Key
}
return true
}
if a[j].Space == "xmlns" {
return false
}
return composeAttr(a[i].Space, a[i].Key) < composeAttr(a[j].Space, a[j].Key)
}
type c14nSpace struct {
a etree.Attr
used bool
}
const nsSpace = "xmlns"
// excCanonicalPrep accepts an *etree.Element and recursively transforms it into one
// which is ready for serialization to exclusive canonical form. Specifically this
// entails:
//
// 1. Stripping re-declarations of namespaces
// 2. Stripping unused namespaces
// 3. Sorting attributes into canonical order.
//
// NOTE(russell_h): Currently this function modifies the passed element.
func excCanonicalPrep(el *etree.Element, _nsAlreadyDeclared map[string]c14nSpace, inclusiveNamespaces map[string]struct{}) *etree.Element {
//Copy alreadyDeclared map (only contains namespaces)
nsAlreadyDeclared := make(map[string]c14nSpace, len(_nsAlreadyDeclared))
for k := range _nsAlreadyDeclared {
nsAlreadyDeclared[k] = _nsAlreadyDeclared[k]
}
//Track the namespaces used on the current element
nsUsedHere := make(map[string]struct{})
//Make sure to track the element namespace for the case:
//<foo:bar xmlns:foo="..."/>
if el.Space != "" {
nsUsedHere[el.Space] = struct{}{}
}
toRemove := make([]string, 0, 0)
for _, a := range el.Attr {
switch a.Space {
case nsSpace:
//For simplicity, remove all xmlns attribues; to be added in one pass
//later. Otherwise, we need another map/set to track xmlns attributes
//that we left alone.
toRemove = append(toRemove, a.Space+":"+a.Key)
if _, ok := nsAlreadyDeclared[a.Key]; !ok {
//If we're not tracking ancestor state already for this namespace, add
//it to the map
nsAlreadyDeclared[a.Key] = c14nSpace{a: a, used: false}
}
// This algorithm accepts a set of namespaces which should be treated
// in an inclusive fashion. Specifically that means we should keep the
// declaration of that namespace closest to the root of the tree. We can
// accomplish that be pretending it was used by this element.
_, inclusive := inclusiveNamespaces[a.Key]
if inclusive {
nsUsedHere[a.Key] = struct{}{}
}
default:
//We only track namespaces, so ignore attributes without one.
if a.Space != "" {
nsUsedHere[a.Space] = struct{}{}
}
}
}
//Remove all attributes so that we can add them with much-simpler logic
for _, attrK := range toRemove {
el.RemoveAttr(attrK)
}
//For all namespaces used on the current element, declare them if they were
//not declared (and used) in an ancestor.
for k := range nsUsedHere {
spc := nsAlreadyDeclared[k]
//If previously unused, mark as used
if !spc.used {
el.Attr = append(el.Attr, spc.a)
spc.used = true
//Assignment here is only to update the pre-existing `used` tracking value
nsAlreadyDeclared[k] = spc
}
}
//Canonicalize all children, passing down the ancestor tracking map
for _, child := range el.ChildElements() {
excCanonicalPrep(child, nsAlreadyDeclared, inclusiveNamespaces)
}
//Sort attributes lexicographically
sort.Sort(attrsByKey(el.Attr))
return el.Copy()
}
// canonicalPrep accepts an *etree.Element and transforms it into one which is ready
// for serialization into inclusive canonical form. Specifically this
// entails:
//
// 1. Stripping re-declarations of namespaces
// 2. Sorting attributes into canonical order
//
// Inclusive canonicalization does not strip unused namespaces.
//
// TODO(russell_h): This is very similar to excCanonicalPrep - perhaps they should
// be unified into one parameterized function?
func canonicalPrep(el *etree.Element, seenSoFar map[string]struct{}) *etree.Element {
_seenSoFar := make(map[string]struct{})
for k, v := range seenSoFar {
_seenSoFar[k] = v
}
ne := el.Copy()
sort.Sort(attrsByKey(ne.Attr))
if len(ne.Attr) != 0 {
for _, attr := range ne.Attr {
if attr.Space != nsSpace {
continue
}
key := attr.Space + ":" + attr.Key
if _, seen := _seenSoFar[key]; seen {
ne.RemoveAttr(attr.Space + ":" + attr.Key)
} else {
_seenSoFar[key] = struct{}{}
}
}
}
for i, token := range ne.Child {
childElement, ok := token.(*etree.Element)
if ok {
ne.Child[i] = canonicalPrep(childElement, _seenSoFar)
}
}
return ne
}
func canonicalSerialize(el *etree.Element) ([]byte, error) {
doc := etree.NewDocument()
doc.SetRoot(el)
doc.WriteSettings = etree.WriteSettings{
CanonicalAttrVal: true,
CanonicalEndTags: true,
CanonicalText: true,
}
return doc.WriteToBytes()
}

55
vendor/github.com/russellhaering/goxmldsig/clock.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
package dsig
import (
"time"
"github.com/jonboulle/clockwork"
)
// Clock wraps a clockwork.Clock (which could be real or fake) in order
// to default to a real clock when a nil *Clock is used. In other words,
// if you attempt to use a nil *Clock it will defer to the real system
// clock. This allows Clock to be easily added to structs with methods
// that currently reference the time package, without requiring every
// instantiation of that struct to be updated.
type Clock struct {
wrapped clockwork.Clock
}
func (c *Clock) getWrapped() clockwork.Clock {
if c == nil {
return clockwork.NewRealClock()
}
return c.wrapped
}
func (c *Clock) After(d time.Duration) <-chan time.Time {
return c.getWrapped().After(d)
}
func (c *Clock) Sleep(d time.Duration) {
c.getWrapped().Sleep(d)
}
func (c *Clock) Now() time.Time {
return c.getWrapped().Now()
}
func NewRealClock() *Clock {
return &Clock{
wrapped: clockwork.NewRealClock(),
}
}
func NewFakeClock(wrapped clockwork.Clock) *Clock {
return &Clock{
wrapped: wrapped,
}
}
func NewFakeClockAt(t time.Time) *Clock {
return &Clock{
wrapped: clockwork.NewFakeClockAt(t),
}
}

63
vendor/github.com/russellhaering/goxmldsig/keystore.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
package dsig
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"math/big"
"time"
)
type X509KeyStore interface {
GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error)
}
type X509CertificateStore interface {
Certificates() (roots []*x509.Certificate, err error)
}
type MemoryX509CertificateStore struct {
Roots []*x509.Certificate
}
func (mX509cs *MemoryX509CertificateStore) Certificates() ([]*x509.Certificate, error) {
return mX509cs.Roots, nil
}
type MemoryX509KeyStore struct {
privateKey *rsa.PrivateKey
cert []byte
}
func (ks *MemoryX509KeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
return ks.privateKey, ks.cert, nil
}
func RandomKeyStoreForTest() X509KeyStore {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
now := time.Now()
template := &x509.Certificate{
SerialNumber: big.NewInt(0),
NotBefore: now.Add(-5 * time.Minute),
NotAfter: now.Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{},
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
panic(err)
}
return &MemoryX509KeyStore{
privateKey: key,
cert: cert,
}
}

186
vendor/github.com/russellhaering/goxmldsig/sign.go generated vendored Normal file
View File

@@ -0,0 +1,186 @@
package dsig
import (
"crypto"
"crypto/rand"
"crypto/rsa"
_ "crypto/sha1"
_ "crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"github.com/beevik/etree"
)
type SigningContext struct {
Hash crypto.Hash
KeyStore X509KeyStore
IdAttribute string
Prefix string
Canonicalizer Canonicalizer
}
func NewDefaultSigningContext(ks X509KeyStore) *SigningContext {
return &SigningContext{
Hash: crypto.SHA256,
KeyStore: ks,
IdAttribute: DefaultIdAttr,
Prefix: DefaultPrefix,
Canonicalizer: MakeC14N11Canonicalizer(),
}
}
func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error {
hash, ok := signatureMethodsByIdentifier[algorithmID]
if !ok {
return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID)
}
ctx.Hash = hash
return nil
}
func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) {
canonical, err := ctx.Canonicalizer.Canonicalize(el)
if err != nil {
return nil, err
}
hash := ctx.Hash.New()
_, err = hash.Write(canonical)
if err != nil {
return nil, err
}
return hash.Sum(nil), nil
}
func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) {
digestAlgorithmIdentifier, ok := digestAlgorithmIdentifiers[ctx.Hash]
if !ok {
return nil, errors.New("unsupported hash mechanism")
}
signatureMethodIdentifier, ok := signatureMethodIdentifiers[ctx.Hash]
if !ok {
return nil, errors.New("unsupported signature method")
}
digest, err := ctx.digest(el)
if err != nil {
return nil, err
}
signedInfo := &etree.Element{
Tag: SignedInfoTag,
Space: ctx.Prefix,
}
// /SignedInfo/CanonicalizationMethod
canonicalizationMethod := ctx.createNamespacedElement(signedInfo, CanonicalizationMethodTag)
canonicalizationMethod.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
// /SignedInfo/SignatureMethod
signatureMethod := ctx.createNamespacedElement(signedInfo, SignatureMethodTag)
signatureMethod.CreateAttr(AlgorithmAttr, signatureMethodIdentifier)
// /SignedInfo/Reference
reference := ctx.createNamespacedElement(signedInfo, ReferenceTag)
dataId := el.SelectAttrValue(DefaultIdAttr, "")
if dataId == "" {
return nil, errors.New("Missing data ID")
}
reference.CreateAttr(URIAttr, "#"+dataId)
// /SignedInfo/Reference/Transforms
transforms := ctx.createNamespacedElement(reference, TransformsTag)
if enveloped {
envelopedTransform := ctx.createNamespacedElement(transforms, TransformTag)
envelopedTransform.CreateAttr(AlgorithmAttr, EnvelopedSignatureAltorithmId.String())
}
canonicalizationAlgorithm := ctx.createNamespacedElement(transforms, TransformTag)
canonicalizationAlgorithm.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
// /SignedInfo/Reference/DigestMethod
digestMethod := ctx.createNamespacedElement(reference, DigestMethodTag)
digestMethod.CreateAttr(AlgorithmAttr, digestAlgorithmIdentifier)
// /SignedInfo/Reference/DigestValue
digestValue := ctx.createNamespacedElement(reference, DigestValueTag)
digestValue.SetText(base64.StdEncoding.EncodeToString(digest))
return signedInfo, nil
}
func (ctx *SigningContext) constructSignature(el *etree.Element, enveloped bool) (*etree.Element, error) {
signedInfo, err := ctx.constructSignedInfo(el, enveloped)
if err != nil {
return nil, err
}
sig := &etree.Element{
Tag: SignatureTag,
Space: ctx.Prefix,
}
xmlns := "xmlns"
if ctx.Prefix != "" {
xmlns += ":" + ctx.Prefix
}
sig.CreateAttr(xmlns, Namespace)
sig.Child = append(sig.Child, signedInfo)
// Must propagate down the attributes to the 'SignedInfo' before digesting
for _, attr := range sig.Attr {
signedInfo.CreateAttr(attr.Space+":"+attr.Key, attr.Value)
}
digest, err := ctx.digest(signedInfo)
if err != nil {
return nil, err
}
key, cert, err := ctx.KeyStore.GetKeyPair()
if err != nil {
return nil, err
}
rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest)
if err != nil {
return nil, err
}
signatureValue := ctx.createNamespacedElement(sig, SignatureValueTag)
signatureValue.SetText(base64.StdEncoding.EncodeToString(rawSignature))
keyInfo := ctx.createNamespacedElement(sig, KeyInfoTag)
x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag)
x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag)
x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert))
return sig, nil
}
func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string) *etree.Element {
child := el.CreateElement(tag)
child.Space = ctx.Prefix
return child
}
func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, error) {
sig, err := ctx.constructSignature(el, true)
if err != nil {
return nil, err
}
ret := el.Copy()
ret.Child = append(ret.Child, sig)
return ret, nil
}

View File

@@ -0,0 +1,34 @@
package dsig
import (
"crypto/rsa"
"crypto/tls"
"fmt"
)
//Well-known errors
var (
ErrNonRSAKey = fmt.Errorf("Private key was not RSA")
ErrMissingCertificates = fmt.Errorf("No public certificates provided")
)
//TLSCertKeyStore wraps the stdlib tls.Certificate to return its contained key
//and certs.
type TLSCertKeyStore tls.Certificate
//GetKeyPair implements X509KeyStore using the underlying tls.Certificate
func (d TLSCertKeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
pk, ok := d.PrivateKey.(*rsa.PrivateKey)
if !ok {
return nil, nil, ErrNonRSAKey
}
if len(d.Certificate) < 1 {
return nil, nil, ErrMissingCertificates
}
crt := d.Certificate[0]
return pk, crt, nil
}

397
vendor/github.com/russellhaering/goxmldsig/validate.go generated vendored Normal file
View File

@@ -0,0 +1,397 @@
package dsig
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"regexp"
"github.com/beevik/etree"
)
var uriRegexp = regexp.MustCompile("^#[a-zA-Z_][\\w.-]*$")
type ValidationContext struct {
CertificateStore X509CertificateStore
IdAttribute string
Clock *Clock
}
func NewDefaultValidationContext(certificateStore X509CertificateStore) *ValidationContext {
return &ValidationContext{
CertificateStore: certificateStore,
IdAttribute: DefaultIdAttr,
}
}
// TODO(russell_h): More flexible namespace support. This might barely work.
func inNamespace(el *etree.Element, ns string) bool {
for _, attr := range el.Attr {
if attr.Value == ns {
if attr.Space == "" && attr.Key == "xmlns" {
return el.Space == ""
} else if attr.Space == "xmlns" {
return el.Space == attr.Key
}
}
}
return false
}
func childPath(space, tag string) string {
if space == "" {
return "./" + tag
} else {
return "./" + space + ":" + tag
}
}
// The RemoveElement method on etree.Element isn't recursive...
func recursivelyRemoveElement(tree, el *etree.Element) bool {
if tree.RemoveChild(el) != nil {
return true
}
for _, child := range tree.Child {
if childElement, ok := child.(*etree.Element); ok {
if recursivelyRemoveElement(childElement, el) {
return true
}
}
}
return false
}
// transform applies the passed set of transforms to the specified root element.
//
// The functionality of transform is currently very limited and purpose-specific.
//
// NOTE(russell_h): Ideally this wouldn't mutate the root passed to it, and would
// instead return a copy. Unfortunately copying the tree makes it difficult to
// correctly locate the signature. I'm opting, for now, to simply mutate the root
// parameter.
func (ctx *ValidationContext) transform(root, sig *etree.Element, transforms []*etree.Element) (*etree.Element, Canonicalizer, error) {
if len(transforms) != 2 {
return nil, nil, errors.New("Expected Enveloped and C14N transforms")
}
var canonicalizer Canonicalizer
for _, transform := range transforms {
algo := transform.SelectAttr(AlgorithmAttr)
if algo == nil {
return nil, nil, errors.New("Missing Algorithm attribute")
}
switch AlgorithmID(algo.Value) {
case EnvelopedSignatureAltorithmId:
if !recursivelyRemoveElement(root, sig) {
return nil, nil, errors.New("Error applying canonicalization transform: Signature not found")
}
case CanonicalXML10ExclusiveAlgorithmId:
var prefixList string
ins := transform.FindElement(childPath("", InclusiveNamespacesTag))
if ins != nil {
prefixListEl := ins.SelectAttr(PrefixListAttr)
if prefixListEl != nil {
prefixList = prefixListEl.Value
}
}
canonicalizer = MakeC14N10ExclusiveCanonicalizerWithPrefixList(prefixList)
case CanonicalXML11AlgorithmId:
canonicalizer = MakeC14N11Canonicalizer()
default:
return nil, nil, errors.New("Unknown Transform Algorithm: " + algo.Value)
}
}
if canonicalizer == nil {
return nil, nil, errors.New("Expected canonicalization transform")
}
return root, canonicalizer, nil
}
func (ctx *ValidationContext) digest(el *etree.Element, digestAlgorithmId string, canonicalizer Canonicalizer) ([]byte, error) {
data, err := canonicalizer.Canonicalize(el)
if err != nil {
return nil, err
}
digestAlgorithm, ok := digestAlgorithmsByIdentifier[digestAlgorithmId]
if !ok {
return nil, errors.New("Unknown digest algorithm: " + digestAlgorithmId)
}
hash := digestAlgorithm.New()
_, err = hash.Write(data)
if err != nil {
return nil, err
}
return hash.Sum(nil), nil
}
func (ctx *ValidationContext) verifySignedInfo(signatureElement *etree.Element, canonicalizer Canonicalizer, signatureMethodId string, cert *x509.Certificate, sig []byte) error {
signedInfo := signatureElement.FindElement(childPath(signatureElement.Space, SignedInfoTag))
if signedInfo == nil {
return errors.New("Missing SignedInfo")
}
// Any attributes from the 'Signature' element must be pushed down into the 'SignedInfo' element before it is canonicalized
for _, attr := range signatureElement.Attr {
signedInfo.CreateAttr(attr.Space+":"+attr.Key, attr.Value)
}
// Canonicalize the xml
canonical, err := canonicalizer.Canonicalize(signedInfo)
if err != nil {
return err
}
signatureAlgorithm, ok := signatureMethodsByIdentifier[signatureMethodId]
if !ok {
return errors.New("Unknown signature method: " + signatureMethodId)
}
hash := signatureAlgorithm.New()
_, err = hash.Write(canonical)
if err != nil {
return err
}
hashed := hash.Sum(nil)
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
return errors.New("Invalid public key")
}
// Verify that the private key matching the public key from the cert was what was used to sign the 'SignedInfo' and produce the 'SignatureValue'
err = rsa.VerifyPKCS1v15(pubKey, signatureAlgorithm, hashed[:], sig)
if err != nil {
return err
}
return nil
}
func (ctx *ValidationContext) validateSignature(el *etree.Element, cert *x509.Certificate) (*etree.Element, error) {
el = el.Copy()
// Verify the document minus the signedInfo against the 'DigestValue'
// Find the 'Signature' element
sig := el.FindElement(SignatureTag)
if sig == nil {
return nil, errors.New("Missing Signature")
}
if !inNamespace(sig, Namespace) {
return nil, errors.New("Signature element is in the wrong namespace")
}
// Get the 'SignedInfo' element
signedInfo := sig.FindElement(childPath(sig.Space, SignedInfoTag))
if signedInfo == nil {
return nil, errors.New("Missing SignedInfo")
}
reference := signedInfo.FindElement(childPath(sig.Space, ReferenceTag))
if reference == nil {
return nil, errors.New("Missing Reference")
}
transforms := reference.FindElement(childPath(sig.Space, TransformsTag))
if transforms == nil {
return nil, errors.New("Missing Transforms")
}
uri := reference.SelectAttr("URI")
if uri == nil {
// TODO(russell_h): It is permissible to leave this out. We should be
// able to fall back to finding the referenced element some other way.
return nil, errors.New("Reference is missing URI attribute")
}
if !uriRegexp.MatchString(uri.Value) {
return nil, errors.New("Invalid URI: " + uri.Value)
}
// Get the element referenced in the 'SignedInfo'
referencedElement := el.FindElement(fmt.Sprintf("//[@%s='%s']", ctx.IdAttribute, uri.Value[1:]))
if referencedElement == nil {
return nil, errors.New("Unable to find referenced element: " + uri.Value)
}
// Perform all transformations listed in the 'SignedInfo'
// Basically, this means removing the 'SignedInfo'
transformed, canonicalizer, err := ctx.transform(referencedElement, sig, transforms.ChildElements())
if err != nil {
return nil, err
}
digestMethod := reference.FindElement(childPath(sig.Space, DigestMethodTag))
if digestMethod == nil {
return nil, errors.New("Missing DigestMethod")
}
digestValue := reference.FindElement(childPath(sig.Space, DigestValueTag))
if digestValue == nil {
return nil, errors.New("Missing DigestValue")
}
digestAlgorithmAttr := digestMethod.SelectAttr(AlgorithmAttr)
if digestAlgorithmAttr == nil {
return nil, errors.New("Missing DigestMethod Algorithm attribute")
}
// Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo'
digest, err := ctx.digest(transformed, digestAlgorithmAttr.Value, canonicalizer)
if err != nil {
return nil, err
}
decodedDigestValue, err := base64.StdEncoding.DecodeString(digestValue.Text())
if err != nil {
return nil, err
}
if !bytes.Equal(digest, decodedDigestValue) {
return nil, errors.New("Signature could not be verified")
}
//Verify the signed info
signatureMethod := signedInfo.FindElement(childPath(sig.Space, SignatureMethodTag))
if signatureMethod == nil {
return nil, errors.New("Missing SignatureMethod")
}
signatureMethodAlgorithmAttr := signatureMethod.SelectAttr(AlgorithmAttr)
if digestAlgorithmAttr == nil {
return nil, errors.New("Missing SignatureMethod Algorithm attribute")
}
// Decode the 'SignatureValue' so we can compare against it
signatureValue := sig.FindElement(childPath(sig.Space, SignatureValueTag))
if signatureValue == nil {
return nil, errors.New("Missing SignatureValue")
}
decodedSignature, err := base64.StdEncoding.DecodeString(signatureValue.Text())
if err != nil {
return nil, errors.New("Could not decode signature")
}
// Actually verify the 'SignedInfo' was signed by a trusted source
err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethodAlgorithmAttr.Value, cert, decodedSignature)
if err != nil {
return nil, err
}
return transformed, nil
}
func contains(roots []*x509.Certificate, cert *x509.Certificate) bool {
for _, root := range roots {
if root.Equal(cert) {
return true
}
}
return false
}
func (ctx *ValidationContext) verifyCertificate(el *etree.Element) (*x509.Certificate, error) {
now := ctx.Clock.Now()
el = el.Copy()
idAttr := el.SelectAttr(DefaultIdAttr)
if idAttr == nil || idAttr.Value == "" {
return nil, errors.New("Missing ID attribute")
}
signatureElements := el.FindElements("//" + SignatureTag)
var signatureElement *etree.Element
// Find the Signature element that references the whole Response element
for _, e := range signatureElements {
e2 := e.Copy()
signedInfo := e2.FindElement(childPath(e2.Space, SignedInfoTag))
if signedInfo == nil {
return nil, errors.New("Missing SignedInfo")
}
referenceElement := signedInfo.FindElement(childPath(e2.Space, ReferenceTag))
if referenceElement == nil {
return nil, errors.New("Missing Reference Element")
}
uriAttr := referenceElement.SelectAttr(URIAttr)
if uriAttr == nil || uriAttr.Value == "" {
return nil, errors.New("Missing URI attribute")
}
if uriAttr.Value[1:] == idAttr.Value {
signatureElement = e
break
}
}
if signatureElement == nil {
return nil, errors.New("Missing signature referencing the top-level element")
}
// Get the x509 element from the signature
x509Element := signatureElement.FindElement("//" + childPath(signatureElement.Space, X509CertificateTag))
if x509Element == nil {
return nil, errors.New("Missing x509 Element")
}
x509Text := "-----BEGIN CERTIFICATE-----\n" + x509Element.Text() + "\n-----END CERTIFICATE-----"
block, _ := pem.Decode([]byte(x509Text))
if block == nil {
return nil, errors.New("Failed to parse certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
roots, err := ctx.CertificateStore.Certificates()
if err != nil {
return nil, err
}
// Verify that the certificate is one we trust
if !contains(roots, cert) {
return nil, errors.New("Could not verify certificate against trusted certs")
}
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
return nil, errors.New("Cert is not valid at this time")
}
return cert, nil
}
func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error) {
cert, err := ctx.verifyCertificate(el)
if err != nil {
return nil, err
}
return ctx.validateSignature(el, cert)
}

View File

@@ -0,0 +1,78 @@
package dsig
import "crypto"
const (
DefaultPrefix = "ds"
Namespace = "http://www.w3.org/2000/09/xmldsig#"
)
// Tags
const (
SignatureTag = "Signature"
SignedInfoTag = "SignedInfo"
CanonicalizationMethodTag = "CanonicalizationMethod"
SignatureMethodTag = "SignatureMethod"
ReferenceTag = "Reference"
TransformsTag = "Transforms"
TransformTag = "Transform"
DigestMethodTag = "DigestMethod"
DigestValueTag = "DigestValue"
SignatureValueTag = "SignatureValue"
KeyInfoTag = "KeyInfo"
X509DataTag = "X509Data"
X509CertificateTag = "X509Certificate"
InclusiveNamespacesTag = "InclusiveNamespaces"
)
const (
AlgorithmAttr = "Algorithm"
URIAttr = "URI"
DefaultIdAttr = "ID"
PrefixListAttr = "PrefixList"
)
type AlgorithmID string
func (id AlgorithmID) String() string {
return string(id)
}
const (
RSASHA1SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
RSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
)
//Well-known signature algorithms
const (
// Supported canonicalization algorithms
CanonicalXML10ExclusiveAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#"
CanonicalXML11AlgorithmId AlgorithmID = "http://www.w3.org/2006/12/xml-c14n11"
EnvelopedSignatureAltorithmId AlgorithmID = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
)
var digestAlgorithmIdentifiers = map[crypto.Hash]string{
crypto.SHA1: "http://www.w3.org/2000/09/xmldsig#sha1",
crypto.SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
}
var digestAlgorithmsByIdentifier = map[string]crypto.Hash{}
var signatureMethodsByIdentifier = map[string]crypto.Hash{}
func init() {
for hash, id := range digestAlgorithmIdentifiers {
digestAlgorithmsByIdentifier[id] = hash
}
for hash, id := range signatureMethodIdentifiers {
signatureMethodsByIdentifier[id] = hash
}
}
var signatureMethodIdentifiers = map[crypto.Hash]string{
crypto.SHA1: RSASHA1SignatureMethod,
crypto.SHA256: RSASHA256SignatureMethod,
crypto.SHA512: RSASHA512SignatureMethod,
}