Skip to content

Instantly share code, notes, and snippets.

@stnor
Last active December 19, 2018 15:41
Show Gist options
  • Save stnor/1823ed708e9936360402998493a08e68 to your computer and use it in GitHub Desktop.
Save stnor/1823ed708e9936360402998493a08e68 to your computer and use it in GitHub Desktop.
Implementationsanteckningar fed-scim

Implementationsanteckningar Fed-scim

Intro

Federationens infrastruktur är byggd på självsignerade certifikat. Metadata publiceras av organisationer ("entities"). Både klienter och servrars certifikat-fingerprint("pin") finns i metadata-katalogen. Även utfärdande certifkats publika nyckel finns i metadata ("issuers") per organisation.

Skapa certifikat

Servercertifikat

Skapa ett PKCS12 keystore

$ keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650
Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  scim.nomp.se
What is the name of your organizational unit?
  [Unknown]:  NOMP
What is the name of your organization?
  [Unknown]:  Selessia AB
What is the name of your City or Locality?
  [Unknown]:  Sundbyberg
What is the name of your State or Province?
  [Unknown]:
What is the two-letter country code for this unit?
  [Unknown]:  SE
Is CN=scim.nomp.se, OU=NOMP, O=Selessia AB, L=Sundbyberg, ST=Unknown, C=SE correct?
  [no]:  yes

$ openssl pkcs12 -in keystore.p12 -clcerts -nokeys -out server.pem
# Skapa pin för server cert
$ openssl x509 -in server.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Klientcertifikat

$ keytool -genkey -alias client -keyalg RSA -keysize 2048 -storetype PKCS12 -keypass changeit -storepass changeit -keystore client.p12
openssl pkcs12 -in server.p12 -out ca.pem -nokeys
openssl pkcs12 -in client.p12 -out client.pem -nokeys
openssl pkcs12 -in client.p12 -out key.pem -nocerts
#Testa
curl -v --cacert ./ca.pem   --cert ./client.pem:changeit --key ./key.pem https://scim.nomp.se:9443/scim/v2/Organisations

# Skapa pin för klient-cert
$ openssl x509 -in client.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Hur implementerar man server autenticering av klientanrop?

  1. Ha en schemalagd hämtning av metadata. På Nomp hämtas metadata varje timme.
  2. Bortse från validering av klient-certifikat. I java kan man använda en egen TrustManager.
  3. När det kommer ett klient-certifikat, så gör autentisering genom att hasha certifikatets publika nyckel med sha-256 (enda supportade algoritmen f.n.) och leta efter vilken organisation i metadatat som har en matchande hash ("pin") under clients.

Kodexempel i Java:

/**
 * A trust manager that accepts all certificates
 */
@Slf4j
public class FederationTrustManager implements X509TrustManager {

    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        // Allow all
    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        throw new RuntimeException("Should not be invoked for a trust store");
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        List<X509Certificate> certificateIssuers = getIssuers();
        X509Certificate[] x509Certificates = certificateIssuers.toArray(new X509Certificate[certificateIssuers.size()]);
        log.debug("Get accepted issuers returned {} issuers", x509Certificates.length);
        return x509Certificates;
    }

    private List<X509Certificate> getIssuers() {
        return FederationMetadataRegistry.INSTANCE.getCertificateIssuers();
    }

}

@Slf4j
@Component
public class CertUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

    @Autowired
    private FederationMetadataRegistry metadataRegistry;

    @Override
    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken preAuthenticatedAuthenticationToken) throws UsernameNotFoundException {
        X509Certificate clientCert = (X509Certificate) preAuthenticatedAuthenticationToken.getCredentials();
        String pinValue = pinValueForCert(clientCert);
        log.debug("Cert pin value is {}", pinValue);

        Optional<FederationEntity> entityIdForPin = metadataRegistry.getEntityIdForPin(pinValue);
        if (entityIdForPin.isPresent()) {
            return new CertUserDetails(entityIdForPin.get());
        }
        throw new UsernameNotFoundException("Found no matching entityId for pin:" + pinValue);
    }

    static String pinValueForCert(X509Certificate cert) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] der = cert.getPublicKey().getEncoded();
            md.update(der);
            byte[] digest = md.digest();
            byte[] b64Digest = Base64.getEncoder().encode(digest);
            return new String(b64Digest);
        } catch (Exception e) {
            log.warn("Error generating pin-value for certificate", e);
            throw new RuntimeException(e);
        }
    }
}

@Slf4j
@Component
public class FederationMetadataRegistry {

    /** Trust manager access */
    public static FederationMetadataRegistry INSTANCE;

    private FederationMetadata metadata;

    @Autowired
    private MetadataClient metadataClient;

    @PostConstruct
    public void init() {
        INSTANCE = this;
    }

    // Run every hour
    @Scheduled(fixedDelay = 3600 * 1000L)
    @Synchronized
    private void fetchMetadata() {
        metadata = metadataClient.fetchMetadata();
    }

    @Synchronized
    public List<X509Certificate> getCertificateIssuers() {
        return metadata.getEntities().stream().flatMap(e -> e.getIssuers().stream().map(FederationIssuer::getCertificate))
                .filter(cert -> cert != null).collect(Collectors.toList());
    }

    @Synchronized
    public Optional<FederationEntity> getEntityIdForPin(String pin) {
        for (FederationEntity entity : metadata.getEntities()) {
            if (entity.hasMatchingPin(pin)) {
                return Optional.of(entity);
            }
        }
        return Optional.empty();
    }
}
Spring boot

Om man använder Spring boot och/eller Tomcat så behöver man sätta "trustManagerClassName" på connectorn.

@Slf4j
@Component
public class TomcatWebServiceCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.getTomcatConnectorCustomizers().add(connector -> connector.setAttribute("trustManagerClassName", FederationTrustManager.class.getName()));
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment