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 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
$ 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
- Ha en schemalagd hämtning av metadata. På Nomp hämtas metadata varje timme.
- Bortse från validering av klient-certifikat. I java kan man använda en egen TrustManager.
- 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.
/**
* 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();
}
}
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()));
}
}