This repository has been archived on 2022-08-17. You can view files and clone it, but cannot push or open issues or pull requests.
dex/vendor/github.com/russellhaering/goxmldsig/sign.go
Stephan Renatus 076cd77469
run 'go get -u; make revendor'
Signed-off-by: Stephan Renatus <srenatus@chef.io>
2019-07-31 08:09:38 +02:00

256 lines
7 KiB
Go

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
}