Skip to content

Instantly share code, notes, and snippets.

@ewollesen
Last active January 12, 2024 23:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ewollesen/34a7d19af71c254afb0b7dba9990f597 to your computer and use it in GitHub Desktop.
Save ewollesen/34a7d19af71c254afb0b7dba9990f597 to your computer and use it in GitHub Desktop.
utility for extracting SSO data
module keycloak-idp-phase2
go 1.21.5
package main
// keycloak-id-phase2 a small utility to extract useful info from the XML
// metadata returned by clinics when integrating SSO.
//
// git clone git@gist.github.com:34a7d19af71c254afb0b7dba9990f597.git keycloak-idp-phase2
//
// $ ./keycloak-idp-phase2 < metadata.xml
import (
"crypto/x509"
"encoding/base64"
"encoding/xml"
"flag"
"fmt"
"log"
"net/url"
"os"
"regexp"
"strings"
)
var PostBindingRE = regexp.MustCompile(`bindings:HTTP-POST`)
type Metadata struct {
SigningCert struct {
Data string `xml:",innerxml"`
} `xml:"IDPSSODescriptor>KeyDescriptor>KeyInfo>X509Data>X509Certificate"`
SSOServices []struct {
Binding string `xml:",attr"`
Location string `xml:",attr"`
} `xml:"IDPSSODescriptor>SingleSignOnService"`
}
// parseCert makes sure that the certificate data is parseable.
func parseCert(cert string) (*x509.Certificate, error) {
der, err := base64.StdEncoding.DecodeString(strings.TrimSpace(cert))
if err != nil {
return nil, fmt.Errorf("base64 decoding: %w", err)
}
parsed, err := x509.ParseCertificate(der)
if err != nil {
return nil, fmt.Errorf("parsing certificate data: %w", err)
}
return parsed, nil
}
// validateCertAndURL ensures that the signing certificate applies to the SSO
// Service URL.
func validateCertAndURL(cert *x509.Certificate, ssoServiceURL string) error {
ssoURL, err := url.Parse(ssoServiceURL)
if err != nil {
return fmt.Errorf("SSO service location failed to parse: %s", err)
} else {
if err := cert.VerifyHostname(ssoURL.Host); err != nil {
return fmt.Errorf("certificate isn't valid for SSO service location: %s", err)
}
}
return nil
}
func main() {
var xmlFilename string
flag.StringVar(&xmlFilename, "xml-filename", "-", "the XML metadata from the SSO partner clinic")
flag.Parse()
var f *os.File = os.Stdin
if xmlFilename != "-" {
xmlFile, err := os.Open(xmlFilename)
if err != nil {
log.Fatalf("opening XML filename %q: %s", xmlFilename, err)
}
defer xmlFile.Close()
f = xmlFile
}
metadata := &Metadata{}
if err := xml.NewDecoder(f).Decode(&metadata); err != nil {
log.Fatal("parsing XML: %s", err)
}
var ssoServiceLocation string
for _, ssoService := range metadata.SSOServices {
if PostBindingRE.MatchString(ssoService.Binding) {
ssoServiceLocation = ssoService.Location
}
}
if strings.TrimSpace(ssoServiceLocation) == "" {
log.Fatal("no SSO service location found")
}
trimmedCert := strings.TrimSpace(metadata.SigningCert.Data)
if trimmedCert == "" {
log.Fatal("no X509 signing certificate found")
}
cert, err := parseCert(trimmedCert)
if err != nil {
log.Fatal("X509 signing certificate failed to parse: %s", err)
}
if cert == nil {
log.Fatal("no X509 certificate (this shouldn't happen!)")
}
if err := validateCertAndURL(cert, ssoServiceLocation); err != nil {
log.Fatal(err)
}
fmt.Printf("single_sign_on_service_url = \"%s\"\n", ssoServiceLocation)
fmt.Printf("signing_certificate = \"%s\"\n", strings.TrimSpace(metadata.SigningCert.Data))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment