| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- package dsig
- import (
- "crypto"
- "crypto/rand"
- "crypto/rsa"
- _ "crypto/sha1"
- _ "crypto/sha256"
- "encoding/base64"
- "errors"
- "fmt"
- "github.com/beevik/etree"
- "github.com/russellhaering/goxmldsig/etreeutils"
- )
- 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 := ctx.GetDigestAlgorithmIdentifier()
- if digestAlgorithmIdentifier == "" {
- return nil, errors.New("unsupported hash mechanism")
- }
- signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier()
- if signatureMethodIdentifier == "" {
- 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(ctx.IdAttribute, "")
- 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.AddChild(signedInfo)
- // When using xml-c14n11 (ie, non-exclusive canonicalization) the canonical form
- // of the SignedInfo must declare all namespaces that are in scope at it's final
- // enveloped location in the document. In order to do that, we're going to construct
- // a series of cascading NSContexts to capture namespace declarations:
- // First get the context surrounding the element we are signing.
- rootNSCtx, err := etreeutils.NSBuildParentContext(el)
- if err != nil {
- return nil, err
- }
- // Then capture any declarations on the element itself.
- elNSCtx, err := rootNSCtx.SubContext(el)
- if err != nil {
- return nil, err
- }
- // Followed by declarations on the Signature (which we just added above)
- sigNSCtx, err := elNSCtx.SubContext(sig)
- if err != nil {
- return nil, err
- }
- // Finally detatch the SignedInfo in order to capture all of the namespace
- // declarations in the scope we've constructed.
- detatchedSignedInfo, err := etreeutils.NSDetatch(sigNSCtx, signedInfo)
- if err != nil {
- return nil, err
- }
- digest, err := ctx.digest(detatchedSignedInfo)
- if err != nil {
- return nil, err
- }
- key, cert, err := ctx.KeyStore.GetKeyPair()
- if err != nil {
- return nil, err
- }
- certs := [][]byte{cert}
- if cs, ok := ctx.KeyStore.(X509ChainStore); ok {
- certs, err = cs.GetChain()
- 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)
- for _, cert := range certs {
- 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
- }
- func (ctx *SigningContext) GetSignatureMethodIdentifier() string {
- if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok {
- return ident
- }
- return ""
- }
- func (ctx *SigningContext) GetDigestAlgorithmIdentifier() string {
- if ident, ok := digestAlgorithmIdentifiers[ctx.Hash]; ok {
- return ident
- }
- return ""
- }
- // Useful for signing query string (including DEFLATED AuthnRequest) when
- // using HTTP-Redirect to make a signed request.
- // See 3.4.4.1 DEFLATE Encoding of https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
- func (ctx *SigningContext) SignString(content string) ([]byte, error) {
- hash := ctx.Hash.New()
- if ln, err := hash.Write([]byte(content)); err != nil {
- return nil, fmt.Errorf("error calculating hash: %v", err)
- } else if ln < 1 {
- return nil, fmt.Errorf("zero length hash")
- }
- digest := hash.Sum(nil)
- var signature []byte
- if key, _, err := ctx.KeyStore.GetKeyPair(); err != nil {
- return nil, fmt.Errorf("unable to fetch key for signing: %v", err)
- } else if signature, err = rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest); err != nil {
- return nil, fmt.Errorf("error signing: %v", err)
- }
- return signature, nil
- }
|