org.wildfly.security.sasl.entity.EntitySaslClient Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2015 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.sasl.entity;
import static org.wildfly.security.asn1.ASN1.CONTEXT_SPECIFIC_MASK;
import static org.wildfly.security.mechanism._private.ElytronMessages.saslEntity;
import static org.wildfly.security.sasl.entity.Entity.keyType;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.SaslException;
import org.wildfly.common.Assert;
import org.wildfly.security.asn1.ASN1Exception;
import org.wildfly.security.asn1.DERDecoder;
import org.wildfly.security.asn1.DEREncoder;
import org.wildfly.security.auth.callback.CredentialCallback;
import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
import org.wildfly.security.auth.callback.TrustedAuthoritiesCallback;
import org.wildfly.security.credential.X509CertificateChainPrivateCredential;
import org.wildfly.security.evidence.X509PeerCertificateChainEvidence;
import org.wildfly.security.x500.GeneralName;
import org.wildfly.security.x500.GeneralName.DNSName;
import org.wildfly.security.x500.GeneralName.DirectoryName;
import org.wildfly.security.sasl.util.AbstractSaslClient;
import org.wildfly.security.x500.TrustedAuthority;
/**
* SaslClient for the ISO/IEC 9798-3 authentication mechanism as defined by
* RFC 3163.
*
* @author Farah Juma
*/
final class EntitySaslClient extends AbstractSaslClient {
private static final int ST_CHALLENGE_RESPONSE = 1;
private static final int ST_RESPONSE_SENT = 2;
private final SecureRandom secureRandom;
private final Signature signature;
private final boolean mutual;
private final String serverName;
private byte[] randomA;
private byte[] randomB;
private X509Certificate[] clientCertChain;
EntitySaslClient(final String mechanismName, final boolean mutual, final Signature signature, final SecureRandom secureRandom, final String protocol,
final String serverName, final CallbackHandler callbackHandler, final String authorizationId) {
super(mechanismName, protocol, serverName, callbackHandler, authorizationId, false, saslEntity);
this.signature = signature;
this.secureRandom = secureRandom;
this.mutual = mutual;
this.serverName = serverName;
}
@Override
public void init() {
setNegotiationState(ST_CHALLENGE_RESPONSE);
}
@Override
protected byte[] evaluateMessage(final int state, final byte[] challenge) throws SaslException {
switch (state) {
case ST_CHALLENGE_RESPONSE: {
final DERDecoder decoder = new DERDecoder(challenge);
List trustedAuthorities = null;
List entityB = null;
try {
// == Parse message ==
decoder.startSequence();
// randomB
randomB = decoder.decodeOctetString();
// entityB
if ((serverName != null) && (! serverName.isEmpty())) {
entityB = new ArrayList(1);
entityB.add(new DNSName(serverName));
}
if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, 0, true)) {
decoder.decodeImplicit(0);
List decodedEntityB = EntityUtil.decodeGeneralNames(decoder);
if ((entityB != null) && (! EntityUtil.matchGeneralNames(decodedEntityB, entityB))) {
throw saslEntity.mechServerIdentifierMismatch().toSaslException();
}
}
// certPref
if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, 1, true)) {
decoder.decodeImplicit(1);
trustedAuthorities = EntityUtil.decodeTrustedAuthorities(decoder);
}
decoder.endSequence();
} catch (ASN1Exception e) {
throw saslEntity.mechInvalidServerMessageWithCause(e).toSaslException();
}
// == Send response ==
// Construct TokenAB, where:
// TokenAB ::= SEQUENCE {
// randomA RandomNumber,
// entityB [0] GeneralNames OPTIONAL,
// certA [1] CertData,
// authId [2] GeneralNames OPTIONAL,
// signature SIGNATURE { TBSDataAB }
// }
// TBSDataAB ::= SEQUENCE {
// randomA RandomNumber,
// randomB RandomNumber,
// entityB [0] GeneralNames OPTIONAL,
// authId [1] GeneralNames OPTIONAL
// }
// CertData ::= CHOICE {
// certificateSet SET SIZE (1..MAX) OF Certificate
// certURL IA5String (Note: No support for certificate URL)
// }
// SIGNATURE { ToBeSigned } ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// signature BIT STRING
// }
final DEREncoder encoder = new DEREncoder();
try {
encoder.startSequence();
// randomA
randomA = EntityUtil.encodeRandomNumber(encoder, secureRandom);
// entityB
if (entityB != null) {
encoder.encodeImplicit(0);
EntityUtil.encodeGeneralNames(encoder, entityB);
}
// certA (try obtaining a certificate chain)
encoder.startExplicit(1);
TrustedAuthoritiesCallback trustedAuthoritiesCallback = new TrustedAuthoritiesCallback();
trustedAuthoritiesCallback.setTrustedAuthorities(trustedAuthorities); // Server's preferred certificates
CredentialCallback credentialCallback = new CredentialCallback(X509CertificateChainPrivateCredential.class, keyType(signature.getAlgorithm()));
PrivateKey privateKey;
try {
tryHandleCallbacks(trustedAuthoritiesCallback, credentialCallback);
final X509CertificateChainPrivateCredential clientCertChainPrivateCredential = credentialCallback.getCredential(X509CertificateChainPrivateCredential.class);
if (clientCertChainPrivateCredential != null) {
clientCertChain = clientCertChainPrivateCredential.getCertificateChain();
if ((clientCertChain != null) && (clientCertChain.length > 0)) {
EntityUtil.encodeX509CertificateChain(encoder, clientCertChain);
} else {
throw saslEntity.mechCallbackHandlerNotProvidedClientCertificate().toSaslException();
}
privateKey = clientCertChainPrivateCredential.getPrivateKey();
} else {
throw saslEntity.mechCallbackHandlerNotProvidedClientCertificate().toSaslException();
}
} catch (UnsupportedCallbackException e) {
throw saslEntity.mechCallbackHandlerNotProvidedClientCertificate().toSaslException();
}
encoder.endExplicit();
// authId
final String authorizationId = getAuthorizationId();
List authId = null;
if (authorizationId != null) {
encoder.encodeImplicit(2);
// TODO: Will authorizationId be a distinguished name or is a callback needed to
// determine the appropriate GeneralName type to use?
authId = new ArrayList(1);
authId.add(new DirectoryName(authorizationId));
EntityUtil.encodeGeneralNames(encoder, authId);
}
// Private key
if (privateKey == null) {
throw saslEntity.mechCallbackHandlerNotProvidedPrivateKey().toSaslException();
}
// TBSDataAB
final DEREncoder tbsEncoder = new DEREncoder();
tbsEncoder.startSequence();
tbsEncoder.encodeOctetString(randomA);
tbsEncoder.encodeOctetString(randomB);
if (entityB != null) {
tbsEncoder.encodeImplicit(0);
EntityUtil.encodeGeneralNames(tbsEncoder, entityB);
}
if (authId != null) {
tbsEncoder.encodeImplicit(1);
EntityUtil.encodeGeneralNames(tbsEncoder, authId);
}
tbsEncoder.endSequence();
// Signature
byte[] signatureBytes;
try {
signature.initSign(privateKey);
signature.update(tbsEncoder.getEncoded());
signatureBytes = signature.sign();
} catch (SignatureException | InvalidKeyException e) {
throw saslEntity.mechUnableToCreateSignature(e).toSaslException();
}
encoder.startSequence();
EntityUtil.encodeAlgorithmIdentifier(encoder, signature.getAlgorithm());
encoder.encodeBitString(signatureBytes);
encoder.endSequence();
encoder.endSequence();
} catch (ASN1Exception e) {
throw saslEntity.mechUnableToCreateResponseToken(e).toSaslException();
}
setNegotiationState(ST_RESPONSE_SENT);
return encoder.getEncoded();
}
case ST_RESPONSE_SENT: {
if (mutual) {
final DERDecoder decoder = new DERDecoder(challenge);
List entityA = null;
try {
decoder.startSequence();
byte[] randomC = decoder.decodeOctetString();
if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, 0, true)) {
decoder.decodeImplicit(0);
entityA = EntityUtil.decodeGeneralNames(decoder);
// Verify that entityA matches the client's distinguishing identifier
if (! EntityUtil.matchGeneralNames(entityA, getClientCertificate())) {
throw saslEntity.mechClientIdentifierMismatch().toSaslException();
}
}
// Get the server's certificate data and verify it
decoder.startExplicit(1);
final X509PeerCertificateChainEvidence evidence = new X509PeerCertificateChainEvidence(EntityUtil.decodeCertificateData(decoder));
decoder.endExplicit();
X509Certificate serverCert = evidence.getFirstCertificate();
EvidenceVerifyCallback evidenceVerifyCallback = new EvidenceVerifyCallback(evidence);
handleCallbacks(evidenceVerifyCallback);
if (! evidenceVerifyCallback.isVerified()) {
throw saslEntity.mechServerAuthenticityCannotBeVerified().toSaslException();
}
// Get the server's signature and verify it
decoder.startSequence();
decoder.skipElement();
byte[] serverSignature = decoder.decodeBitString();
decoder.endSequence();
final DEREncoder tbsEncoder = new DEREncoder();
tbsEncoder.startSequence();
tbsEncoder.encodeOctetString(randomB);
tbsEncoder.encodeOctetString(randomA);
tbsEncoder.encodeOctetString(randomC);
if (entityA != null) {
EntityUtil.encodeGeneralNames(tbsEncoder, entityA);
}
tbsEncoder.endSequence();
try {
signature.initVerify(serverCert);
signature.update(tbsEncoder.getEncoded());
if (! signature.verify(serverSignature)) {
setNegotiationState(FAILED_STATE);
throw saslEntity.mechServerAuthenticityCannotBeVerified().toSaslException();
}
} catch (SignatureException | InvalidKeyException e) {
throw saslEntity.mechUnableToVerifyServerSignature(e).toSaslException();
}
decoder.endSequence();
} catch (ASN1Exception e) {
throw saslEntity.mechInvalidServerMessageWithCause(e).toSaslException();
}
} else {
if (challenge != null && challenge.length != 0) {
throw saslEntity.mechServerSentExtraMessage().toSaslException();
}
}
negotiationComplete();
return null;
}
}
throw Assert.impossibleSwitchCase(state);
}
@Override
public void dispose() throws SaslException {
clientCertChain = null;
}
private X509Certificate getClientCertificate() throws SaslException {
if (clientCertChain == null || clientCertChain.length == 0) throw saslEntity.mechCallbackHandlerNotProvidedServerCertificate().toSaslException();
return clientCertChain[0];
}
}