blob: dc8402b018f48fc8c57b5a109bc8d616e398950e [file] [log] [blame]
/*
Copyright (c) 2018 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
// The sts/internal package provides the types for invoking the sts.Issue method.
// The sts.Issue and SessionManager LoginByToken methods require an XML signature.
// Unlike the JRE and .NET runtimes, the Go stdlib does not support XML signing.
// We should considering contributing to the goxmldsig package and gosaml2 to meet
// the needs of sts.Issue rather than maintaining this package long term.
// The tricky part of xmldig is the XML canonicalization (C14N), which is responsible
// for most of the make-your-eyes bleed XML formatting in this package.
// C14N is also why some structures use xml.Name without a field tag and methods modify the xml.Name directly,
// though also working around Go's handling of XML namespace prefixes.
// Most of the types in this package were originally generated from the wsdl and hacked up gen/ scripts,
// but have since been modified by hand.
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"log"
"path"
"reflect"
"strings"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/vim25/xml"
)
const (
XSI = "http://www.w3.org/2001/XMLSchema-instance"
WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
DSIG = "http://www.w3.org/2000/09/xmldsig#"
SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
Time = "2006-01-02T15:04:05.000Z"
)
// Security is used as soap.Envelope.Header.Security when signing requests.
type Security struct {
XMLName xml.Name `xml:"wsse:Security"`
WSSE string `xml:"xmlns:wsse,attr"`
WSU string `xml:"xmlns:wsu,attr"`
Timestamp Timestamp
BinarySecurityToken *BinarySecurityToken `xml:",omitempty"`
UsernameToken *UsernameToken `xml:",omitempty"`
Assertion string `xml:",innerxml"`
Signature *Signature `xml:",omitempty"`
}
type Timestamp struct {
XMLName xml.Name `xml:"wsu:Timestamp"`
NS string `xml:"xmlns:wsu,attr"`
ID string `xml:"wsu:Id,attr"`
Created string `xml:"wsu:Created"`
Expires string `xml:"wsu:Expires"`
}
func (t *Timestamp) C14N() string {
return Marshal(t)
}
type BinarySecurityToken struct {
XMLName xml.Name `xml:"wsse:BinarySecurityToken"`
EncodingType string `xml:"EncodingType,attr"`
ValueType string `xml:"ValueType,attr"`
ID string `xml:"wsu:Id,attr"`
Value string `xml:",chardata"`
}
type UsernameToken struct {
XMLName xml.Name `xml:"wsse:UsernameToken"`
Username string `xml:"wsse:Username"`
Password string `xml:"wsse:Password"`
}
type Signature struct {
XMLName xml.Name
NS string `xml:"xmlns:ds,attr"`
ID string `xml:"Id,attr"`
SignedInfo SignedInfo
SignatureValue Value
KeyInfo KeyInfo
}
func (s *Signature) C14N() string {
return fmt.Sprintf(`<ds:Signature xmlns:ds="%s">%s%s%s</ds:Signature>`,
DSIG, s.SignedInfo.C14N(), s.SignatureValue.C14N(), s.KeyInfo.C14N())
}
type SignedInfo struct {
XMLName xml.Name
NS string `xml:"xmlns:ds,attr,omitempty"`
CanonicalizationMethod Method
SignatureMethod Method
Reference []Reference
}
func (s SignedInfo) C14N() string {
ns := "" // empty in ActAs c14n form for example
if s.NS != "" {
ns = fmt.Sprintf(` xmlns:ds="%s"`, s.NS)
}
c14n := []string{fmt.Sprintf("<ds:SignedInfo%s>", ns)}
c14n = append(c14n, s.CanonicalizationMethod.C14N(), s.SignatureMethod.C14N())
for i := range s.Reference {
c14n = append(c14n, s.Reference[i].C14N())
}
c14n = append(c14n, "</ds:SignedInfo>")
return strings.Join(c14n, "")
}
type Method struct {
XMLName xml.Name
Algorithm string `xml:",attr"`
}
func (m *Method) C14N() string {
return mkns("ds", m, &m.XMLName)
}
type Value struct {
XMLName xml.Name
Value string `xml:",innerxml"`
}
func (v *Value) C14N() string {
return mkns("ds", v, &v.XMLName)
}
type Reference struct {
XMLName xml.Name
URI string `xml:",attr"`
Transforms Transforms
DigestMethod Method
DigestValue Value
}
func (r Reference) C14N() string {
for i := range r.Transforms.Transform {
t := &r.Transforms.Transform[i]
t.XMLName.Local = "ds:Transform"
t.XMLName.Space = ""
if t.InclusiveNamespaces != nil {
name := &t.InclusiveNamespaces.XMLName
if !strings.HasPrefix(name.Local, "ec:") {
name.Local = "ec:" + name.Local
name.Space = ""
}
t.InclusiveNamespaces.NS = t.Algorithm
}
}
c14n := []string{
fmt.Sprintf(`<ds:Reference URI="%s">`, r.URI),
r.Transforms.C14N(),
r.DigestMethod.C14N(),
r.DigestValue.C14N(),
"</ds:Reference>",
}
return strings.Join(c14n, "")
}
func NewReference(id string, val string) Reference {
sum := sha256.Sum256([]byte(val))
return Reference{
XMLName: xml.Name{Local: "ds:Reference"},
URI: "#" + id,
Transforms: Transforms{
XMLName: xml.Name{Local: "ds:Transforms"},
Transform: []Transform{
Transform{
XMLName: xml.Name{Local: "ds:Transform"},
Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
},
},
},
DigestMethod: Method{
XMLName: xml.Name{Local: "ds:DigestMethod"},
Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
},
DigestValue: Value{
XMLName: xml.Name{Local: "ds:DigestValue"},
Value: base64.StdEncoding.EncodeToString(sum[:]),
},
}
}
type Transforms struct {
XMLName xml.Name
Transform []Transform
}
func (t *Transforms) C14N() string {
return mkns("ds", t, &t.XMLName)
}
type Transform struct {
XMLName xml.Name
Algorithm string `xml:",attr"`
InclusiveNamespaces *InclusiveNamespaces `xml:",omitempty"`
}
type InclusiveNamespaces struct {
XMLName xml.Name
NS string `xml:"xmlns:ec,attr,omitempty"`
PrefixList string `xml:",attr"`
}
type X509Data struct {
XMLName xml.Name
X509Certificate string `xml:",innerxml"`
}
type KeyInfo struct {
XMLName xml.Name
NS string `xml:"xmlns:ds,attr,omitempty"`
SecurityTokenReference *SecurityTokenReference `xml:",omitempty"`
X509Data *X509Data `xml:",omitempty"`
}
func (o *KeyInfo) C14N() string {
names := []*xml.Name{
&o.XMLName,
}
if o.SecurityTokenReference != nil {
names = append(names, &o.SecurityTokenReference.XMLName)
}
if o.X509Data != nil {
names = append(names, &o.X509Data.XMLName)
}
return mkns("ds", o, names...)
}
type SecurityTokenReference struct {
XMLName xml.Name `xml:"wsse:SecurityTokenReference"`
WSSE11 string `xml:"xmlns:wsse11,attr,omitempty"`
TokenType string `xml:"wsse11:TokenType,attr,omitempty"`
Reference *SecurityReference `xml:",omitempty"`
KeyIdentifier *KeyIdentifier `xml:",omitempty"`
}
type SecurityReference struct {
XMLName xml.Name `xml:"wsse:Reference"`
URI string `xml:",attr"`
ValueType string `xml:",attr"`
}
type KeyIdentifier struct {
XMLName xml.Name `xml:"wsse:KeyIdentifier"`
ID string `xml:",innerxml"`
ValueType string `xml:",attr"`
}
type Issuer struct {
XMLName xml.Name
Format string `xml:",attr"`
Value string `xml:",innerxml"`
}
func (i *Issuer) C14N() string {
return mkns("saml2", i, &i.XMLName)
}
type Assertion struct {
XMLName xml.Name
ID string `xml:",attr"`
IssueInstant string `xml:",attr"`
Version string `xml:",attr"`
Issuer Issuer
Signature Signature
Subject Subject
Conditions Conditions
AuthnStatement AuthnStatement
AttributeStatement AttributeStatement
}
func (a *Assertion) C14N() string {
start := `<saml2:Assertion xmlns:saml2="%s" ID="%s" IssueInstant="%s" Version="%s">`
c14n := []string{
fmt.Sprintf(start, a.XMLName.Space, a.ID, a.IssueInstant, a.Version),
a.Issuer.C14N(),
a.Signature.C14N(),
a.Subject.C14N(),
a.Conditions.C14N(),
a.AuthnStatement.C14N(),
a.AttributeStatement.C14N(),
`</saml2:Assertion>`,
}
return strings.Join(c14n, "")
}
type NameID struct {
XMLName xml.Name
Format string `xml:",attr"`
ID string `xml:",innerxml"`
}
type Subject struct {
XMLName xml.Name
NameID NameID
SubjectConfirmation SubjectConfirmation
}
func (s *Subject) C14N() string {
data := &s.SubjectConfirmation.SubjectConfirmationData
names := []*xml.Name{
&s.XMLName,
&s.NameID.XMLName,
&s.SubjectConfirmation.XMLName,
&data.XMLName,
}
if s.SubjectConfirmation.NameID != nil {
names = append(names, &s.SubjectConfirmation.NameID.XMLName)
}
if data.KeyInfo != nil {
data.NS = XSI
data.Type = "saml2:KeyInfoConfirmationDataType"
data.KeyInfo.XMLName = xml.Name{Local: "ds:KeyInfo"}
data.KeyInfo.X509Data.XMLName = xml.Name{Local: "ds:X509Data"}
data.KeyInfo.NS = DSIG
}
return mkns("saml2", s, names...)
}
type SubjectConfirmationData struct {
XMLName xml.Name
NS string `xml:"xmlns:xsi,attr,omitempty"`
Type string `xml:"xsi:type,attr,omitempty"`
NotOnOrAfter string `xml:",attr,omitempty"`
KeyInfo *KeyInfo
}
type SubjectConfirmation struct {
XMLName xml.Name
Method string `xml:",attr"`
NameID *NameID
SubjectConfirmationData SubjectConfirmationData
}
type Condition struct {
Type string `xml:"xsi:type,attr,omitempty"`
}
func (c *Condition) GetCondition() *Condition {
return c
}
type BaseCondition interface {
GetCondition() *Condition
}
func init() {
types.Add("BaseCondition", reflect.TypeOf((*Condition)(nil)).Elem())
types.Add("del:DelegationRestrictionType", reflect.TypeOf((*DelegateRestriction)(nil)).Elem())
types.Add("rsa:RenewRestrictionType", reflect.TypeOf((*RenewRestriction)(nil)).Elem())
}
type Conditions struct {
XMLName xml.Name
NotBefore string `xml:",attr"`
NotOnOrAfter string `xml:",attr"`
ProxyRestriction *ProxyRestriction `xml:",omitempty"`
Condition []BaseCondition `xml:",omitempty"`
}
func (c *Conditions) C14N() string {
names := []*xml.Name{
&c.XMLName,
}
if c.ProxyRestriction != nil {
names = append(names, &c.ProxyRestriction.XMLName)
}
for i := range c.Condition {
switch r := c.Condition[i].(type) {
case *DelegateRestriction:
names = append(names, &r.XMLName, &r.Delegate.NameID.XMLName)
r.NS = XSI
r.Type = "del:DelegationRestrictionType"
r.Delegate.NS = r.Delegate.XMLName.Space
r.Delegate.XMLName = xml.Name{Local: "del:Delegate"}
case *RenewRestriction:
names = append(names, &r.XMLName)
r.NS = XSI
r.Type = "rsa:RenewRestrictionType"
}
}
return mkns("saml2", c, names...)
}
type ProxyRestriction struct {
XMLName xml.Name
Count int32 `xml:",attr"`
}
type RenewRestriction struct {
XMLName xml.Name
NS string `xml:"xmlns:xsi,attr,omitempty"`
Count int32 `xml:",attr"`
Condition
}
type Delegate struct {
XMLName xml.Name
NS string `xml:"xmlns:del,attr,omitempty"`
DelegationInstant string `xml:",attr"`
NameID NameID
}
type DelegateRestriction struct {
XMLName xml.Name
NS string `xml:"xmlns:xsi,attr,omitempty"`
Condition
Delegate Delegate
}
type AuthnStatement struct {
XMLName xml.Name
AuthnInstant string `xml:",attr"`
AuthnContext struct {
XMLName xml.Name
AuthnContextClassRef struct {
XMLName xml.Name
Value string `xml:",innerxml"`
}
}
}
func (a *AuthnStatement) C14N() string {
return mkns("saml2", a, &a.XMLName, &a.AuthnContext.XMLName, &a.AuthnContext.AuthnContextClassRef.XMLName)
}
type AttributeStatement struct {
XMLName xml.Name
Attribute []Attribute
}
func (a *AttributeStatement) C14N() string {
c14n := []string{"<saml2:AttributeStatement>"}
for i := range a.Attribute {
c14n = append(c14n, a.Attribute[i].C14N())
}
c14n = append(c14n, "</saml2:AttributeStatement>")
return strings.Join(c14n, "")
}
type AttributeValue struct {
XMLName xml.Name
Type string `xml:"type,attr"`
Value string `xml:",innerxml"`
}
func (a *AttributeValue) C14N() string {
return fmt.Sprintf(`<saml2:AttributeValue xmlns:xsi="%s" xsi:type="xs:string">%s</saml2:AttributeValue>`, XSI, a.Value)
}
type Attribute struct {
XMLName xml.Name
FriendlyName string `xml:",attr"`
Name string `xml:",attr"`
NameFormat string `xml:",attr"`
AttributeValue []AttributeValue
}
func (a *Attribute) C14N() string {
c14n := []string{
fmt.Sprintf(`<saml2:Attribute FriendlyName="%s" Name="%s" NameFormat="%s">`, a.FriendlyName, a.Name, a.NameFormat),
}
for i := range a.AttributeValue {
c14n = append(c14n, a.AttributeValue[i].C14N())
}
c14n = append(c14n, `</saml2:Attribute>`)
return strings.Join(c14n, "")
}
type Lifetime struct {
Created string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"`
Expires string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Expires"`
}
func (t *Lifetime) C14N() string {
return fmt.Sprintf(`<Lifetime><wsu:Created>%s</wsu:Created><wsu:Expires>%s</wsu:Expires></Lifetime>`, t.Created, t.Expires)
}
type Renewing struct {
Allow bool `xml:",attr"`
OK bool `xml:",attr"`
}
type UseKey struct {
Sig string `xml:",attr"`
}
type Target struct {
Token string `xml:",innerxml"`
}
type RequestSecurityToken struct {
TokenType string `xml:",omitempty"`
RequestType string `xml:",omitempty"`
Lifetime *Lifetime `xml:",omitempty"`
Renewing *Renewing `xml:",omitempty"`
Delegatable bool `xml:",omitempty"`
KeyType string `xml:",omitempty"`
SignatureAlgorithm string `xml:",omitempty"`
UseKey *UseKey `xml:",omitempty"`
ActAs *Target `xml:",omitempty"`
ValidateTarget *Target `xml:",omitempty"`
RenewTarget *Target `xml:",omitempty"`
}
func Unmarshal(data []byte, v interface{}) error {
dec := xml.NewDecoder(bytes.NewReader(data))
dec.TypeFunc = types.TypeFunc()
return dec.Decode(v)
}
// toString returns an XML encoded RequestSecurityToken.
// When c14n is true, returns the canonicalized ActAs.Assertion which is required to sign the Issue request.
// When c14n is false, returns the original content of the ActAs.Assertion.
// The original content must be used within the request Body, as it has its own signature.
func (r *RequestSecurityToken) toString(c14n bool) string {
actas := ""
if r.ActAs != nil {
token := r.ActAs.Token
if c14n {
var a Assertion
err := Unmarshal([]byte(r.ActAs.Token), &a)
if err != nil {
log.Printf("decode ActAs: %s", err)
}
token = a.C14N()
}
actas = fmt.Sprintf(`<wst:ActAs xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200802">%s</wst:ActAs>`, token)
}
body := []string{
fmt.Sprintf(`<RequestSecurityToken xmlns="http://docs.oasis-open.org/ws-sx/ws-trust/200512">`),
fmt.Sprintf(`<TokenType>%s</TokenType>`, r.TokenType),
fmt.Sprintf(`<RequestType>%s</RequestType>`, r.RequestType),
r.Lifetime.C14N(),
}
if r.RenewTarget == nil {
body = append(body,
fmt.Sprintf(`<Renewing Allow="%t" OK="%t"></Renewing>`, r.Renewing.Allow, r.Renewing.OK),
fmt.Sprintf(`<Delegatable>%t</Delegatable>`, r.Delegatable),
actas,
fmt.Sprintf(`<KeyType>%s</KeyType>`, r.KeyType),
fmt.Sprintf(`<SignatureAlgorithm>%s</SignatureAlgorithm>`, r.SignatureAlgorithm),
fmt.Sprintf(`<UseKey Sig="%s"></UseKey>`, r.UseKey.Sig))
} else {
token := r.RenewTarget.Token
if c14n {
var a Assertion
err := Unmarshal([]byte(r.RenewTarget.Token), &a)
if err != nil {
log.Printf("decode Renew: %s", err)
}
token = a.C14N()
}
body = append(body,
fmt.Sprintf(`<UseKey Sig="%s"></UseKey>`, r.UseKey.Sig),
fmt.Sprintf(`<RenewTarget>%s</RenewTarget>`, token))
}
return strings.Join(append(body, `</RequestSecurityToken>`), "")
}
func (r *RequestSecurityToken) C14N() string {
return r.toString(true)
}
func (r *RequestSecurityToken) String() string {
return r.toString(false)
}
type RequestSecurityTokenResponseCollection struct {
RequestSecurityTokenResponse RequestSecurityTokenResponse
}
type RequestSecurityTokenResponse struct {
RequestedSecurityToken RequestedSecurityToken
Lifetime *Lifetime `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 Lifetime"`
}
type RequestedSecurityToken struct {
Assertion string `xml:",innerxml"`
}
type RequestSecurityTokenBody struct {
Req *RequestSecurityToken `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityToken,omitempty"`
Res *RequestSecurityTokenResponseCollection `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityTokenResponseCollection,omitempty"`
Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
}
func (b *RequestSecurityTokenBody) Fault() *soap.Fault { return b.Fault_ }
func (b *RequestSecurityTokenBody) RequestSecurityToken() *RequestSecurityToken { return b.Req }
func (r *RequestSecurityToken) Action() string {
kind := path.Base(r.RequestType)
return "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/" + kind
}
func Issue(ctx context.Context, r soap.RoundTripper, req *RequestSecurityToken) (*RequestSecurityTokenResponseCollection, error) {
var reqBody, resBody RequestSecurityTokenBody
reqBody.Req = req
if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
return nil, err
}
return resBody.Res, nil
}
type RenewSecurityTokenBody struct {
Req *RequestSecurityToken `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityToken,omitempty"`
Res *RequestSecurityTokenResponse `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityTokenResponse,omitempty"`
Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
}
func (b *RenewSecurityTokenBody) Fault() *soap.Fault { return b.Fault_ }
func (b *RenewSecurityTokenBody) RequestSecurityToken() *RequestSecurityToken { return b.Req }
func Renew(ctx context.Context, r soap.RoundTripper, req *RequestSecurityToken) (*RequestSecurityTokenResponse, error) {
var reqBody, resBody RenewSecurityTokenBody
reqBody.Req = req
if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
return nil, err
}
return resBody.Res, nil
}
// Marshal panics if xml.Marshal returns an error
func Marshal(val interface{}) string {
b, err := xml.Marshal(val)
if err != nil {
panic(err)
}
return string(b)
}
// mkns prepends the given namespace to xml.Name.Local and returns obj encoded as xml.
// Note that the namespace is required when encoding, but the namespace prefix must not be
// present when decoding as Go's decoding does not handle namespace prefix.
func mkns(ns string, obj interface{}, name ...*xml.Name) string {
ns = ns + ":"
for i := range name {
name[i].Space = ""
if !strings.HasPrefix(name[i].Local, ns) {
name[i].Local = ns + name[i].Local
}
}
return Marshal(obj)
}