// Copyright (c) 2025 Probo Inc . // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. package auth import ( "crypto/x509" "encoding/pem" "fmt" "net/url" pemutil "go.probo.inc/probo/pkg/crypto/pem" ) // ValidateIdPConfiguration validates only the IdP (Identity Provider) configuration. // This validates user-provided data from the IdP. // SP (Service Provider) configuration is generated by the application and doesn't need validation. func ValidateIdPConfiguration( idpEntityID string, idpSsoURL string, idpCertificate string, ) error { // Validate IdP Entity ID if idpEntityID == "" { return fmt.Errorf("IdP Entity ID cannot be empty") } // Validate IdP SSO URL - accept both HTTP and HTTPS if err := validateURL(idpSsoURL); err != nil { return err } // Validate IdP certificate if err := validateCertificate(idpCertificate); err != nil { return err } return nil } func validateURL(urlStr string) error { if urlStr == "" { return fmt.Errorf("URL cannot be empty") } parsedURL, err := url.Parse(urlStr) if err != nil { return fmt.Errorf("invalid URL format: %w", err) } if parsedURL.Scheme == "" { return fmt.Errorf("URL must have a scheme (http or https)") } if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { return fmt.Errorf("URL scheme must be http or https (found: %s)", parsedURL.Scheme) } if parsedURL.Host == "" { return fmt.Errorf("URL must have a host") } return nil } func validateCertificate(certPEM string) error { if certPEM == "" { return fmt.Errorf("certificate cannot be empty") } block, _ := pem.Decode([]byte(certPEM)) if block == nil { return fmt.Errorf("cannot parse certificate PEM") } if block.Type != pemutil.BlockTypeCertificate { return fmt.Errorf("PEM block type must be CERTIFICATE (found: %s)", block.Type) } _, err := x509.ParseCertificate(block.Bytes) if err != nil { return fmt.Errorf("cannot parse X.509 certificate: %w", err) } return nil }