sign.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package dsig
  2. import (
  3. "crypto"
  4. "crypto/rand"
  5. "crypto/rsa"
  6. _ "crypto/sha1"
  7. _ "crypto/sha256"
  8. "encoding/base64"
  9. "errors"
  10. "fmt"
  11. "github.com/beevik/etree"
  12. "github.com/russellhaering/goxmldsig/etreeutils"
  13. )
  14. type SigningContext struct {
  15. Hash crypto.Hash
  16. KeyStore X509KeyStore
  17. IdAttribute string
  18. Prefix string
  19. Canonicalizer Canonicalizer
  20. }
  21. func NewDefaultSigningContext(ks X509KeyStore) *SigningContext {
  22. return &SigningContext{
  23. Hash: crypto.SHA256,
  24. KeyStore: ks,
  25. IdAttribute: DefaultIdAttr,
  26. Prefix: DefaultPrefix,
  27. Canonicalizer: MakeC14N11Canonicalizer(),
  28. }
  29. }
  30. func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error {
  31. hash, ok := signatureMethodsByIdentifier[algorithmID]
  32. if !ok {
  33. return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID)
  34. }
  35. ctx.Hash = hash
  36. return nil
  37. }
  38. func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) {
  39. canonical, err := ctx.Canonicalizer.Canonicalize(el)
  40. if err != nil {
  41. return nil, err
  42. }
  43. hash := ctx.Hash.New()
  44. _, err = hash.Write(canonical)
  45. if err != nil {
  46. return nil, err
  47. }
  48. return hash.Sum(nil), nil
  49. }
  50. func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) {
  51. digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier()
  52. if digestAlgorithmIdentifier == "" {
  53. return nil, errors.New("unsupported hash mechanism")
  54. }
  55. signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier()
  56. if signatureMethodIdentifier == "" {
  57. return nil, errors.New("unsupported signature method")
  58. }
  59. digest, err := ctx.digest(el)
  60. if err != nil {
  61. return nil, err
  62. }
  63. signedInfo := &etree.Element{
  64. Tag: SignedInfoTag,
  65. Space: ctx.Prefix,
  66. }
  67. // /SignedInfo/CanonicalizationMethod
  68. canonicalizationMethod := ctx.createNamespacedElement(signedInfo, CanonicalizationMethodTag)
  69. canonicalizationMethod.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
  70. // /SignedInfo/SignatureMethod
  71. signatureMethod := ctx.createNamespacedElement(signedInfo, SignatureMethodTag)
  72. signatureMethod.CreateAttr(AlgorithmAttr, signatureMethodIdentifier)
  73. // /SignedInfo/Reference
  74. reference := ctx.createNamespacedElement(signedInfo, ReferenceTag)
  75. dataId := el.SelectAttrValue(ctx.IdAttribute, "")
  76. if dataId == "" {
  77. return nil, errors.New("Missing data ID")
  78. }
  79. reference.CreateAttr(URIAttr, "#"+dataId)
  80. // /SignedInfo/Reference/Transforms
  81. transforms := ctx.createNamespacedElement(reference, TransformsTag)
  82. if enveloped {
  83. envelopedTransform := ctx.createNamespacedElement(transforms, TransformTag)
  84. envelopedTransform.CreateAttr(AlgorithmAttr, EnvelopedSignatureAltorithmId.String())
  85. }
  86. canonicalizationAlgorithm := ctx.createNamespacedElement(transforms, TransformTag)
  87. canonicalizationAlgorithm.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
  88. // /SignedInfo/Reference/DigestMethod
  89. digestMethod := ctx.createNamespacedElement(reference, DigestMethodTag)
  90. digestMethod.CreateAttr(AlgorithmAttr, digestAlgorithmIdentifier)
  91. // /SignedInfo/Reference/DigestValue
  92. digestValue := ctx.createNamespacedElement(reference, DigestValueTag)
  93. digestValue.SetText(base64.StdEncoding.EncodeToString(digest))
  94. return signedInfo, nil
  95. }
  96. func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) (*etree.Element, error) {
  97. signedInfo, err := ctx.constructSignedInfo(el, enveloped)
  98. if err != nil {
  99. return nil, err
  100. }
  101. sig := &etree.Element{
  102. Tag: SignatureTag,
  103. Space: ctx.Prefix,
  104. }
  105. xmlns := "xmlns"
  106. if ctx.Prefix != "" {
  107. xmlns += ":" + ctx.Prefix
  108. }
  109. sig.CreateAttr(xmlns, Namespace)
  110. sig.AddChild(signedInfo)
  111. // When using xml-c14n11 (ie, non-exclusive canonicalization) the canonical form
  112. // of the SignedInfo must declare all namespaces that are in scope at it's final
  113. // enveloped location in the document. In order to do that, we're going to construct
  114. // a series of cascading NSContexts to capture namespace declarations:
  115. // First get the context surrounding the element we are signing.
  116. rootNSCtx, err := etreeutils.NSBuildParentContext(el)
  117. if err != nil {
  118. return nil, err
  119. }
  120. // Then capture any declarations on the element itself.
  121. elNSCtx, err := rootNSCtx.SubContext(el)
  122. if err != nil {
  123. return nil, err
  124. }
  125. // Followed by declarations on the Signature (which we just added above)
  126. sigNSCtx, err := elNSCtx.SubContext(sig)
  127. if err != nil {
  128. return nil, err
  129. }
  130. // Finally detatch the SignedInfo in order to capture all of the namespace
  131. // declarations in the scope we've constructed.
  132. detatchedSignedInfo, err := etreeutils.NSDetatch(sigNSCtx, signedInfo)
  133. if err != nil {
  134. return nil, err
  135. }
  136. digest, err := ctx.digest(detatchedSignedInfo)
  137. if err != nil {
  138. return nil, err
  139. }
  140. key, cert, err := ctx.KeyStore.GetKeyPair()
  141. if err != nil {
  142. return nil, err
  143. }
  144. certs := [][]byte{cert}
  145. if cs, ok := ctx.KeyStore.(X509ChainStore); ok {
  146. certs, err = cs.GetChain()
  147. if err != nil {
  148. return nil, err
  149. }
  150. }
  151. rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest)
  152. if err != nil {
  153. return nil, err
  154. }
  155. signatureValue := ctx.createNamespacedElement(sig, SignatureValueTag)
  156. signatureValue.SetText(base64.StdEncoding.EncodeToString(rawSignature))
  157. keyInfo := ctx.createNamespacedElement(sig, KeyInfoTag)
  158. x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag)
  159. for _, cert := range certs {
  160. x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag)
  161. x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert))
  162. }
  163. return sig, nil
  164. }
  165. func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string) *etree.Element {
  166. child := el.CreateElement(tag)
  167. child.Space = ctx.Prefix
  168. return child
  169. }
  170. func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, error) {
  171. sig, err := ctx.ConstructSignature(el, true)
  172. if err != nil {
  173. return nil, err
  174. }
  175. ret := el.Copy()
  176. ret.Child = append(ret.Child, sig)
  177. return ret, nil
  178. }
  179. func (ctx *SigningContext) GetSignatureMethodIdentifier() string {
  180. if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok {
  181. return ident
  182. }
  183. return ""
  184. }
  185. func (ctx *SigningContext) GetDigestAlgorithmIdentifier() string {
  186. if ident, ok := digestAlgorithmIdentifiers[ctx.Hash]; ok {
  187. return ident
  188. }
  189. return ""
  190. }
  191. // Useful for signing query string (including DEFLATED AuthnRequest) when
  192. // using HTTP-Redirect to make a signed request.
  193. // See 3.4.4.1 DEFLATE Encoding of https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
  194. func (ctx *SigningContext) SignString(content string) ([]byte, error) {
  195. hash := ctx.Hash.New()
  196. if ln, err := hash.Write([]byte(content)); err != nil {
  197. return nil, fmt.Errorf("error calculating hash: %v", err)
  198. } else if ln < 1 {
  199. return nil, fmt.Errorf("zero length hash")
  200. }
  201. digest := hash.Sum(nil)
  202. var signature []byte
  203. if key, _, err := ctx.KeyStore.GetKeyPair(); err != nil {
  204. return nil, fmt.Errorf("unable to fetch key for signing: %v", err)
  205. } else if signature, err = rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest); err != nil {
  206. return nil, fmt.Errorf("error signing: %v", err)
  207. }
  208. return signature, nil
  209. }