/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.auth.realm.ldap;
import org.wildfly.security._private.ElytronMessages;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.evidence.X509PeerCertificateChainEvidence;
import org.wildfly.security.util.ByteIterator;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
/**
* An {@link EvidenceVerifier} that verifies a {@link org.wildfly.security.evidence.X509PeerCertificateChainEvidence}.
*
* @author <a href="mailto:jkalina@redhat.com">Jan Kalina</a>
*/
class X509EvidenceVerifier implements EvidenceVerifier {
private final List<CertificateVerifier> certificateVerifiers;
X509EvidenceVerifier(final List<CertificateVerifier> certificateVerifiers) {
this.certificateVerifiers = certificateVerifiers;
}
/**
* Object allowing to verify X509 certificate against information from LDAP
*/
interface CertificateVerifier {
/**
* Construct set of LDAP attributes, which should be loaded to be able to {@link #verifyCertificate}.
* @param requiredAttributes output set of attribute names
*/
default void addRequiredLdapAttributes(Collection<String> requiredAttributes) {}
/**
* Construct set of LDAP attributes, which should be loaded as binary data.
* @param binaryAttributes output set of attribute names
*/
default void addBinaryLdapAttributes(Collection<String> binaryAttributes) {}
/**
* Verify X509 certificate of user using identity information from LDAP
* @param certificate X509 certificate to verify
* @param attributes LDAP attributes values of given identity
* @return if certificate was accepted by this verifier
* @throws NamingException when problem with LDAP
*/
boolean verifyCertificate(X509Certificate certificate, Attributes attributes) throws NamingException, RealmUnavailableException;
}
static class SerialNumberCertificateVerifier implements CertificateVerifier {
final String ldapAttribute;
SerialNumberCertificateVerifier(String ldapAttribute) {
this.ldapAttribute = ldapAttribute;
}
@Override
public void addRequiredLdapAttributes(Collection<String> requiredAttributes) {
requiredAttributes.add(ldapAttribute);
}
@Override
public boolean verifyCertificate(X509Certificate certificate, Attributes attributes) throws NamingException {
Attribute attribute = attributes.get(ldapAttribute);
final int size = attribute.size();
for (int i = 0; i < size; i++) {
BigInteger value = new BigInteger((String) attribute.get(i));
if (certificate.getSerialNumber().equals(value)) {
return true;
}
}
return false;
}
}
static class SubjectDnCertificateVerifier implements CertificateVerifier {
final String ldapAttribute;
SubjectDnCertificateVerifier(String ldapAttribute) {
this.ldapAttribute = ldapAttribute;
}
@Override
public void addRequiredLdapAttributes(Collection<String> requiredAttributes) {
requiredAttributes.add(ldapAttribute);
}
@Override
public boolean verifyCertificate(X509Certificate certificate, Attributes attributes) throws NamingException {
Attribute attribute = attributes.get(ldapAttribute);
final int size = attribute.size();
for (int i = 0; i < size; i++) {
if (certificate.getSubjectDN().getName().equals(attribute.get(i))) {
return true;
}
}
return false;
}
}
static class DigestCertificateVerifier implements CertificateVerifier {
final String ldapAttribute;
final String algorithm;
DigestCertificateVerifier(String ldapAttribute, String algorithm) {
this.ldapAttribute = ldapAttribute;
this.algorithm = algorithm;
}
@Override
public void addRequiredLdapAttributes(Collection<String> requiredAttributes) {
requiredAttributes.add(ldapAttribute);
}
@Override
public boolean verifyCertificate(X509Certificate certificate, Attributes attributes) throws NamingException, RealmUnavailableException {
Attribute attribute = attributes.get(ldapAttribute);
final int size = attribute.size();
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
for (int i = 0; i < size; i++) {
String digest = ByteIterator.ofBytes(md.digest(certificate.getEncoded())).hexEncode(true).drainToString();
if (digest.equalsIgnoreCase((String) attribute.get(i))) {
return true;
}
}
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
throw new RealmUnavailableException(e);
}
return false;
}
}
static class EncodedCertificateVerifier implements CertificateVerifier {
final String ldapAttribute;
EncodedCertificateVerifier(String ldapAttribute) {
this.ldapAttribute = ldapAttribute;
}
@Override
public void addRequiredLdapAttributes(Collection<String> requiredAttributes) {
requiredAttributes.add(ldapAttribute);
}
@Override
public void addBinaryLdapAttributes(Collection<String> binaryAttributes) {
binaryAttributes.add(ldapAttribute);
}
@Override
public boolean verifyCertificate(X509Certificate certificate, Attributes attributes) throws NamingException, RealmUnavailableException {
Attribute attribute = attributes.get(ldapAttribute);
final int size = attribute.size();
try {
for (int i = 0; i < size; i++) {
if (Arrays.equals(certificate.getEncoded(), (byte[]) attribute.get(i))) {
return true;
}
}
} catch (CertificateEncodingException e) {
throw new RealmUnavailableException(e);
}
return false;
}
}
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
return evidenceType == X509PeerCertificateChainEvidence.class ? SupportLevel.POSSIBLY_SUPPORTED : SupportLevel.UNSUPPORTED;
}
@Override
public IdentityEvidenceVerifier forIdentity(final DirContext dirContext, final String distinguishedName, final String url, Attributes attributes) throws RealmUnavailableException {
return new IdentityEvidenceVerifier() {
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName, Supplier<Provider[]> providers) throws RealmUnavailableException {
return evidenceType == X509PeerCertificateChainEvidence.class ? SupportLevel.POSSIBLY_SUPPORTED : SupportLevel.UNSUPPORTED;
}
@Override
public boolean verifyEvidence(Evidence evidence, Supplier<Provider[]> providers) throws RealmUnavailableException {
if (evidence instanceof X509PeerCertificateChainEvidence) {
X509Certificate certificate = ((X509PeerCertificateChainEvidence) evidence).getFirstCertificate();
try {
for (CertificateVerifier certificateVerifier : certificateVerifiers) {
if ( ! certificateVerifier.verifyCertificate(certificate, attributes)) {
ElytronMessages.log.tracef("X509 client certificate rejected by %s of X509EvidenceVerifier", certificateVerifier);
return false;
}
}
ElytronMessages.log.trace("X509 client certificate accepted by X509EvidenceVerifier");
return true;
} catch (NamingException e) {
throw new RealmUnavailableException(e);
}
}
return false;
}
};
}
@Override
public void addRequiredIdentityAttributes(Collection<String> attributes) {
for (CertificateVerifier verifier : certificateVerifiers) {
verifier.addRequiredLdapAttributes(attributes);
}
}
@Override
public void addBinaryIdentityAttributes(Collection<String> attributes) {
for (CertificateVerifier verifier : certificateVerifiers) {
verifier.addBinaryLdapAttributes(attributes);
}
}
}