package main import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "os" "path/filepath" "sort" "strings" "ocspcrl/internal/ocsp_source" ) const ( crlFileName = "crl.pem" keyFileName = "key.pem" responderFileName = "responder.crt" caFileName = "ca.crt" ) type caFiles struct { key string responder string ca string crl string } func newCaFiles(directory string) caFiles { return caFiles{ key: filepath.Join(directory, keyFileName), responder: filepath.Join(directory, responderFileName), ca: filepath.Join(directory, caFileName), crl: filepath.Join(directory, crlFileName), } } func (f caFiles) ensureExist(caName string) error { for _, path := range []string{f.key, f.responder, f.ca, f.crl} { if _, statError := os.Stat(path); statError != nil { return fmt.Errorf("ca %q: %w", caName, statError) } } return nil } func verifyResponderIssuedByCa(responder tls.Certificate, ca *x509.Certificate) error { if responder.Leaf == nil { return fmt.Errorf("responder leaf certificate could not be parsed") } if !bytes.Equal(ca.RawSubject, responder.Leaf.RawIssuer) { return fmt.Errorf("responder certificate issuer does not match ca certificate subject; %+q != %+q", ca.Subject.String(), responder.Leaf.Issuer.String()) } return nil } func loadCa(name, directory string) (*caInstance, error) { files := newCaFiles(directory) if existsError := files.ensureExist(name); existsError != nil { return nil, existsError } responderKeyPair, loadResponderError := tls.LoadX509KeyPair(files.responder, files.key) if loadResponderError != nil { return nil, fmt.Errorf("ca %q: failed to load responder key pair: %w", name, loadResponderError) } caCertificate, loadCaError := loadCertificateFromFile(files.ca) if loadCaError != nil { return nil, fmt.Errorf("ca %q: failed to load ca certificate: %w", name, loadCaError) } if verifyError := verifyResponderIssuedByCa(responderKeyPair, caCertificate); verifyError != nil { return nil, fmt.Errorf("ca %q: %w", name, verifyError) } instance := &caInstance{ name: name, crlPath: files.crl, caCertificate: caCertificate, source: ocsp_source.NewCrlSource(caCertificate, responderKeyPair), } if reloadError := instance.reloadCrl(); reloadError != nil { return nil, fmt.Errorf("ca %q: failed to load crl: %w", name, reloadError) } return instance, nil } func listCaSubdirectories(rootDir string) ([]string, error) { entries, readDirError := os.ReadDir(rootDir) if readDirError != nil { return nil, fmt.Errorf("failed to read cas directory: %w", readDirError) } names := []string{} for _, entry := range entries { if !entry.IsDir() { continue } if strings.HasPrefix(entry.Name(), ".") { continue } names = append(names, entry.Name()) } sort.Strings(names) return names, nil } func discoverCas(rootDir string) ([]*caInstance, error) { names, listError := listCaSubdirectories(rootDir) if listError != nil { return nil, listError } if len(names) == 0 { return nil, fmt.Errorf("no ca subdirectories found in %s", rootDir) } cas := make([]*caInstance, 0, len(names)) for _, name := range names { instance, loadError := loadCa(name, filepath.Join(rootDir, name)) if loadError != nil { return nil, loadError } cas = append(cas, instance) } return cas, nil }