// Html2SAMLResponse extracts the SAMLResponse from a html document
func Html2SAMLResponse(tp *Testparams) (samlresponse *gosaml.Xp) {
	response := gosaml.NewHtmlXp(tp.Responsebody)
	samlbase64 := response.Query1(nil, `//input[@name="SAMLResponse"]/@value`)
	samlxml, _ := base64.StdEncoding.DecodeString(samlbase64)
	samlresponse = gosaml.NewXp(samlxml)
	if _, err := samlresponse.SchemaValidate(samlSchema); err != nil {
		fmt.Println("SchemaError")
	}

	certs := tp.Firstidpmd.Query(nil, `//md:KeyDescriptor[@use="signing" or not(@use)]/ds:KeyInfo/ds:X509Data/ds:X509Certificate`)
	if len(certs) == 0 {
		fmt.Printf("Could not find signing cert for: %s", tp.Firstidpmd.Query1(nil, "/@entityID"))
		log.Printf("Could not find signing cert for: %s", tp.Firstidpmd.Query1(nil, "/@entityID"))
	}

	_, pub, _ := gosaml.PublicKeyInfo(tp.Firstidpmd.NodeGetContent(certs[0]))
	assertion := samlresponse.Query(nil, "saml:Assertion[1]")
	if assertion == nil {
		fmt.Println("no assertion found")
	}
	if err := samlresponse.VerifySignature(assertion[0], pub); err != nil {
		fmt.Printf("SignatureVerificationError %s", err)
	}
	return
}
Exemple #2
0
/**
  One at a time - not that fast - only use for testing
  Filtered by xpath for testing purposes
*/
func (mdq *MDQ) MDQFilter(xpathfilter string) (xp *gosaml.Xp, numberOfEntities int, err error) {
	recs, err := mdq.getEntityList()
	if err != nil {
		return
	}

	// get the entities into an ordered slice
	index := make([]string, len(recs))
	i := 0
	for k, _ := range recs {
		index[i] = k
		i++
	}
	sort.Strings(index)

	//log.Println(xpathfilter)
	xp = gosaml.NewXp([]byte(`<md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" />`))

	for _, entityID := range index {
		ent, _ := mdq.dbget(entityID, false)

		if xpathfilter == "" || len(ent.Query(nil, xpathfilter)) > 0 {
			xp.DocGetRootElement().AddChild(xp.CopyNode(ent.DocGetRootElement(), 1))
			numberOfEntities++
		}
	}
	return
}
Exemple #3
0
func ExampleWayfAttributeHandler() {
	hub_md := gosaml.NewXp(wayfrequestedattributes)
	idp := sourceResponse.Query1(nil, "/samlp:Response/saml:Issuer")

	hub_op, _ := new(lMDQ.MDQ).Open(HUB_OP_MDQ)
	idp_md, _, _ := hub_op.MDQ(idp)
	WayfAttributeHandler(idp_md, hub_md, sourceResponse)
	log.Println(sourceResponse.Pp())
	// output: hi
}
Exemple #4
0
func acsService(w http.ResponseWriter, r *http.Request) (err error) {
	defer r.Body.Close()
	birk, err := r.Cookie("BIRK")
	if err != nil {
		return err
	}
	// to-do: check hmac
	// we checked the request when we received in birkService - we can use it without fear

	request, err := gosaml.DecodeSAMLMsg(birk.Value, true)
	sp_md, err := hub_ops.MDQ(request.Query1(nil, "/samlp:AuthnRequest/saml:Issuer"))

	//    spmd, _, err := edugain.MDQ(request.Query1(nil, "/samlp:AuthnRequest/saml:Issuer"))

	//http.SetCookie(w, &http.Cookie{Name: "BIRK", Value: "", MaxAge: -1, Domain: config["HYBRID_DOMAIN"], Path: "/", Secure: true, HttpOnly: true})

	response, idp_md, _, err := gosaml.GetSAMLMsg(r, "SAMLResponse", hub_ops, hub, hubmd)
	if err != nil {
		return
	}

	hub_md := gosaml.NewXp(Wayfrequestedattributes)
	err = WayfAttributeHandler(idp_md, hub_md, sp_md, response)
	if err != nil {
		return
	}

	birkmd, err := edugain.MDQ(request.Query1(nil, "@Destination"))
	nameid := response.Query(nil, "./saml:Assertion/saml:Subject/saml:NameID")[0]
	// respect nameID in req, give persistent id + all computed attributes + nameformat conversion
	nameidformat := sp_md.Query1(nil, "./md:SPSSODescriptor/md:NameIDFormat")
	if nameidformat == persistent {
		response.QueryDashP(nameid, "@Format", persistent, nil)
		eptid := response.Query1(nil, `./saml:Assertion/saml:AttributeStatement/saml:Attribute[@FriendlyName="eduPersonTargetedID"]/saml:AttributeValue`)
		response.QueryDashP(nameid, ".", eptid, nil)
	} else if nameidformat == transient {
		response.QueryDashP(nameid, ".", gosaml.Id(), nil)
	}

	newresponse := gosaml.NewResponse(stdtiming.Refresh(), birkmd, sp_md, request, response)

	for _, q := range elementsToSign {
		err = gosaml.SignResponse(newresponse, q, birkmd)
		if err != nil {
			return
		}
	}

	// when consent as a service is ready - we will post to that
	acs := newresponse.Query1(nil, "@Destination")

	data := formdata{Acs: acs, Samlresponse: base64.StdEncoding.EncodeToString([]byte(newresponse.X2s()))}
	postform.Execute(w, data)
	return
}
Exemple #5
0
func main() {
	//	logwriter, e := syslog.New(syslog.LOG_NOTICE, "goeleven")
	//	if e == nil {
	//		log.SetOutput(logwriter)
	//	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		<-c
		f, err := os.Create("hybrid.pprof")
		if err != nil {
			log.Fatal(err)
		}
		pprof.WriteHeapProfile(f)
		f.Close()
		os.Exit(1)
	}()

	var err error
	if hub, err = lMDQ.Open("/home/mz/test_hub.mddb"); err != nil {
		log.Println(err)
	}
	if hub_ops, err = lMDQ.Open("/home/mz/test_hub_ops.mddb"); err != nil {
		log.Println(err)
	}
	if edugain, err = lMDQ.Open("/home/mz/test_edugain.mddb"); err != nil {
		log.Println(err)
	}

	hubmd, _ = hub.MDQ(config["HYBRID_HUB"])

	attrs := gosaml.NewXp(Wayfrequestedattributes)
	for _, attr := range attrs.Query(nil, "./md:SPSSODescriptor/md:AttributeConsumingService/md:RequestedAttribute") {
		basic2uri[attr.GetAttr("FriendlyName")] = attr.GetAttr("Name")
	}

	//http.HandleFunc("/status", statushandler)
	http.Handle(config["HYBRID_PUBLIC_PREFIX"], http.FileServer(http.Dir(config["HYBRID_PUBLIC"])))
	http.Handle(config["HYBRID_SSO_SERVICE"], appHandler(ssoService))
	http.Handle(config["HYBRID_ACS"], appHandler(acsService))
	http.Handle(config["HYBRID_BIRK"], appHandler(birkService))
	http.Handle(config["HYBRID_KRIB"], appHandler(kribService))

	log.Println("listening on ", config["HYBRID_INTERFACE"])
	err = http.ListenAndServeTLS(config["HYBRID_INTERFACE"], config["HYBRID_HTTPS_CERT"], config["HYBRID_HTTPS_KEY"], nil)
	//err = openssl.ListenAndServeTLS(config["HYBRID_INTERFACE"], config["HYBRID_HTTPS_CERT"], config["HYBRID_HTTPS_KEY"], nil)

	if err != nil {
		log.Printf("main(): %s\n", err)
	}
}
func b(attrs map[string][]string) (ats *gosaml.Xp) {
	template := []byte(`<saml:AttributeStatement xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema"/>`)
	ats = gosaml.NewXp(template)
	i := 1
	for attr, attrvals := range attrs {
		attrelement := ats.QueryDashP(nil, `saml:Attribute[`+strconv.Itoa(i)+`]`, "", nil)
		ats.QueryDashP(attrelement, "@Name", attr, nil)
		ats.QueryDashP(attrelement, "@NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", nil)
		j := 1
		for _, attrval := range attrvals {
			attrvalelement := ats.QueryDashP(attrelement, `saml:AttributeValue[`+strconv.Itoa(j)+`]`, attrval, nil)
			ats.QueryDashP(attrvalelement, "@xsi:type", "xs:string", nil)
			j = j + 1
		}
		i = i + 1
	}
	return
}
Exemple #7
0
func (mdq *MDQ) dbget(key string, cache bool) (xp *gosaml.Xp, err error) {
	if mdq.stmt == nil {
		mdq.stmt, err = mdq.db.Prepare(`select e.md from entity e, lookup l, validuntil v
        where l.hash = $1 and l.entity_id_fk = e.id and v.validuntil >= $2`)
		if err != nil {
			return
		}
	}

	k := key
	const prefix = "{sha1}"
	if strings.HasPrefix(key, prefix) {
		key = key[6:]
	} else {
		key = hex.EncodeToString(gosaml.Hash(crypto.SHA1, key))
	}

	mdq.Lock.Lock()
	defer mdq.Lock.Unlock()
	cachedxp := mdq.Cache[key]
	if cachedxp != nil && cachedxp.Valid(cacheduration) {
		xp = cachedxp.Xp.CpXp()
		return
	}

	var xml []byte
	err = mdq.stmt.QueryRow(key, time.Now().Unix()).Scan(&xml)
	if err != nil {
		//log.Println("query", mdq.path, k, key, err, string(xml))
		err = fmt.Errorf("Metadata not found for entity: %s", k)
		//debug.PrintStack()
		return
	}
	xp = gosaml.NewXp(xml)
	if cache {
		mdxp := new(MdXp)
		mdxp.Xp = xp
		mdxp.created = time.Now()
		mdq.Cache[key] = mdxp
	}
	return
}
// SendRequest sends a http request - GET or POST using the supplied url, server, method and cookies
// It updates the cookies and returns a http.Response and a posssible response body and error
// The server parameter contains the dns name of the actual server, which should respond to the host part of the url
func (tp *Testparams) sendRequest(url *url.URL, server, method, body string, cookies map[string]map[string]*http.Cookie) (resp *http.Response, responsebody []byte, err error) {
	if server == "" {
		server = url.Host
	}
	server += ":443"

	tr := &http.Transport{
		TLSClientConfig:    &tls.Config{InsecureSkipVerify: true},
		Dial:               func(network, addr string) (net.Conn, error) { return net.Dial("tcp", server) },
		DisableCompression: true,
	}

	client := &http.Client{
		Transport:     tr,
		CheckRedirect: func(req *http.Request, via []*http.Request) error { return errors.New("redirect-not-allowed") },
	}

	var payload io.Reader
	if method == "POST" {
		payload = strings.NewReader(body)
	}

	host := url.Host
	cookiedomain := "wayf.dk"
	req, err := http.NewRequest(method, url.String(), payload)

	for _, cookie := range cookies[cookiedomain] {
		req.AddCookie(cookie)
	}

	if method == "POST" {
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
		req.Header.Add("Content-Length", strconv.Itoa(len(body)))
	}

	req.Header.Add("Host", host)

	resp, err = client.Do(req)
	if err != nil && !strings.HasSuffix(err.Error(), "redirect-not-allowed") {
		// we need to do the redirect ourselves so a self inflicted redirect "error" is not an error
		debug.PrintStack()
		log.Fatalln("client.do", err)
	}

	location, _ := resp.Location()
	loc := ""
	if location != nil {
		loc = location.Host + location.Path
	}

	setcookies := resp.Cookies()
	for _, cookie := range setcookies {
		if cookies[cookiedomain] == nil {
			cookies[cookiedomain] = make(map[string]*http.Cookie)
		}
		cookies[cookiedomain][cookie.Name] = cookie
	}

	// We can't get to the body if we got a redirect pseudo error above
	if err == nil {
		responsebody, err = ioutil.ReadAll(resp.Body)
		defer resp.Body.Close()
	}

	// We didn't get a Location: header - we are POST'ing a SAMLResponse
	if loc == "" {
		response := gosaml.NewHtmlXp(responsebody)
		samlbase64 := response.Query1(nil, `//input[@name="SAMLResponse"]/@value`)
		if samlbase64 != "" {
			samlxml, _ := base64.StdEncoding.DecodeString(samlbase64)
			samlresponse := gosaml.NewXp(samlxml)
			u, _ := url.Parse(samlresponse.Query1(nil, "@Destination"))
			loc = u.Host + u.Path
		}
	}

	if tp.Trace {
		log.Printf("%-4s %-70s %s %-15s %s\n", req.Method, host+req.URL.Path, resp.Proto, resp.Status, loc)
	}

	// we need to nullify the damn redirec-not-allowed error from above
	err = nil
	return
}
// SSOSendRequest2 does the 2nd part of sending the request to the final IdP.
// Creates the response and signs and optionally encrypts it
func (tp *Testparams) SSOSendRequest2() {
	u, _ := tp.Resp.Location()

	// if going via birk we now got a scoped request to the hub
	if tp.Usedoubleproxy {

		if tp.Logxml {
			query := u.Query()
			req, _ := base64.StdEncoding.DecodeString(query["SAMLRequest"][0])
			authnrequest := gosaml.NewXp(gosaml.Inflate(req))
			log.Println("birkrequest", authnrequest.Pp())
		}

		tp.Resp, tp.Responsebody, _ = tp.sendRequest(u, tp.Resolv[u.Host], "GET", "", tp.Cookiejar)
		u, _ = tp.Resp.Location()
	}

	// We still expect to be redirected
	// if we are not at our final IdP something is rotten

	eid := tp.Idpmd.Query1(nil, "@entityID")
	idp, _ := url.Parse(eid)
	if u.Host != idp.Host {
		//log.Println("u.host != idp.Host", u, idp)
		// Errors from HUB is 302 to https://wayf.wayf.dk/displayerror.php ... which is a 500 with html content
		u, _ = tp.Resp.Location()
		tp.Resp, tp.Responsebody, tp.Err = tp.sendRequest(u, tp.Resolv[u.Host], "GET", "", tp.Cookiejar)
		return
	}

	// get the SAMLRequest
	query := u.Query()
	req, _ := base64.StdEncoding.DecodeString(query["SAMLRequest"][0])
	authnrequest := gosaml.NewXp(gosaml.Inflate(req))

	if tp.Logxml {
		log.Println("idprequest", authnrequest.Pp())
	}

	// create a response
	tp.Newresponse = gosaml.NewResponse(gosaml.IdAndTiming{time.Now(), 4 * time.Minute, 4 * time.Hour, "", ""}, tp.Idpmd, tp.Hubspmd, authnrequest, tp.Attributestmt)

	if tp.Logxml {
		log.Println("response", tp.Newresponse.Pp())
	}

	// and sign it
	assertion := tp.Newresponse.Query(nil, "saml:Assertion[1]")[0]

	// use cert to calculate key name
	err := tp.Newresponse.Sign(assertion, tp.Privatekey, tp.Privatekeypw, tp.Certificate, tp.Hashalgorithm)
	if err != nil {
		log.Fatal(err)
	}

	if tp.Encryptresponse {

		certs := tp.Hubspmd.Query(nil, `//md:KeyDescriptor[@use="encryption" or not(@use)]/ds:KeyInfo/ds:X509Data/ds:X509Certificate`)
		if len(certs) == 0 {
			fmt.Errorf("Could not find encryption cert for: %s", tp.Hubspmd.Query1(nil, "/@entityID"))
		}

		_, publickey, _ := gosaml.PublicKeyInfo(tp.Hubspmd.NodeGetContent(certs[0]))

		if tp.Env == "xdev" {
			cert, err := ioutil.ReadFile(*testcertpath)
			pk, err := x509.ParseCertificate(cert)
			if err != nil {
				return
			}
			publickey = pk.PublicKey.(*rsa.PublicKey)
		}

		tp.Newresponse.Encrypt(assertion, publickey)
		tp.Encryptresponse = false // for now only possible for idp -> hub
	}

	return
}
Exemple #10
0
func (mdq *MDQ) Update() (err error) {
	start := time.Now()
	log.Println("lMDQ updating", mdq.Url, mdq.Path)

	_, err = mdq.db.Exec(lMDQSchema)
	if err != nil {
		return
	}

	recs, err := mdq.getEntityList()
	if err != nil {
		return err
	}
	var md []byte
	if md, err = get(mdq.Url); err != nil {
		return
	}

	dom := gosaml.NewXp(md)

	if _, err := dom.SchemaValidate(mdq.MetadataSchemaPath); err != nil {
		log.Println("feed", "SchemaError")
	}

	certificate := dom.Query(nil, "/md:EntitiesDescriptor/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
	if len(certificate) != 1 {
		err = errors.New("Metadata not signed")
		return
	}
	keyname, key, err := gosaml.PublicKeyInfo(dom.NodeGetContent(certificate[0]))

	if err != nil {
		return
	}

	ok := dom.VerifySignature(nil, key)
	if ok != nil || keyname != mdq.Hash {
		return fmt.Errorf("Signature check failed. Signature %s, %s = %s", ok, keyname, mdq.Hash)
	}

	tx, err := mdq.db.Begin()
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			tx.Rollback()
			return
		}
		err = tx.Commit()
	}()

	entityInsertStmt, err := tx.Prepare("insert into entity (entityid, md, hash) values ($1, $2, $3)")
	if err != nil {
		return
	}
	defer entityInsertStmt.Close()

	lookupInsertStmt, err := tx.Prepare("insert or ignore into lookup (hash, entity_id_fk) values (?, ?)")
	if err != nil {
		return err
	}
	defer lookupInsertStmt.Close()

	entityDeleteStmt, err := tx.Prepare("delete from entity where id = $1")
	if err != nil {
		return err
	}
	defer entityDeleteStmt.Close()

	vu, err := time.Parse(time.RFC3339Nano, dom.Query1(nil, "@validUntil"))
	if err != nil {
		return err
	}
	validUntil := vu.Unix()

	var new, updated, nochange, deleted int
	seen := map[string]bool{}

	entities := dom.Query(nil, "./md:EntityDescriptor")
	for _, entity := range entities {
		entityID := dom.Query1(entity, "@entityID")
		if seen[entityID] {
			log.Printf("lMDQ duplicate entityID: %s", entityID)
			continue
		}
		seen[entityID] = true
		md := gosaml.NewXpFromNode(entity).X2s()
		rec := recs[entityID]
		id := rec.id
		hash := hex.EncodeToString(gosaml.Hash(crypto.SHA1, md))
		oldhash := rec.hash
		if rec.hash == hash { // no changes
			delete(recs, entityID) // remove so it won't be deleted
			nochange++
			continue
		} else if oldhash != "" { // update is delete + insert - then the cascading delete will also delete the potential stale lookup entries
			_, err = entityDeleteStmt.Exec(rec.id)
			if err != nil {
				return
			}
			updated++
			log.Printf("lMDQ updated entityID: %s", entityID)
			delete(recs, entityID) // updated - remove so it won't be deleted
		} else {
			new++
			if !mdq.Silent {
				log.Printf("lMDQ new entityID: %s", entityID)
			}
		}
		var res sql.Result
		res, err = entityInsertStmt.Exec(entityID, md, hash)
		if err != nil {
			return err
		}

		id, _ = res.LastInsertId()

		_, err = lookupInsertStmt.Exec(hex.EncodeToString(gosaml.Hash(crypto.SHA1, entityID)), id)
		if err != nil {
			return
		}

		for _, target := range indextargets {
			locations := dom.Query(entity, target)
			for i, location := range locations {
				if !mdq.Silent {
					log.Println(i, dom.NodeGetContent(location))
				}
				_, err = lookupInsertStmt.Exec(hex.EncodeToString(gosaml.Hash(crypto.SHA1, dom.NodeGetContent(location))), id)
				if err != nil {
					return
				}
			}
		}
	}
	for entid, ent := range recs { // delete entities no longer in feed
		_, err = entityDeleteStmt.Exec(ent.id)
		if err != nil {
			return
		}
		deleted++
		log.Printf("lMDQ deleted entityID: %s", entid)
	}

	_, err = tx.Exec("update validuntil set validuntil = $1 where id = 1", validUntil)
	if err != nil {
		return
	}

	log.Printf("lMDQ finished %d new, %d updated, %d unchanged, %d deleted validUntil: %s duration: %.1f",
		new, updated, nochange, deleted, time.Unix(validUntil, 0).Format(time.RFC3339), time.Since(start).Seconds())
	return
}
Exemple #11
0
	sourceResponse = gosaml.NewXp([]byte(`<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                ID="_336333b557cf99d44124c2c2b02b1cc2d4efb4fe2c"
                Version="2.0"
                IssueInstant="2016-02-02T21:33:10Z"
                Destination="https://wayf.wayf.dk/module.php/saml/sp/saml2-acs.php/wayf.wayf.dk"
                InResponseTo="_be2b06534490f9b658487041f1f011348c2834f8aa"
                >
    <saml:Issuer>https://wayf.ait.dtu.dk/saml2/idp/metadata.php</saml:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">"
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <ds:Reference URI="#_336333b557cf99d44124c2c2b02b1cc2d4efb4fe2c">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <ds:DigestValue>CLUJqLbuMsvHggz9sqJqm5PRjBE=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>WKizpTmuMFUN3jv5c1DC4v7XVDOv4i8VrbcF9BCWNGb8BhMpVSar+Z6cwaL91fCtNwrAwq5quloImTjkjWcHi7XY2YEVgC2C3IB7yulhkkz8RxbI6HoLT04wjXcv6PW6kpZQszCMw5Y9zliRuRSk1b90nzuUhvEJWh3v3zFRR52E6jhpwW277ANMHK5AOWTtYuWfocmy6J9JPOKPlxs1fRbO1y9X9+tJ7jABltNMYfXJSoblsFGag/nAadg8ChuDsxzQ+ZNnBRjLjf6+TWclqVj1W7M6hDWJRpAHUotvMYRh2/wpvRzGx30mEcMOkgGP5a21A4BGWBIPJGA5Z6oi5Q==</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIIEszCCA5ugAwIBAgIQBuU97081jlKyFb1aYH7wSTANBgkqhkiG9w0BAQUFADA2MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEgU1NMIENBMB4XDTExMTIxMzAwMDAwMFoXDTE0MTIxMjIzNTk1OVowgY4xCzAJBgNVBAYTAkRLMRAwDgYDVQQIEwdEZW5tYXJrMRQwEgYDVQQHEwtLZ3MuIEx5bmdieTEoMCYGA1UEChMfVGVjaG5pY2FsIFVuaXZlcnNpdHkgb2YgRGVubWFyazETMBEGA1UECxMKSVQgU2VydmljZTEYMBYGA1UEAxMPd2F5Zi5haXQuZHR1LmRrMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA18irEcMKn0RAI8+kxMKMj1vpESz3qLgcILOmzGaHkYCYsiUtAqrHTsmOUYdnE+BfWGEFsngneCoMW/Ct34YCj9CCl9yNqNRXXHnr7+ASMipB7aPODaAfOlxC/W+QNxOgkwfUAcKKA/B2nJ56uPUdtrM3OyQvtcOdkEiCrMTZKb/T5BDOXhM/IeDd2pTPiJUE5WwzanW0RXP7EmLQkygTTFcb2Fh0ARQ+hdZV200U/ERI5MDGj5IR/lurclKcbP9Bdw0/bgwAfVx7bf+XpuxdQN54NuB91Y7kYIiFT66qkN7ST/ZQjdZqU2F5uAtxdCaSTd2taSgKwoClOX4t32QGBwIDAQABo4IBYjCCAV4wHwYDVR0jBBgwFoAUDL2TaAzz3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFG3N9C92Cr7eyc1EWE4bY+rcdDReMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAYBgNVHSAEETAPMA0GCysGAQQBsjEBAgIdMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3JsMG0GCCsGAQUFBwEBBGEwXzA1BggrBgEFBQcwAoYpaHR0cDovL2NydC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5jcnQwJgYIKwYBBQUHMAGGGmh0dHA6Ly9vY3NwLnRjcy50ZXJlbmEub3JnMBoGA1UdEQQTMBGCD3dheWYuYWl0LmR0dS5kazANBgkqhkiG9w0BAQUFAAOCAQEAmWq3MGe1fKFbmtPYc77grgVEi+n5jJHFHKFv/RTCqVrpLE52Z+wKT15HtKQ1ZfQ0hRvoPcmgDzWj1gc1Y33fG/VYxhJNN7TNwxm61PWpgHDaU63KkPxli6oY6DnKixn4QY6tAmEykB88T2qlj2kYGTBPMj5ndHHKVk9QTVcAsTSI1rXrCjtehtN9my2OFVEy7yapM9d6RO7NjxMJnmnqjjiZoRtgmOSOqCXLpn3bAEqzmdTnn8VNS2i8B1tNWOf4nFpoTLhEuOR4n8MwvA+/mf9uknKyvWOysDsBEjM+M1IG25DzC6T+aYx27niBhygDOFRLI+gIr3Odb9ODe+2yqw==</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </samlp:Status>
    <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:xs="http://www.w3.org/2001/XMLSchema"
                    ID="_c5897a405e2fe06cfb8212f22ae143f949a0e18f2b"
                    Version="2.0"
                    IssueInstant="2016-02-02T21:33:10Z"
                    >
        <saml:Issuer>https://wayf.ait.dtu.dk/saml2/idp/metadata.php</saml:Issuer>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
                <ds:Reference URI="#_c5897a405e2fe06cfb8212f22ae143f949a0e18f2b">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                    <ds:DigestValue>XnR0pWAy72jA02bAXqjAUmwa9RU=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>n9xvr50GYOtx3G068EeD6o2F5PPHttEuxZYBeUwzcNr8fuCQue9Ee3uAcuWYGvq2BcylCOyubYqNyqh9aACK0Eewtn0cJoVP0mtrmTXBbn5wFwPpodRM5Vk08t/+rxKxT03kyfaVyHy0IdERDDgtNbY49nIQXdgiSD+pEizqrjbKh8UXXyX/0oM+q7u1FuhQpPt3vx8DWtaiz5eekoTfIjki87agd+cSJT92uhQq3rBRmQadjGGVpVrK/VHjExHa5ar9N+8xcps/ml8QqVlzK8Jkd9WFsIsKv5CEYJdVn3LlokHm2OobRdw2/F0Wa2FN1mzWxQY6amaBqx3jygd0Jg==</ds:SignatureValue>
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>MIIEszCCA5ugAwIBAgIQBuU97081jlKyFb1aYH7wSTANBgkqhkiG9w0BAQUFADA2MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEgU1NMIENBMB4XDTExMTIxMzAwMDAwMFoXDTE0MTIxMjIzNTk1OVowgY4xCzAJBgNVBAYTAkRLMRAwDgYDVQQIEwdEZW5tYXJrMRQwEgYDVQQHEwtLZ3MuIEx5bmdieTEoMCYGA1UEChMfVGVjaG5pY2FsIFVuaXZlcnNpdHkgb2YgRGVubWFyazETMBEGA1UECxMKSVQgU2VydmljZTEYMBYGA1UEAxMPd2F5Zi5haXQuZHR1LmRrMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA18irEcMKn0RAI8+kxMKMj1vpESz3qLgcILOmzGaHkYCYsiUtAqrHTsmOUYdnE+BfWGEFsngneCoMW/Ct34YCj9CCl9yNqNRXXHnr7+ASMipB7aPODaAfOlxC/W+QNxOgkwfUAcKKA/B2nJ56uPUdtrM3OyQvtcOdkEiCrMTZKb/T5BDOXhM/IeDd2pTPiJUE5WwzanW0RXP7EmLQkygTTFcb2Fh0ARQ+hdZV200U/ERI5MDGj5IR/lurclKcbP9Bdw0/bgwAfVx7bf+XpuxdQN54NuB91Y7kYIiFT66qkN7ST/ZQjdZqU2F5uAtxdCaSTd2taSgKwoClOX4t32QGBwIDAQABo4IBYjCCAV4wHwYDVR0jBBgwFoAUDL2TaAzz3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFG3N9C92Cr7eyc1EWE4bY+rcdDReMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAYBgNVHSAEETAPMA0GCysGAQQBsjEBAgIdMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3JsMG0GCCsGAQUFBwEBBGEwXzA1BggrBgEFBQcwAoYpaHR0cDovL2NydC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5jcnQwJgYIKwYBBQUHMAGGGmh0dHA6Ly9vY3NwLnRjcy50ZXJlbmEub3JnMBoGA1UdEQQTMBGCD3dheWYuYWl0LmR0dS5kazANBgkqhkiG9w0BAQUFAAOCAQEAmWq3MGe1fKFbmtPYc77grgVEi+n5jJHFHKFv/RTCqVrpLE52Z+wKT15HtKQ1ZfQ0hRvoPcmgDzWj1gc1Y33fG/VYxhJNN7TNwxm61PWpgHDaU63KkPxli6oY6DnKixn4QY6tAmEykB88T2qlj2kYGTBPMj5ndHHKVk9QTVcAsTSI1rXrCjtehtN9my2OFVEy7yapM9d6RO7NjxMJnmnqjjiZoRtgmOSOqCXLpn3bAEqzmdTnn8VNS2i8B1tNWOf4nFpoTLhEuOR4n8MwvA+/mf9uknKyvWOysDsBEjM+M1IG25DzC6T+aYx27niBhygDOFRLI+gIr3Odb9ODe+2yqw==</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </ds:Signature>
        <saml:Subject>
            <saml:NameID SPNameQualifier="https://wayf.wayf.dk"
                         Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
                         >_470f3e8bd3430425c2a962310b4a8d0a79d3fcf23e</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData NotOnOrAfter="2016-02-02T21:38:10Z"
                                              Recipient="https://wayf.wayf.dk/module.php/saml/sp/saml2-acs.php/wayf.wayf.dk"
                                              InResponseTo="_be2b06534490f9b658487041f1f011348c2834f8aa"
                                              />
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2016-02-02T21:32:40Z"
                         NotOnOrAfter="2016-02-02T21:38:10Z"
                         >
            <saml:AudienceRestriction>
                <saml:Audience>https://wayf.wayf.dk</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2016-02-02T21:09:08Z"
                             SessionNotOnOrAfter="2016-02-03T05:33:10Z"
                             SessionIndex="_ba4740cce62d7b571aa13ff862d1242b37984f4d82"
                             >
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement>
            <saml:Attribute Name="uid"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">madpe</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="mail"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="gn"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">Mads Freek</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="sn"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">Petersen</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="cn"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">Mads Freek Petersen</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="preferredLanguage"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">da-DK</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="organizationName"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">Danmarks Tekniske Universitet</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonPrincipalName"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonPrimaryAffiliation"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">staff</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonScopedAffiliation"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="schacPersonalUniqueID"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">urn:mace:terena.org:schac:personalUniqueID:dk:CPR:2408586234</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonAssurance"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">2</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonEntitlement"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xsi:type="xs:string">urn:mace:terena.org:tcs:escience-user</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>
`))
		"displayName":                 {"Anton Banton Cantonsen"},
	}

	hub              = flag.String("hub", "wayf.wayf.dk", "the hostname for the hub server to be tested")
	hubbe            = flag.String("hubbe", "", "the hub backend server")
	birk             = flag.String("birk", "birk.wayf.dk", "the hostname for the BIRK server to be tested")
	birkbe           = flag.String("birkbe", "", "the birk backend server")
	trace            = flag.Bool("xtrace", false, "trace the request/response flow")
	logxml           = flag.Bool("logxml", false, "dump requests/responses in xml")
	dohub            = flag.Bool("dohub", false, "do test the hub")
	dobirk           = flag.Bool("dobirk", false, "do test BIRK")
	dokrib           = flag.Bool("dokrib", false, "do (only) test KRIB - implies !birk and !hub")
	env              = flag.String("env", "dev", "which environment to test dev, hybrid, prod - if not dev")
	refreshmd        = flag.Bool("refreshmd", true, "update local metadatcache before testing")
	testcertpath     = flag.String("testcertpath", "/etc/ssl/wayf/certs/wildcard.test.lan.pem", "path to the testing cert")
	wayfAttCSDoc     = gosaml.NewXp(main.Wayfrequestedattributes)
	wayfAttCSElement = wayfAttCSDoc.Query(nil, "./md:SPSSODescriptor/md:AttributeConsumingService")[0]

	testSPs *gosaml.Xp

	old, r, w      *os.File
	outC           = make(chan string)
	templatevalues = map[string]map[string]string{
		"prod": {
			"eptid":   "WAYF-DK-c52a92a5467ae336a2be77cd06719c645e72dfd2",
			"pnameid": "WAYF-DK-c52a92a5467ae336a2be77cd06719c645e72dfd2",
		},
		"dev": {
			"eptid":   "WAYF-DK-a7379f69e957371dc49350a27b704093c0b813f1",
			"pnameid": "WAYF-DK-a7379f69e957371dc49350a27b704093c0b813f1",
		},