diff --git a/etreeutils/namespace.go b/etreeutils/namespace.go index 6a5be03..15b074d 100644 --- a/etreeutils/namespace.go +++ b/etreeutils/namespace.go @@ -90,34 +90,40 @@ func (ctx NSContext) declare(prefix, namespace string) etree.Attr { } func (ctx NSContext) SubContext(el *etree.Element) (NSContext, error) { + return ctx.SubContexts([]*etree.Element{el}) +} + +func (ctx NSContext) SubContexts(els []*etree.Element) (NSContext, error) { // The subcontext should inherit existing declared prefixes newCtx := ctx.Copy() - // Merge new namespace declarations on top of existing ones. - for _, attr := range el.Attr { - if attr.Space == xmlnsPrefix { - // This attribute is a namespace declaration of the form "xmlns:" + for _, el := range els { + // Merge new namespace declarations on top of existing ones. + for _, attr := range el.Attr { + if attr.Space == xmlnsPrefix { + // This attribute is a namespace declaration of the form "xmlns:" - // The 'xml' namespace may only be re-declared with the name 'http://www.w3.org/XML/1998/namespace' - if attr.Key == xmlPrefix && attr.Value != XMLNamespace { - return ctx, ErrReservedNamespace - } + // The 'xml' namespace may only be re-declared with the name 'http://www.w3.org/XML/1998/namespace' + if attr.Key == xmlPrefix && attr.Value != XMLNamespace { + return ctx, ErrReservedNamespace + } - // The 'xmlns' namespace may not be re-declared - if attr.Key == xmlnsPrefix { - return ctx, ErrReservedNamespace - } + // The 'xmlns' namespace may not be re-declared + if attr.Key == xmlnsPrefix { + return ctx, ErrReservedNamespace + } - newCtx.declare(attr.Key, attr.Value) - } else if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix { - // This attribute is a default namespace declaration + newCtx.declare(attr.Key, attr.Value) + } else if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix { + // This attribute is a default namespace declaration - // The xmlns namespace value may not be declared as the default namespace - if attr.Value == XMLNSNamespace { - return ctx, ErrInvalidDefaultNamespace - } + // The xmlns namespace value may not be declared as the default namespace + if attr.Value == XMLNSNamespace { + return ctx, ErrInvalidDefaultNamespace + } - newCtx.declare(defaultPrefix, attr.Value) + newCtx.declare(defaultPrefix, attr.Value) + } } } @@ -413,13 +419,32 @@ func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) // NSBuildParentContext recurses upward from an element in order to build an NSContext // for its immediate parent. If the element has no parent DefaultNSContext // is returned. +func NSBuildParentContexts(els []*etree.Element) (NSContext, error) { + + ctx := NewDefaultNSContext() + for _, el := range els { + dctx, err := nSBuildParentContext(ctx, el) + ctx = dctx + if err != nil { + return NewDefaultNSContext(), err + } + } + return ctx, nil +} + func NSBuildParentContext(el *etree.Element) (NSContext, error) { + + return NSBuildParentContexts([]*etree.Element{el}) +} + +func nSBuildParentContext(ctx NSContext, el *etree.Element) (NSContext, error) { parent := el.Parent() if parent == nil { return NewDefaultNSContext(), nil } - ctx, err := NSBuildParentContext(parent) + ctx, err := nSBuildParentContext(ctx, parent) + if err != nil { return ctx, err } diff --git a/sign.go b/sign.go index cc77f79..46f38de 100644 --- a/sign.go +++ b/sign.go @@ -148,7 +148,7 @@ func (ctx *SigningContext) getCerts() ([][]byte, error) { } } -func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) { +func (ctx *SigningContext) constructSignedInfo(els []*etree.Element, enveloped bool) (*etree.Element, error) { digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() if digestAlgorithmIdentifier == "" { return nil, errors.New("unsupported hash mechanism") @@ -159,11 +159,6 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool 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, @@ -177,38 +172,55 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool signatureMethod := ctx.createNamespacedElement(signedInfo, SignatureMethodTag) signatureMethod.CreateAttr(AlgorithmAttr, signatureMethodIdentifier) - // /SignedInfo/Reference - reference := ctx.createNamespacedElement(signedInfo, ReferenceTag) - dataId := el.SelectAttrValue(ctx.IdAttribute, "") - if dataId == "" { - reference.CreateAttr(URIAttr, "") - } else { - 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())) + for _, el := range els { + digest, err := ctx.digest(el) + if err != nil { + return nil, err + } + + // /SignedInfo/Reference + reference := ctx.createNamespacedElement(signedInfo, ReferenceTag) + + dataId := el.SelectAttrValue(ctx.IdAttribute, "") + if dataId == "" { + reference.CreateAttr(URIAttr, "") + } else { + reference.CreateAttr(URIAttr, "#"+dataId) + } - // /SignedInfo/Reference/DigestMethod - digestMethod := ctx.createNamespacedElement(reference, DigestMethodTag) - digestMethod.CreateAttr(AlgorithmAttr, digestAlgorithmIdentifier) + // /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)) + // /SignedInfo/Reference/DigestValue + digestValue := ctx.createNamespacedElement(reference, DigestValueTag) + digestValue.SetText(base64.StdEncoding.EncodeToString(digest)) + } return signedInfo, nil } +func (ctx *SigningContext) ConstructSignatures(els []*etree.Element, enveloped bool) (*etree.Element, error) { + return ctx.ConstructSignatureRef(els, nil, enveloped) +} + func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) (*etree.Element, error) { - signedInfo, err := ctx.constructSignedInfo(el, enveloped) + return ctx.ConstructSignatures([]*etree.Element { el }, enveloped) +} + +func (ctx *SigningContext) ConstructSignatureRef(els []*etree.Element, keyRefElementDecorator func(*etree.Element), enveloped bool) (*etree.Element, error) { + signedInfo, err := ctx.constructSignedInfo(els, enveloped) if err != nil { return nil, err } @@ -232,13 +244,13 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) // a series of cascading NSContexts to capture namespace declarations: // First get the context surrounding the element we are signing. - rootNSCtx, err := etreeutils.NSBuildParentContext(el) + rootNSCtx, err := etreeutils.NSBuildParentContexts(els) if err != nil { return nil, err } // Then capture any declarations on the element itself. - elNSCtx, err := rootNSCtx.SubContext(el) + elNSCtx, err := rootNSCtx.SubContexts(els) if err != nil { return nil, err } @@ -275,10 +287,15 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) signatureValue.SetText(base64.StdEncoding.EncodeToString(rawSignature)) keyInfo := ctx.createNamespacedElement(sig, KeyInfoTag) - x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag) - for _, cert := range certs { - x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag) - x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert)) + + if (keyRefElementDecorator == nil) { + x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag) + for _, cert := range certs { + x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag) + x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert)) + } + } else { + keyRefElementDecorator(keyInfo) } return sig, nil diff --git a/sign_test.go b/sign_test.go index b0042ae..816574f 100644 --- a/sign_test.go +++ b/sign_test.go @@ -138,6 +138,45 @@ func TestSignNonDefaultID(t *testing.T) { require.Equal(t, refURI, "#"+id) } +func TestSignSoapRequest(t *testing.T) { + // Given + //bs, err := ioutil.ReadFile("./testdata/soaprequest_result.xml") + + ks := RandomKeyStoreForTest() + ctx := &SigningContext{ + Hash: crypto.SHA256, + KeyStore: ks, + IdAttribute: "wsu:Id", + Prefix: DefaultPrefix, + Canonicalizer: MakeC14N11Canonicalizer(), + } + + doc := etree.NewDocument() + err := doc.ReadFromFile("./testdata/soaprequest.xml") + + bodyPath, err := etree.CompilePath("./soap:Envelope/soap:Body") + bodyElement := doc.FindElementPath(bodyPath) + require.NotNil(t, bodyElement) + + actionPath, err := etree.CompilePath("./soap:Envelope/soap:Header/Action") + actionElement := doc.FindElementPath(actionPath) + require.NotNil(t, actionElement) + + securityPath, err := etree.CompilePath("./soap:Envelope/soap:Header/wsse:Security") + securityElement := doc.FindElementPath(securityPath) + require.NotNil(t, securityElement) + + // When + sig, err := ctx.ConstructSignatures([]*etree.Element{bodyElement, actionElement}, true) + require.NoError(t, err) + require.NotNil(t, sig) + securityElement.AddChild(sig) + + // Then + // str, err := doc.WriteToString() + // require.NoError(t, err) +} + func TestIncompatibleSignatureMethods(t *testing.T) { // RSA randomKeyStore := RandomKeyStoreForTest().(*MemoryX509KeyStore) diff --git a/testdata/soaprequest.xml b/testdata/soaprequest.xml new file mode 100644 index 0000000..bd50148 --- /dev/null +++ b/testdata/soaprequest.xml @@ -0,0 +1 @@ +http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issueurn:uuid:3f58046c-0a79-4aa3-887d-81e8bd47108dhttps://sts.test-vdxapi.vconf.dk/sts/service/sts
http://www.w3.org/2005/08/addressing/anonymous
2019-05-13T06:45:42.701Z2019-05-13T06:50:42.701Z
http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issueurn:medcom:videoapihttp://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKeyrXApxxjCWlsEfeKgUPOl1mJC9aqkkWooyUgOU+KsrH9qRCoK9xVdI7YJebwr5+TJtBbWkKkuD926SMxJV1LY6IT8tCflomIl4E5IZdRZPci1N71lQDV6SfNuGPHNpFpLssdSY34+t4/vuGeTZ2lJB5IP4sDvjAxJ+nXECcHmcupEEQu3wI2nijcWl4hRRSdhUuKDB/AiaZvsPKcdFj4WTlRdewJS4v5m1khwce6Zj1jw6N7PSQPHaisIxqx2SMHvKiepPuESgEpqP+sGRaL2ESJWuB1kTsNHmer6cJ+ba/pvJy3xraY7mrgRv/zWa+6Of9LSVw2hfFx3pEjBgYHhhw==AQAB
\ No newline at end of file