signer.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*
  2. Copyright (c) 2018 VMware, Inc. All Rights Reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package sts
  14. import (
  15. "bytes"
  16. "compress/gzip"
  17. "crypto"
  18. "crypto/rand"
  19. "crypto/rsa"
  20. "crypto/sha256"
  21. "crypto/tls"
  22. "encoding/base64"
  23. "errors"
  24. "fmt"
  25. "io"
  26. "io/ioutil"
  27. mrand "math/rand"
  28. "net/http"
  29. "net/url"
  30. "strings"
  31. "time"
  32. "github.com/google/uuid"
  33. "github.com/vmware/govmomi/sts/internal"
  34. "github.com/vmware/govmomi/vim25/methods"
  35. "github.com/vmware/govmomi/vim25/soap"
  36. "github.com/vmware/govmomi/vim25/xml"
  37. )
  38. // Signer implements the soap.Signer interface.
  39. type Signer struct {
  40. Token string // Token is a SAML token
  41. Certificate *tls.Certificate // Certificate is used to sign requests
  42. Lifetime struct {
  43. Created time.Time
  44. Expires time.Time
  45. }
  46. user *url.Userinfo // user contains the credentials for bearer token request
  47. keyID string // keyID is the Signature UseKey ID, which is referenced in both the soap body and header
  48. }
  49. // signedEnvelope is similar to soap.Envelope, but with namespace and Body as innerxml
  50. type signedEnvelope struct {
  51. XMLName xml.Name `xml:"soap:Envelope"`
  52. NS string `xml:"xmlns:soap,attr"`
  53. Header soap.Header `xml:"soap:Header"`
  54. Body string `xml:",innerxml"`
  55. }
  56. // newID returns a unique Reference ID, with a leading underscore as required by STS.
  57. func newID() string {
  58. return "_" + uuid.New().String()
  59. }
  60. func (s *Signer) setTokenReference(info *internal.KeyInfo) error {
  61. var token struct {
  62. ID string `xml:",attr"` // parse saml2:Assertion ID attribute
  63. InnerXML string `xml:",innerxml"` // no need to parse the entire token
  64. }
  65. if err := xml.Unmarshal([]byte(s.Token), &token); err != nil {
  66. return err
  67. }
  68. info.SecurityTokenReference = &internal.SecurityTokenReference{
  69. WSSE11: "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd",
  70. TokenType: "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
  71. KeyIdentifier: &internal.KeyIdentifier{
  72. ID: token.ID,
  73. ValueType: "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID",
  74. },
  75. }
  76. return nil
  77. }
  78. // Sign is a soap.Signer implementation which can be used to sign RequestSecurityToken and LoginByTokenBody requests.
  79. func (s *Signer) Sign(env soap.Envelope) ([]byte, error) {
  80. var key *rsa.PrivateKey
  81. hasKey := false
  82. if s.Certificate != nil {
  83. key, hasKey = s.Certificate.PrivateKey.(*rsa.PrivateKey)
  84. if !hasKey {
  85. return nil, errors.New("sts: rsa.PrivateKey is required")
  86. }
  87. }
  88. created := time.Now().UTC()
  89. header := &internal.Security{
  90. WSU: internal.WSU,
  91. WSSE: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
  92. Timestamp: internal.Timestamp{
  93. NS: internal.WSU,
  94. ID: newID(),
  95. Created: created.Format(internal.Time),
  96. Expires: created.Add(time.Minute).Format(internal.Time), // If STS receives this request after this, it is assumed to have expired.
  97. },
  98. }
  99. env.Header.Security = header
  100. info := internal.KeyInfo{XMLName: xml.Name{Local: "ds:KeyInfo"}}
  101. var c14n, body string
  102. type requestToken interface {
  103. RequestSecurityToken() *internal.RequestSecurityToken
  104. }
  105. switch x := env.Body.(type) {
  106. case requestToken:
  107. if hasKey {
  108. // We need c14n for all requests, as its digest is included in the signature and must match on the server side.
  109. // We need the body in original form when using an ActAs or RenewTarget token, where the token and its signature are embedded in the body.
  110. req := x.RequestSecurityToken()
  111. c14n = req.C14N()
  112. body = req.String()
  113. id := newID()
  114. info.SecurityTokenReference = &internal.SecurityTokenReference{
  115. Reference: &internal.SecurityReference{
  116. URI: "#" + id,
  117. ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3",
  118. },
  119. }
  120. header.BinarySecurityToken = &internal.BinarySecurityToken{
  121. EncodingType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary",
  122. ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3",
  123. ID: id,
  124. Value: base64.StdEncoding.EncodeToString(s.Certificate.Certificate[0]),
  125. }
  126. } else {
  127. header.UsernameToken = &internal.UsernameToken{
  128. Username: s.user.Username(),
  129. }
  130. header.UsernameToken.Password, _ = s.user.Password()
  131. }
  132. case *methods.LoginByTokenBody:
  133. header.Assertion = s.Token
  134. if hasKey {
  135. if err := s.setTokenReference(&info); err != nil {
  136. return nil, err
  137. }
  138. c14n = internal.Marshal(x.Req)
  139. }
  140. default:
  141. // We can end up here via ssoadmin.SessionManager.Login().
  142. // No other known cases where a signed request is needed.
  143. header.Assertion = s.Token
  144. if hasKey {
  145. if err := s.setTokenReference(&info); err != nil {
  146. return nil, err
  147. }
  148. type Req interface {
  149. C14N() string
  150. }
  151. c14n = env.Body.(Req).C14N()
  152. }
  153. }
  154. if !hasKey {
  155. return xml.Marshal(env) // Bearer token without key to sign
  156. }
  157. id := newID()
  158. tmpl := `<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="%s" wsu:Id="%s">%s</soap:Body>`
  159. c14n = fmt.Sprintf(tmpl, internal.WSU, id, c14n)
  160. if body == "" {
  161. body = c14n
  162. } else {
  163. body = fmt.Sprintf(tmpl, internal.WSU, id, body)
  164. }
  165. header.Signature = &internal.Signature{
  166. XMLName: xml.Name{Local: "ds:Signature"},
  167. NS: internal.DSIG,
  168. ID: s.keyID,
  169. KeyInfo: info,
  170. SignedInfo: internal.SignedInfo{
  171. XMLName: xml.Name{Local: "ds:SignedInfo"},
  172. NS: internal.DSIG,
  173. CanonicalizationMethod: internal.Method{
  174. XMLName: xml.Name{Local: "ds:CanonicalizationMethod"},
  175. Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
  176. },
  177. SignatureMethod: internal.Method{
  178. XMLName: xml.Name{Local: "ds:SignatureMethod"},
  179. Algorithm: internal.SHA256,
  180. },
  181. Reference: []internal.Reference{
  182. internal.NewReference(header.Timestamp.ID, header.Timestamp.C14N()),
  183. internal.NewReference(id, c14n),
  184. },
  185. },
  186. }
  187. sum := sha256.Sum256([]byte(header.Signature.SignedInfo.C14N()))
  188. sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:])
  189. if err != nil {
  190. return nil, err
  191. }
  192. header.Signature.SignatureValue = internal.Value{
  193. XMLName: xml.Name{Local: "ds:SignatureValue"},
  194. Value: base64.StdEncoding.EncodeToString(sig),
  195. }
  196. return xml.Marshal(signedEnvelope{
  197. NS: "http://schemas.xmlsoap.org/soap/envelope/",
  198. Header: *env.Header,
  199. Body: body,
  200. })
  201. }
  202. // SignRequest is a rest.Signer implementation which can be used to sign rest.Client.LoginByTokenBody requests.
  203. func (s *Signer) SignRequest(req *http.Request) error {
  204. type param struct {
  205. key, val string
  206. }
  207. var params []string
  208. add := func(p param) {
  209. params = append(params, fmt.Sprintf(`%s="%s"`, p.key, p.val))
  210. }
  211. var buf bytes.Buffer
  212. gz := gzip.NewWriter(&buf)
  213. if _, err := io.WriteString(gz, s.Token); err != nil {
  214. return fmt.Errorf("zip token: %s", err)
  215. }
  216. if err := gz.Close(); err != nil {
  217. return fmt.Errorf("zip token: %s", err)
  218. }
  219. add(param{
  220. key: "token",
  221. val: base64.StdEncoding.EncodeToString(buf.Bytes()),
  222. })
  223. if s.Certificate != nil {
  224. nonce := fmt.Sprintf("%d:%d", time.Now().UnixNano()/1e6, mrand.Int())
  225. var body []byte
  226. if req.GetBody != nil {
  227. r, rerr := req.GetBody()
  228. if rerr != nil {
  229. return fmt.Errorf("sts: getting http.Request body: %s", rerr)
  230. }
  231. defer r.Close()
  232. body, rerr = ioutil.ReadAll(r)
  233. if rerr != nil {
  234. return fmt.Errorf("sts: reading http.Request body: %s", rerr)
  235. }
  236. }
  237. bhash := sha256.New().Sum(body)
  238. port := req.URL.Port()
  239. if port == "" {
  240. port = "80" // Default port for the "Host" header on the server side
  241. }
  242. var buf bytes.Buffer
  243. msg := []string{
  244. nonce,
  245. req.Method,
  246. req.URL.Path,
  247. strings.ToLower(req.URL.Hostname()),
  248. port,
  249. }
  250. for i := range msg {
  251. buf.WriteString(msg[i])
  252. buf.WriteByte('\n')
  253. }
  254. buf.Write(bhash)
  255. buf.WriteByte('\n')
  256. sum := sha256.Sum256(buf.Bytes())
  257. key, ok := s.Certificate.PrivateKey.(*rsa.PrivateKey)
  258. if !ok {
  259. return errors.New("sts: rsa.PrivateKey is required to sign http.Request")
  260. }
  261. sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:])
  262. if err != nil {
  263. return err
  264. }
  265. add(param{
  266. key: "signature_alg",
  267. val: "RSA-SHA256",
  268. })
  269. add(param{
  270. key: "signature",
  271. val: base64.StdEncoding.EncodeToString(sig),
  272. })
  273. add(param{
  274. key: "nonce",
  275. val: nonce,
  276. })
  277. add(param{
  278. key: "bodyhash",
  279. val: base64.StdEncoding.EncodeToString(bhash),
  280. })
  281. }
  282. req.Header.Set("Authorization", fmt.Sprintf("SIGN %s", strings.Join(params, ", ")))
  283. return nil
  284. }
  285. func (s *Signer) NewRequest() TokenRequest {
  286. return TokenRequest{
  287. Token: s.Token,
  288. Certificate: s.Certificate,
  289. Userinfo: s.user,
  290. KeyID: s.keyID,
  291. }
  292. }