Last active
February 7, 2025 21:08
-
-
Save ewollesen/34a7d19af71c254afb0b7dba9990f597 to your computer and use it in GitHub Desktop.
utility for extracting SSO data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module keycloak-idp-phase2 | |
go 1.21.5 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// keycloak-id-phase2 a small utility to extract useful info from the XML | |
// metadata returned by clinics when integrating SSO. | |
// | |
// git clone [email protected]: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.Fatalf("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") | |
} | |
trimmedCerts := strings.Split(trimmedCert, ",") | |
for _, trimmedCert := range trimmedCerts { | |
cert, err := parseCert(trimmedCert) | |
if err != nil { | |
log.Fatalf("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.Printf("warning: cert failed validation (this isn't necessarily an error): %s", 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