mirror of https://github.com/sipwise/jitsi.git
Replace the CertificateVerificationService with CertificateService to correctly validate the hostname of serverscusax-fix
parent
27197bb56b
commit
60385be26e
Binary file not shown.
@ -0,0 +1,692 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.certificate;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.*;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import javax.swing.*;
|
||||
|
||||
import org.bouncycastle.asn1.*;
|
||||
import org.bouncycastle.asn1.x509.*;
|
||||
import org.bouncycastle.asn1.x509.X509Extension;
|
||||
|
||||
import net.java.sip.communicator.service.certificate.*;
|
||||
import net.java.sip.communicator.service.configuration.*;
|
||||
import net.java.sip.communicator.service.httputil.*;
|
||||
import net.java.sip.communicator.service.resources.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* Implementation of the CertificateService. It asks the user to trust a
|
||||
* certificate when the automatic verification fails.
|
||||
*
|
||||
* @author Ingo Bauersachs
|
||||
*/
|
||||
public class CertificateServiceImpl
|
||||
implements CertificateService
|
||||
{
|
||||
// services
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(CertificateServiceImpl.class);
|
||||
|
||||
private final ResourceManagementService R =
|
||||
CertificateVerificationActivator.getResources();
|
||||
|
||||
private final ConfigurationService config =
|
||||
CertificateVerificationActivator.getConfigurationService();
|
||||
|
||||
// properties
|
||||
/**
|
||||
* Base property name for the storage of certificate user preferences.
|
||||
*/
|
||||
private final static String PNAME_CERT_TRUST_PREFIX =
|
||||
"net.java.sip.communicator.impl.certservice";
|
||||
|
||||
/** Hash algorithm for the cert thumbprint*/
|
||||
private final static String THUMBPRINT_HASH_ALGORITHM = "SHA1";
|
||||
|
||||
// variables
|
||||
/**
|
||||
* Stores the certificates that are trusted as long as this service lives.
|
||||
*/
|
||||
private Map<String, String> sessionAllowedCertificates =
|
||||
new HashMap<String, String>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see net.java.sip.communicator.service.certificate.CertificateService#
|
||||
* addCertificateToTrust(java.security.cert.Certificate, java.lang.String,
|
||||
* int)
|
||||
*/
|
||||
public void addCertificateToTrust(Certificate cert, String trustFor,
|
||||
int trustMode)
|
||||
throws CertificateException
|
||||
{
|
||||
switch (trustMode)
|
||||
{
|
||||
case DO_NOT_TRUST:
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot add a certificate to trust when "
|
||||
+ "no trust is requested.");
|
||||
case TRUST_ALWAYS:
|
||||
config.setProperty(PNAME_CERT_TRUST_PREFIX + ".param." + trustFor,
|
||||
getThumbprint(cert, THUMBPRINT_HASH_ALGORITHM));
|
||||
break;
|
||||
case TRUST_THIS_SESSION_ONLY:
|
||||
sessionAllowedCertificates.put(PNAME_CERT_TRUST_PREFIX + ".param."
|
||||
+ trustFor, getThumbprint(cert, THUMBPRINT_HASH_ALGORITHM));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see net.java.sip.communicator.service.certificate.CertificateService#
|
||||
* getSSLContext()
|
||||
*/
|
||||
public SSLContext getSSLContext() throws GeneralSecurityException
|
||||
{
|
||||
return getSSLContext(getTrustManager((Iterable<String>)null));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see net.java.sip.communicator.service.certificate.CertificateService#
|
||||
* getSSLContext(javax.net.ssl.X509TrustManager)
|
||||
*/
|
||||
public SSLContext getSSLContext(X509TrustManager trustManager)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
try
|
||||
{
|
||||
KeyStore ks =
|
||||
KeyStore.getInstance(System.getProperty(
|
||||
"javax.net.ssl.keyStoreType", KeyStore.getDefaultType()));
|
||||
KeyManagerFactory kmFactory =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory
|
||||
.getDefaultAlgorithm());
|
||||
|
||||
String keyStorePassword =
|
||||
System.getProperty("javax.net.ssl.keyStorePassword");
|
||||
if (System.getProperty("javax.net.ssl.keyStore") != null)
|
||||
{
|
||||
ks.load(
|
||||
new FileInputStream(System
|
||||
.getProperty("javax.net.ssl.keyStore")), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
ks.load(null, null);
|
||||
}
|
||||
|
||||
kmFactory.init(ks, keyStorePassword == null ? null
|
||||
: keyStorePassword.toCharArray());
|
||||
|
||||
//TODO: inject our own socket factory to use our own DNS stuff
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(kmFactory.getKeyManagers(), new TrustManager[]
|
||||
{ trustManager }, null);
|
||||
|
||||
return sslContext;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new GeneralSecurityException("Cannot init SSLContext: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* net.java.sip.communicator.service.certificate
|
||||
* .CertificateService#getTrustManager(java.lang.Iterable)
|
||||
*/
|
||||
public X509TrustManager getTrustManager(Iterable<String> identitiesToTest)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
return getTrustManager(
|
||||
identitiesToTest,
|
||||
new EMailAddressMatcher(),
|
||||
new BrowserLikeHostnameMatcher()
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* net.java.sip.communicator.service.certificate.CertificateService
|
||||
* #getTrustManager(java.lang.String)
|
||||
*/
|
||||
public X509TrustManager getTrustManager(String identityToTest)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
return getTrustManager(
|
||||
Arrays.asList(new String[]{identityToTest}),
|
||||
new EMailAddressMatcher(),
|
||||
new BrowserLikeHostnameMatcher()
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* net.java.sip.communicator.service.certificate.CertificateService
|
||||
* #getTrustManager(java.lang.String,
|
||||
* net.java.sip.communicator.service.certificate.CertificateMatcher,
|
||||
* net.java.sip.communicator.service.certificate.CertificateMatcher)
|
||||
*/
|
||||
public X509TrustManager getTrustManager(
|
||||
String identityToTest,
|
||||
CertificateMatcher clientVerifier,
|
||||
CertificateMatcher serverVerifier)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
return getTrustManager(
|
||||
Arrays.asList(new String[]{identityToTest}),
|
||||
clientVerifier,
|
||||
serverVerifier
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* net.java.sip.communicator.service.certificate.CertificateService
|
||||
* #getTrustManager(java.lang.Iterable,
|
||||
* net.java.sip.communicator.service.certificate.CertificateMatcher,
|
||||
* net.java.sip.communicator.service.certificate.CertificateMatcher)
|
||||
*/
|
||||
public X509TrustManager getTrustManager(
|
||||
final Iterable<String> identitiesToTest,
|
||||
final CertificateMatcher clientVerifier,
|
||||
final CertificateMatcher serverVerifier)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
// obtain the default X509 trust manager
|
||||
X509TrustManager defaultTm = null;
|
||||
TrustManagerFactory tmFactory =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory
|
||||
.getDefaultAlgorithm());
|
||||
tmFactory.init((KeyStore) null);
|
||||
for (TrustManager m : tmFactory.getTrustManagers())
|
||||
{
|
||||
if (m instanceof X509TrustManager)
|
||||
{
|
||||
defaultTm = (X509TrustManager) m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (defaultTm == null)
|
||||
throw new GeneralSecurityException(
|
||||
"No default X509 trust manager found");
|
||||
|
||||
final X509TrustManager tm = defaultTm;
|
||||
|
||||
return new X509TrustManager()
|
||||
{
|
||||
private boolean serverCheck;
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers()
|
||||
{
|
||||
return tm.getAcceptedIssuers();
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException
|
||||
{
|
||||
serverCheck = true;
|
||||
checkCertTrusted(chain, authType);
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException
|
||||
{
|
||||
serverCheck = false;
|
||||
checkCertTrusted(chain, authType);
|
||||
}
|
||||
|
||||
private void checkCertTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException
|
||||
{
|
||||
if(config.getBoolean(PNAME_ALWAYS_TRUST, false))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// check the certificate itself (issuer, validity)
|
||||
try
|
||||
{
|
||||
chain = tryBuildChain(chain);
|
||||
}
|
||||
catch (Exception e)
|
||||
{} // don't care and take the chain as is
|
||||
|
||||
if(serverCheck)
|
||||
tm.checkServerTrusted(chain, authType);
|
||||
else
|
||||
tm.checkClientTrusted(chain, authType);
|
||||
|
||||
if(identitiesToTest == null
|
||||
|| !identitiesToTest.iterator().hasNext())
|
||||
return;
|
||||
else if(serverCheck)
|
||||
serverVerifier.verify(identitiesToTest, chain[0]);
|
||||
else
|
||||
clientVerifier.verify(identitiesToTest, chain[0]);
|
||||
|
||||
// ok, globally valid cert
|
||||
}
|
||||
catch (CertificateException e)
|
||||
{
|
||||
String thumbprint = getThumbprint(
|
||||
chain[0], THUMBPRINT_HASH_ALGORITHM);
|
||||
String propName = null;
|
||||
String message = null;
|
||||
String storedCert = null;
|
||||
String appName =
|
||||
R.getSettingsString("service.gui.APPLICATION_NAME");
|
||||
|
||||
if (identitiesToTest == null
|
||||
|| !identitiesToTest.iterator().hasNext())
|
||||
{
|
||||
propName =
|
||||
PNAME_CERT_TRUST_PREFIX + ".server." + thumbprint;
|
||||
message =
|
||||
R.getI18NString("service.gui."
|
||||
+ "CERT_DIALOG_DESCRIPTION_TXT_NOHOST",
|
||||
new String[] {
|
||||
appName
|
||||
}
|
||||
);
|
||||
|
||||
// get the thumbprint from the permanent allowances
|
||||
storedCert = config.getString(propName);
|
||||
// not found? check the session allowances
|
||||
if (storedCert == null)
|
||||
storedCert =
|
||||
sessionAllowedCertificates.get(propName);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (String identity : identitiesToTest)
|
||||
{
|
||||
if (serverCheck)
|
||||
{
|
||||
message =
|
||||
R.getI18NString(
|
||||
"service.gui."
|
||||
+ "CERT_DIALOG_DESCRIPTION_TXT",
|
||||
new String[] {
|
||||
appName,
|
||||
identitiesToTest.toString()
|
||||
}
|
||||
);
|
||||
propName =
|
||||
PNAME_CERT_TRUST_PREFIX + ".param."
|
||||
+ identity;
|
||||
}
|
||||
else
|
||||
{
|
||||
message =
|
||||
R.getI18NString(
|
||||
"service.gui."
|
||||
+ "CERT_DIALOG_PEER_DESCRIPTION_TXT",
|
||||
new String[] {
|
||||
appName,
|
||||
identitiesToTest.toString()
|
||||
}
|
||||
);
|
||||
propName =
|
||||
PNAME_CERT_TRUST_PREFIX + ".param."
|
||||
+ identity;
|
||||
}
|
||||
|
||||
// get the thumbprint from the permanent allowances
|
||||
storedCert = config.getString(propName);
|
||||
// not found? check the session allowances
|
||||
if (storedCert == null)
|
||||
storedCert =
|
||||
sessionAllowedCertificates.get(propName);
|
||||
|
||||
// stop search for further saved allowances if we
|
||||
// found a match
|
||||
if (storedCert != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!thumbprint.equals(storedCert))
|
||||
{
|
||||
switch (verify(chain, message))
|
||||
{
|
||||
case DO_NOT_TRUST:
|
||||
throw new CertificateException(
|
||||
"The peer provided certificate with Subject <"
|
||||
+ chain[0].getSubjectDN()
|
||||
+ "> is not trusted");
|
||||
case TRUST_ALWAYS:
|
||||
config.setProperty(propName, thumbprint);
|
||||
break;
|
||||
case TRUST_THIS_SESSION_ONLY:
|
||||
sessionAllowedCertificates
|
||||
.put(propName, thumbprint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ok, we've seen this certificate before
|
||||
}
|
||||
}
|
||||
|
||||
private X509Certificate[] tryBuildChain(X509Certificate[] chain)
|
||||
throws IOException,
|
||||
URISyntaxException,
|
||||
CertificateException
|
||||
{
|
||||
// Only try to build chains for servers that send only their
|
||||
// own cert, but no issuer. This also matches self signed (will
|
||||
// be ignored later) and Root-CA signed certs. In this case we
|
||||
// throw the Root-CA away after the lookup
|
||||
if (chain.length != 1)
|
||||
return chain;
|
||||
|
||||
// ignore self signed certs
|
||||
if (chain[0].getIssuerDN().equals(chain[0].getSubjectDN()))
|
||||
return chain;
|
||||
|
||||
// prepare for the newly created chain
|
||||
List<X509Certificate> newChain =
|
||||
new ArrayList<X509Certificate>(chain.length + 4);
|
||||
for (X509Certificate cert : chain)
|
||||
{
|
||||
newChain.add(cert);
|
||||
}
|
||||
|
||||
// search from the topmost certificate upwards
|
||||
CertificateFactory certFactory =
|
||||
CertificateFactory.getInstance("X.509");
|
||||
X509Certificate current = chain[chain.length - 1];
|
||||
boolean foundParent;
|
||||
int chainLookupCount = 0;
|
||||
do
|
||||
{
|
||||
foundParent = false;
|
||||
// extract the url(s) where the parent certificate can be
|
||||
// found
|
||||
byte[] aiaBytes =
|
||||
current.getExtensionValue(
|
||||
X509Extension.authorityInfoAccess.getId());
|
||||
if (aiaBytes == null)
|
||||
break;
|
||||
|
||||
DEROctetString octs =
|
||||
(DEROctetString) ASN1Object.fromByteArray(aiaBytes);
|
||||
ASN1InputStream as = new ASN1InputStream(octs.getOctets());
|
||||
AuthorityInformationAccess aia =
|
||||
AuthorityInformationAccess
|
||||
.getInstance(as.readObject());
|
||||
// the AIA may contain different URLs and types, try all
|
||||
// of them
|
||||
for (AccessDescription ad : aia.getAccessDescriptions())
|
||||
{
|
||||
// we are only interested in the issuer certificate,
|
||||
// not in OCSP urls the like
|
||||
if (!ad.getAccessMethod().equals(
|
||||
AccessDescription.id_ad_caIssuers))
|
||||
continue;
|
||||
|
||||
GeneralName gn = ad.getAccessLocation();
|
||||
if (!(gn.getTagNo() ==
|
||||
GeneralName.uniformResourceIdentifier
|
||||
&& gn.getName() instanceof DERIA5String))
|
||||
continue;
|
||||
|
||||
URI uri =
|
||||
new URI(((DERIA5String) gn.getName()).getString());
|
||||
// only http(s) urls; LDAP is taken care of in the
|
||||
// default implementation
|
||||
if (!(uri.getScheme().equalsIgnoreCase("http") || uri
|
||||
.getScheme().equals("https")))
|
||||
continue;
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger
|
||||
.debug("Downloading parent certificate for <"
|
||||
+ current.getSubjectDN()
|
||||
+ "> from <"
|
||||
+ uri
|
||||
+ ">");
|
||||
|
||||
try
|
||||
{
|
||||
InputStream is =
|
||||
HttpUtils.openURLConnection(uri.toString())
|
||||
.getContent();
|
||||
X509Certificate cert =
|
||||
(X509Certificate) certFactory
|
||||
.generateCertificate(is);
|
||||
if(!cert.getIssuerDN().equals(cert.getSubjectDN()))
|
||||
{
|
||||
newChain.add(cert);
|
||||
foundParent = true;
|
||||
current = cert;
|
||||
break; // an AD was valid, ignore others
|
||||
}
|
||||
else
|
||||
logger.debug("Parent is self-signed, ignoring");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.debug("Could not download from <" + uri
|
||||
+ ">");
|
||||
}
|
||||
}
|
||||
chainLookupCount++;
|
||||
}
|
||||
while (foundParent && chainLookupCount < 10);
|
||||
chain = newChain.toArray(chain);
|
||||
return chain;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected class BrowserLikeHostnameMatcher
|
||||
implements CertificateMatcher
|
||||
{
|
||||
public void verify(Iterable<String> identitiesToTest,
|
||||
X509Certificate cert) throws CertificateException
|
||||
{
|
||||
// check whether one of the hostname is present in the
|
||||
// certificate
|
||||
boolean oneMatched = false;
|
||||
for(String identity : identitiesToTest)
|
||||
{
|
||||
try
|
||||
{
|
||||
org.apache.http.conn.ssl.SSLSocketFactory
|
||||
.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
|
||||
.verify(identity, cert);
|
||||
oneMatched = true;
|
||||
break;
|
||||
}
|
||||
catch (SSLException e)
|
||||
{}
|
||||
}
|
||||
|
||||
if (!oneMatched)
|
||||
throw new CertificateException("None of <"
|
||||
+ identitiesToTest
|
||||
+ "> matched the cert with CN="
|
||||
+ cert.getSubjectDN());
|
||||
}
|
||||
}
|
||||
|
||||
protected class EMailAddressMatcher
|
||||
implements CertificateMatcher
|
||||
{
|
||||
public void verify(Iterable<String> identitiesToTest,
|
||||
X509Certificate cert) throws CertificateException
|
||||
{
|
||||
// check if the certificate contains the E-Mail address(es)
|
||||
// in the SAN(s)
|
||||
//TODO: extract address from DN (E-field) too?
|
||||
boolean oneMatched = false;
|
||||
Iterable<String> emails = getSubjectAltNames(cert, 6);
|
||||
for(String identity : identitiesToTest)
|
||||
{
|
||||
for(String email : emails)
|
||||
{
|
||||
if(identity.equalsIgnoreCase(email))
|
||||
{
|
||||
oneMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!oneMatched)
|
||||
throw new CertificateException(
|
||||
"The peer provided certificate with Subject <"
|
||||
+ cert.getSubjectDN()
|
||||
+ "> contains no SAN for <"
|
||||
+ identitiesToTest + ">");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the user whether he trusts the supplied chain of certificates.
|
||||
*
|
||||
* @param chain The chain of the certificates to check with user.
|
||||
* @param message A text that describes why the verification failed.
|
||||
* @return The result of the user interaction. One of
|
||||
* {@link CertificateService#DO_NOT_TRUST},
|
||||
* {@link CertificateService#TRUST_THIS_SESSION_ONLY},
|
||||
* {@link CertificateService#TRUST_ALWAYS}
|
||||
*/
|
||||
protected int verify(final X509Certificate[] chain, final String message)
|
||||
{
|
||||
if(config.getBoolean(PNAME_NO_USER_INTERACTION, false))
|
||||
return DO_NOT_TRUST;
|
||||
|
||||
final VerifyCertificateDialog dialog =
|
||||
new VerifyCertificateDialog(chain, null, message);
|
||||
try
|
||||
{
|
||||
// show the dialog in the swing thread and wait for the user
|
||||
// choice
|
||||
SwingUtilities.invokeAndWait(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.error("Cannot show certificate verification dialog", e);
|
||||
return DO_NOT_TRUST;
|
||||
}
|
||||
|
||||
if(!dialog.isTrusted)
|
||||
return DO_NOT_TRUST;
|
||||
else if(dialog.alwaysTrustCheckBox.isSelected())
|
||||
return TRUST_ALWAYS;
|
||||
else
|
||||
return TRUST_THIS_SESSION_ONLY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash of the certificate known as the "thumbprint"
|
||||
* and returns it as a string representation.
|
||||
*
|
||||
* @param cert The certificate to hash.
|
||||
* @param algorithm The hash algorithm to use.
|
||||
* @return The SHA-1 hash of the certificate.
|
||||
* @throws CertificateException
|
||||
*/
|
||||
static String getThumbprint(Certificate cert, String algorithm)
|
||||
throws CertificateException
|
||||
{
|
||||
MessageDigest digest;
|
||||
try
|
||||
{
|
||||
digest = MessageDigest.getInstance(algorithm);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
throw new CertificateException(e);
|
||||
}
|
||||
byte[] encodedCert = cert.getEncoded();
|
||||
StringBuilder sb = new StringBuilder(encodedCert.length * 2);
|
||||
Formatter f = new Formatter(sb);
|
||||
for (byte b : digest.digest(encodedCert))
|
||||
{
|
||||
f.format("%02x", b);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SAN (Subject Alternative Name) of the specified type.
|
||||
*
|
||||
* @param cert the certificate to extract from
|
||||
* @param altNameType The type to be returned
|
||||
* @return SAN of the type
|
||||
*
|
||||
* <PRE>
|
||||
* GeneralName ::= CHOICE {
|
||||
* otherName [0] OtherName,
|
||||
* rfc822Name [1] IA5String,
|
||||
* dNSName [2] IA5String,
|
||||
* x400Address [3] ORAddress,
|
||||
* directoryName [4] Name,
|
||||
* ediPartyName [5] EDIPartyName,
|
||||
* uniformResourceIdentifier [6] IA5String,
|
||||
* iPAddress [7] OCTET STRING,
|
||||
* registeredID [8] OBJECT IDENTIFIER
|
||||
* }
|
||||
* <PRE>
|
||||
*/
|
||||
private static Iterable<String> getSubjectAltNames(X509Certificate cert,
|
||||
int altNameType)
|
||||
{
|
||||
Collection<List<?>> altNames = null;
|
||||
try
|
||||
{
|
||||
altNames = cert.getSubjectAlternativeNames();
|
||||
}
|
||||
catch (CertificateParsingException e)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> matchedAltNames = new LinkedList<String>();
|
||||
for (List<?> item : altNames)
|
||||
{
|
||||
if (item.contains(altNameType))
|
||||
{
|
||||
Integer type = (Integer) item.get(0);
|
||||
if (type.intValue() == altNameType)
|
||||
matchedAltNames.add((String) item.get(1));
|
||||
}
|
||||
}
|
||||
return matchedAltNames;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,659 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.certificate;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.security.cert.*;
|
||||
import java.security.interfaces.*;
|
||||
import java.text.*;
|
||||
import java.util.Formatter;
|
||||
|
||||
import javax.naming.*;
|
||||
import javax.naming.ldap.*;
|
||||
import javax.security.auth.x500.*;
|
||||
import javax.swing.*;
|
||||
|
||||
import net.java.sip.communicator.service.resources.*;
|
||||
import net.java.sip.communicator.util.swing.*;
|
||||
|
||||
/**
|
||||
* Dialog that is shown to the user when a certificate verification failed.
|
||||
*/
|
||||
class VerifyCertificateDialog
|
||||
extends SIPCommDialog
|
||||
{
|
||||
/**
|
||||
* Serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private ResourceManagementService R = CertificateVerificationActivator
|
||||
.getResources();
|
||||
|
||||
/**
|
||||
* Date formatter.
|
||||
*/
|
||||
private DateFormat dateFormatter = DateFormat
|
||||
.getDateInstance(DateFormat.MEDIUM);
|
||||
|
||||
/**
|
||||
* The maximum width that we allow message dialogs to have.
|
||||
*/
|
||||
private static final int MAX_MSG_PANE_WIDTH = 600;
|
||||
|
||||
/**
|
||||
* The maximum height that we allow message dialogs to have.
|
||||
*/
|
||||
private static final int MAX_MSG_PANE_HEIGHT = 800;
|
||||
|
||||
/**
|
||||
* The certificate to show.
|
||||
*/
|
||||
Certificate cert;
|
||||
|
||||
/**
|
||||
* A text that describes why the verification failed.
|
||||
*/
|
||||
String message;
|
||||
|
||||
/**
|
||||
* The certificate panel.
|
||||
*/
|
||||
TransparentPanel certPanel;
|
||||
|
||||
/**
|
||||
* This dialog content pane.
|
||||
*/
|
||||
TransparentPanel contentPane;
|
||||
|
||||
/**
|
||||
* Whether certificate description is shown.
|
||||
*/
|
||||
boolean certOpened = false;
|
||||
|
||||
/**
|
||||
* The button to show certificate description.
|
||||
*/
|
||||
JButton certButton;
|
||||
|
||||
/**
|
||||
* The check box if checked permanently stored the certificate
|
||||
* which will be always trusted.
|
||||
*/
|
||||
SIPCommCheckBox alwaysTrustCheckBox = new SIPCommCheckBox(
|
||||
R.getI18NString("service.gui.ALWAYS_TRUST"),
|
||||
false);
|
||||
|
||||
/**
|
||||
* Whether the user trusts this certificate.
|
||||
*/
|
||||
boolean isTrusted = false;
|
||||
|
||||
/**
|
||||
* Creates the dialog.
|
||||
*
|
||||
* @param certs the certificates list
|
||||
* @param title The title of the dialog; when null the resource
|
||||
* <tt>service.gui.CERT_DIALOG_TITLE</tt> is loaded.
|
||||
* @param message A text that describes why the verification failed.
|
||||
*/
|
||||
public VerifyCertificateDialog( Certificate[] certs,
|
||||
String title, String message)
|
||||
{
|
||||
super(false);
|
||||
|
||||
setTitle(title != null ? title :
|
||||
R.getI18NString("service.gui.CERT_DIALOG_TITLE"));
|
||||
setModal(true);
|
||||
|
||||
// for now shows only the first certificate from the chain
|
||||
this.cert = certs[0];
|
||||
this.message = message;
|
||||
|
||||
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||
|
||||
init();
|
||||
|
||||
setLocationRelativeTo(getParent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the dialog initial display.
|
||||
*/
|
||||
private void init()
|
||||
{
|
||||
this.getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
contentPane =
|
||||
new TransparentPanel(new BorderLayout(5, 5));
|
||||
|
||||
TransparentPanel northPanel =
|
||||
new TransparentPanel(new BorderLayout(5, 5));
|
||||
northPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5));
|
||||
|
||||
JLabel imgLabel = new JLabel(
|
||||
R.getImage("service.gui.icons.WARNING_ICON"));
|
||||
imgLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
northPanel.add(imgLabel, BorderLayout.WEST);
|
||||
|
||||
StyledHTMLEditorPane descriptionPane = new StyledHTMLEditorPane();
|
||||
descriptionPane.setOpaque(false);
|
||||
descriptionPane.setEditable(false);
|
||||
descriptionPane.setContentType("text/html");
|
||||
descriptionPane.setText(message);
|
||||
descriptionPane.setSize(
|
||||
new Dimension(MAX_MSG_PANE_WIDTH, MAX_MSG_PANE_HEIGHT));
|
||||
int height = descriptionPane.getPreferredSize().height;
|
||||
descriptionPane.setPreferredSize(
|
||||
new Dimension(MAX_MSG_PANE_WIDTH, height));
|
||||
|
||||
northPanel.add(descriptionPane, BorderLayout.CENTER);
|
||||
contentPane.add(northPanel, BorderLayout.NORTH);
|
||||
|
||||
certPanel = new TransparentPanel();
|
||||
contentPane.add(certPanel, BorderLayout.CENTER);
|
||||
|
||||
TransparentPanel southPanel =
|
||||
new TransparentPanel(new BorderLayout());
|
||||
contentPane.add(southPanel, BorderLayout.SOUTH);
|
||||
|
||||
certButton = new JButton();
|
||||
certButton.setText(R.getI18NString("service.gui.SHOW_CERT"));
|
||||
certButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
actionShowCertificate();
|
||||
}
|
||||
});
|
||||
TransparentPanel firstButonPanel =
|
||||
new TransparentPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
firstButonPanel.add(certButton);
|
||||
southPanel.add(firstButonPanel, BorderLayout.WEST);
|
||||
|
||||
TransparentPanel secondButonPanel =
|
||||
new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
JButton cancelButton = new JButton(
|
||||
R.getI18NString("service.gui.CANCEL"));
|
||||
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
actionCancel();
|
||||
}
|
||||
});
|
||||
JButton continueButton = new JButton(
|
||||
R.getI18NString("service.gui.CONTINUE_ANYWAY"));
|
||||
|
||||
continueButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
actionContinue();
|
||||
}
|
||||
});
|
||||
secondButonPanel.add(continueButton);
|
||||
secondButonPanel.add(cancelButton);
|
||||
southPanel.add(secondButonPanel, BorderLayout.EAST);
|
||||
|
||||
this.getContentPane().add(contentPane, BorderLayout.CENTER);
|
||||
|
||||
pack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when shoe certificate button is clicked.
|
||||
*/
|
||||
private void actionShowCertificate()
|
||||
{
|
||||
if(certOpened)
|
||||
{
|
||||
certPanel.removeAll();
|
||||
certButton.setText(R.getI18NString("service.gui.SHOW_CERT"));
|
||||
|
||||
certPanel.revalidate();
|
||||
certPanel.repaint();
|
||||
pack();
|
||||
certOpened = false;
|
||||
setLocationRelativeTo(getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
certPanel.setLayout(new BorderLayout());
|
||||
certPanel.add(alwaysTrustCheckBox, BorderLayout.NORTH);
|
||||
|
||||
Component certInfoPane = null;
|
||||
if(cert instanceof X509Certificate)
|
||||
{
|
||||
certInfoPane = getX509DisplayComponent((X509Certificate)cert);
|
||||
}
|
||||
else
|
||||
{
|
||||
JTextArea textArea = new JTextArea();
|
||||
textArea.setOpaque(false);
|
||||
textArea.setEditable(false);
|
||||
textArea.setText(cert.toString());
|
||||
certInfoPane = textArea;
|
||||
}
|
||||
|
||||
final JScrollPane certScroll = new JScrollPane(certInfoPane);
|
||||
certScroll.setPreferredSize(new Dimension(300, 300));
|
||||
certPanel.add(certScroll, BorderLayout.CENTER);
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
certScroll.getVerticalScrollBar().setValue(0);
|
||||
}
|
||||
});
|
||||
|
||||
certButton.setText(R.getI18NString("service.gui.HIDE_CERT"));
|
||||
|
||||
certPanel.revalidate();
|
||||
certPanel.repaint();
|
||||
// restore default values for prefered size,
|
||||
// as we have resized its components let it calculate
|
||||
// that size
|
||||
setPreferredSize(null);
|
||||
pack();
|
||||
certOpened = true;
|
||||
setLocationRelativeTo(getParent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when cancel button is clicked.
|
||||
*/
|
||||
private void actionCancel()
|
||||
{
|
||||
isTrusted = false;
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when continue is clicked.
|
||||
*/
|
||||
private void actionContinue()
|
||||
{
|
||||
isTrusted = true;
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when dialog closed or escape pressed.
|
||||
* @param isEscaped is escape button pressed.
|
||||
*/
|
||||
protected void close(boolean isEscaped)
|
||||
{
|
||||
actionCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display component for X509 certificate.
|
||||
*
|
||||
* @param certificate the certificate to show
|
||||
* @return the created component
|
||||
*/
|
||||
private Component getX509DisplayComponent(
|
||||
X509Certificate certificate)
|
||||
{
|
||||
Insets valueInsets = new Insets(2,10,0,0);
|
||||
Insets titleInsets = new Insets(10,5,0,0);
|
||||
|
||||
TransparentPanel certDisplayPanel
|
||||
= new TransparentPanel(new GridBagLayout());
|
||||
|
||||
int currentRow = 0;
|
||||
|
||||
GridBagConstraints constraints = new GridBagConstraints();
|
||||
constraints.anchor = GridBagConstraints.WEST;
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
constraints.insets = new Insets(2,5,0,0);
|
||||
constraints.gridx = 0;
|
||||
constraints.weightx = 0;
|
||||
constraints.weighty = 0;
|
||||
constraints.gridy = currentRow++;
|
||||
|
||||
X500Principal issuer = certificate.getIssuerX500Principal();
|
||||
X500Principal subject = certificate.getSubjectX500Principal();
|
||||
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_ISSUED_TO")),
|
||||
constraints);
|
||||
|
||||
// subject
|
||||
constraints.insets = valueInsets;
|
||||
try
|
||||
{
|
||||
for(Rdn name : new LdapName(subject.getName()).getRdns())
|
||||
{
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
String lbl =
|
||||
R.getI18NString("service.gui.CERT_INFO_" + name.getType());
|
||||
if (lbl
|
||||
.equals("!service.gui.CERT_INFO_" + name.getType() + "!"))
|
||||
lbl = name.getType();
|
||||
certDisplayPanel.add(new JLabel(lbl), constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(
|
||||
name.getValue() instanceof byte[] ?
|
||||
getHex((byte[])name.getValue()) + " ("
|
||||
+ new String((byte[]) name.getValue()) + ")"
|
||||
: name.getValue().toString()),
|
||||
constraints);
|
||||
}
|
||||
}
|
||||
catch (InvalidNameException e1)
|
||||
{
|
||||
constraints.gridy = currentRow++;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_CN")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(subject.getName()),
|
||||
constraints);
|
||||
}
|
||||
|
||||
// issuer
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
constraints.insets = titleInsets;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_ISSUED_BY")),
|
||||
constraints);
|
||||
constraints.insets = valueInsets;
|
||||
try
|
||||
{
|
||||
for(Rdn name : new LdapName(issuer.getName()).getRdns())
|
||||
{
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
constraints.gridx = 0;
|
||||
String lbl =
|
||||
R.getI18NString("service.gui.CERT_INFO_" + name.getType());
|
||||
if (lbl
|
||||
.equals("!service.gui.CERT_INFO_" + name.getType() + "!"))
|
||||
lbl = name.getType();
|
||||
certDisplayPanel.add(new JLabel(lbl), constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(
|
||||
name.getValue() instanceof byte[] ?
|
||||
getHex((byte[])name.getValue()) + " ("
|
||||
+ new String((byte[]) name.getValue()) + ")"
|
||||
: name.getValue().toString()),
|
||||
constraints);
|
||||
}
|
||||
}
|
||||
catch (InvalidNameException e1)
|
||||
{
|
||||
constraints.gridy = currentRow++;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_CN")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(issuer.getName()),
|
||||
constraints);
|
||||
}
|
||||
|
||||
// validity
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
constraints.insets = titleInsets;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_VALIDITY")),
|
||||
constraints);
|
||||
constraints.insets = valueInsets;
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_ISSUED_ON")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(dateFormatter.format(certificate.getNotBefore())),
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_EXPIRES_ON")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(dateFormatter.format(certificate.getNotAfter())),
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
constraints.insets = titleInsets;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_FINGERPRINTS")),
|
||||
constraints);
|
||||
constraints.insets = valueInsets;
|
||||
|
||||
try
|
||||
{
|
||||
String sha1String = CertificateServiceImpl.getThumbprint(certificate, "SHA1");
|
||||
String md5String = CertificateServiceImpl.getThumbprint(certificate, "MD5");
|
||||
|
||||
JTextArea sha1Area = new JTextArea(sha1String);
|
||||
sha1Area.setLineWrap(false);
|
||||
sha1Area.setOpaque(false);
|
||||
sha1Area.setWrapStyleWord(true);
|
||||
sha1Area.setEditable(false);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel("SHA1:"),
|
||||
constraints);
|
||||
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
sha1Area,
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel("MD5:"),
|
||||
constraints);
|
||||
|
||||
JTextArea md5Area = new JTextArea(md5String);
|
||||
md5Area.setLineWrap(false);
|
||||
md5Area.setOpaque(false);
|
||||
md5Area.setWrapStyleWord(true);
|
||||
md5Area.setEditable(false);
|
||||
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
md5Area,
|
||||
constraints);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// do nothing as we cannot show this value
|
||||
}
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
constraints.insets = titleInsets;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_CERT_DETAILS")),
|
||||
constraints);
|
||||
constraints.insets = valueInsets;
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_SER_NUM")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(certificate.getSerialNumber().toString()),
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_VER")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(String.valueOf(certificate.getVersion())),
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_SIGN_ALG")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(String.valueOf(certificate.getSigAlgName())),
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
constraints.insets = titleInsets;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_PUB_KEY_INFO")),
|
||||
constraints);
|
||||
constraints.insets = valueInsets;
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_ALG")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(certificate.getPublicKey().getAlgorithm()),
|
||||
constraints);
|
||||
|
||||
if(certificate.getPublicKey().getAlgorithm().equals("RSA"))
|
||||
{
|
||||
RSAPublicKey key = (RSAPublicKey)certificate.getPublicKey();
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_PUB_KEY")),
|
||||
constraints);
|
||||
|
||||
JTextArea pubkeyArea = new JTextArea(
|
||||
R.getI18NString(
|
||||
"service.gui.CERT_INFO_KEY_BYTES_PRINT",
|
||||
new String[]{
|
||||
String.valueOf(key.getModulus().toByteArray().length - 1),
|
||||
key.getModulus().toString(16)
|
||||
}));
|
||||
pubkeyArea.setLineWrap(false);
|
||||
pubkeyArea.setOpaque(false);
|
||||
pubkeyArea.setWrapStyleWord(true);
|
||||
pubkeyArea.setEditable(false);
|
||||
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
pubkeyArea,
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_EXP")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(key.getPublicExponent().toString()),
|
||||
constraints);
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_KEY_SIZE")),
|
||||
constraints);
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
new JLabel(R.getI18NString(
|
||||
"service.gui.CERT_INFO_KEY_BITS_PRINT",
|
||||
new String[]{
|
||||
String.valueOf(key.getModulus().bitLength())})),
|
||||
constraints);
|
||||
}
|
||||
else if(certificate.getPublicKey().getAlgorithm().equals("DSA"))
|
||||
{
|
||||
DSAPublicKey key =
|
||||
(DSAPublicKey)certificate.getPublicKey();
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel("Y:"), constraints);
|
||||
|
||||
JTextArea yArea = new JTextArea(key.getY().toString(16));
|
||||
yArea.setLineWrap(false);
|
||||
yArea.setOpaque(false);
|
||||
yArea.setWrapStyleWord(true);
|
||||
yArea.setEditable(false);
|
||||
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
yArea,
|
||||
constraints);
|
||||
}
|
||||
|
||||
constraints.gridy = currentRow++;
|
||||
constraints.gridx = 0;
|
||||
certDisplayPanel.add(new JLabel(
|
||||
R.getI18NString("service.gui.CERT_INFO_SIGN")),
|
||||
constraints);
|
||||
|
||||
JTextArea signArea = new JTextArea(
|
||||
R.getI18NString(
|
||||
"service.gui.CERT_INFO_KEY_BYTES_PRINT",
|
||||
new String[]{
|
||||
String.valueOf(certificate.getSignature().length),
|
||||
getHex(certificate.getSignature())
|
||||
}));
|
||||
signArea.setLineWrap(false);
|
||||
signArea.setOpaque(false);
|
||||
signArea.setWrapStyleWord(true);
|
||||
signArea.setEditable(false);
|
||||
|
||||
constraints.gridx = 1;
|
||||
certDisplayPanel.add(
|
||||
signArea,
|
||||
constraints);
|
||||
|
||||
return certDisplayPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the byte array to hex string.
|
||||
* @param raw the data.
|
||||
* @return the hex string.
|
||||
*/
|
||||
public String getHex( byte [] raw )
|
||||
{
|
||||
if (raw == null)
|
||||
return null;
|
||||
|
||||
StringBuilder hex = new StringBuilder(2 * raw.length);
|
||||
Formatter f = new Formatter(hex);
|
||||
for (byte b : raw)
|
||||
{
|
||||
f.format("%02x", b);
|
||||
}
|
||||
return hex.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.protocol.sip.net;
|
||||
|
||||
import java.security.cert.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
import javax.sip.address.*;
|
||||
|
||||
import net.java.sip.communicator.impl.protocol.sip.*;
|
||||
import net.java.sip.communicator.service.certificate.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* Matcher that extracts certificate identities according to <a
|
||||
* href="http://tools.ietf.org/html/rfc5922#section-7.1">RFC5922, Section
|
||||
* 7.1</a> and compares them with the rules from Section 7.2 and 7.3.
|
||||
* @see #PNAME_STRICT_RFC5922 for wildcard handling; the default is false
|
||||
*
|
||||
* @author Ingo Bauersachs
|
||||
*/
|
||||
public class RFC5922Matcher
|
||||
implements CertificateMatcher
|
||||
{
|
||||
/**
|
||||
* When set to true, enables strict validation of the hostname according to
|
||||
* <a href="http://tools.ietf.org/html/rfc5922#section-7.2">RFC5922 Section
|
||||
* 7.2</a>
|
||||
*/
|
||||
public final static String PNAME_STRICT_RFC5922 =
|
||||
"net.java.sip.communicator.sip.tls.STRICT_RFC5922";
|
||||
|
||||
private ProtocolProviderServiceSipImpl provider;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class.
|
||||
* @param provider The SIP Provider to which this matcher belongs.
|
||||
*/
|
||||
public RFC5922Matcher(ProtocolProviderServiceSipImpl provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/** Our class logger. */
|
||||
private static final Logger logger = Logger
|
||||
.getLogger(CertificateMatcher.class);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* net.java.sip.communicator.service.certificate.CertificateMatcher#verify
|
||||
* (java.lang.Iterable, java.security.cert.X509Certificate)
|
||||
*/
|
||||
public void verify(Iterable<String> identitiesToTest, X509Certificate cert)
|
||||
throws CertificateException
|
||||
{
|
||||
boolean strict = SipActivator.getConfigurationService()
|
||||
.getBoolean(PNAME_STRICT_RFC5922, false);
|
||||
|
||||
// if any of the identities is contained in the certificate we're good
|
||||
boolean oneMatched = false;
|
||||
Iterable<String> certIdentities = extractCertIdentities(cert);
|
||||
for (String identity : identitiesToTest)
|
||||
{
|
||||
// check if the intended hostname is contained in one of the
|
||||
// hostnames of the certificate according to
|
||||
// http://tools.ietf.org/html/rfc5922#section-7.2
|
||||
for(String dnsName : certIdentities)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(NetworkUtils.compareDnsNames(dnsName, identity) == 0)
|
||||
{
|
||||
// one of the hostnames matched, we're good to go
|
||||
return;
|
||||
}
|
||||
|
||||
if(!strict
|
||||
// is a wildcard name
|
||||
&& dnsName.startsWith("*.")
|
||||
// contains at least two dots (*.example.com)
|
||||
&& identity.indexOf(".") < identity.lastIndexOf(".")
|
||||
// compare *.example.com stripped to example.com with
|
||||
// - foo.example.com stripped to example.com
|
||||
// - foo.bar.example.com to bar.example.com
|
||||
&& NetworkUtils.compareDnsNames(
|
||||
dnsName.substring(2),
|
||||
identity.substring(identity.indexOf(".")+1)) == 0)
|
||||
{
|
||||
// the wildcard matched, we're good to go
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ParseException e)
|
||||
{} // we don't care - this hostname did not match
|
||||
}
|
||||
}
|
||||
if (!oneMatched)
|
||||
throw new CertificateException("None of <" + identitiesToTest
|
||||
+ "> matched by the rules of RFC5922 to the cert with CN="
|
||||
+ cert.getSubjectDN());
|
||||
}
|
||||
|
||||
private Iterable<String> extractCertIdentities(X509Certificate cert)
|
||||
{
|
||||
List<String> certIdentities = new ArrayList<String>();
|
||||
Collection<List<?>> subjAltNames = null;
|
||||
try
|
||||
{
|
||||
subjAltNames = cert.getSubjectAlternativeNames();
|
||||
}
|
||||
catch (CertificateParsingException ex)
|
||||
{
|
||||
logger.error("Error parsing TLS certificate", ex);
|
||||
}
|
||||
// subjAltName types are defined in rfc2459
|
||||
final Integer dnsNameType = 2;
|
||||
final Integer uriNameType = 6;
|
||||
if (subjAltNames != null)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("found subjAltNames: " + subjAltNames);
|
||||
|
||||
// First look for a URI in the subjectAltName field
|
||||
for (List<?> altName : subjAltNames)
|
||||
{
|
||||
// 0th position is the alt name type
|
||||
// 1st position is the alt name data
|
||||
if (altName.get(0).equals(uriNameType))
|
||||
{
|
||||
SipURI altNameUri;
|
||||
try
|
||||
{
|
||||
altNameUri =
|
||||
provider.getAddressFactory().createSipURI(
|
||||
(String) altName.get(1));
|
||||
// only sip URIs are allowed
|
||||
if (!"sip".equals(altNameUri.getScheme()))
|
||||
continue;
|
||||
// user certificates are not allowed
|
||||
if (altNameUri.getUser() != null)
|
||||
continue;
|
||||
String altHostName = altNameUri.getHost();
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("found uri " + altName.get(1)
|
||||
+ ", hostName " + altHostName);
|
||||
}
|
||||
certIdentities.add(altHostName);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
logger.error("certificate contains invalid uri: "
|
||||
+ altName.get(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// DNS An implementation MUST accept a domain name system
|
||||
// identifier as a SIP domain identity if and only if no other
|
||||
// identity is found that matches the "sip" URI type described
|
||||
// above.
|
||||
if (certIdentities.isEmpty())
|
||||
{
|
||||
for (List<?> altName : subjAltNames)
|
||||
{
|
||||
if (altName.get(0).equals(dnsNameType))
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("found dns " + altName.get(1));
|
||||
certIdentities.add(altName.get(1).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If and only if the subjectAltName does not appear in the
|
||||
// certificate, the implementation MAY examine the CN field of the
|
||||
// certificate. If a valid DNS name is found there, the
|
||||
// implementation MAY accept this value as a SIP domain identity.
|
||||
String dname = cert.getSubjectDN().getName();
|
||||
String cname = "";
|
||||
try
|
||||
{
|
||||
Pattern EXTRACT_CN =
|
||||
Pattern.compile(".*CN\\s*=\\s*([\\w*\\.]+).*");
|
||||
Matcher matcher = EXTRACT_CN.matcher(dname);
|
||||
if (matcher.matches())
|
||||
{
|
||||
cname = matcher.group(1);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("found CN: " + cname + " from DN: "
|
||||
+ dname);
|
||||
}
|
||||
certIdentities.add(cname);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("exception while extracting CN", ex);
|
||||
}
|
||||
}
|
||||
return certIdentities;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.service.certificate;
|
||||
|
||||
import java.security.cert.*;
|
||||
|
||||
public interface CertificateMatcher
|
||||
{
|
||||
/**
|
||||
* Implementations check whether one of the supplied identities is
|
||||
* contained in the certificate.
|
||||
*
|
||||
* @param identitiesToTest The that are compared against the certificate.
|
||||
* @param cert The X.509 certificate that was supplied by the server or
|
||||
* client.
|
||||
* @throws CertificateException When any certificate parsing fails.
|
||||
*/
|
||||
public void verify(Iterable<String> identitiesToTest, X509Certificate cert)
|
||||
throws CertificateException;
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.service.certificate;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.*;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
|
||||
/**
|
||||
* A service which implementors will ask the user for permission for the
|
||||
* certificates which are for some reason not valid and not globally trusted.
|
||||
*
|
||||
* @author Damian Minkov
|
||||
* @author Ingo Bauersachs
|
||||
*/
|
||||
public interface CertificateService
|
||||
{
|
||||
/**
|
||||
* Property for always trust mode. When enabled certificate check is
|
||||
* skipped.
|
||||
*/
|
||||
public final static String PNAME_ALWAYS_TRUST =
|
||||
"net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED";
|
||||
|
||||
/**
|
||||
* When set to true, the certificate check is performed. If the check fails
|
||||
* the user is not asked and the error is directly reported to the calling
|
||||
* service.
|
||||
*/
|
||||
public final static String PNAME_NO_USER_INTERACTION =
|
||||
"net.java.sip.communicator.service.tls.NO_USER_INTERACTION";
|
||||
|
||||
/**
|
||||
* Result of user interaction. User does not trust this certificate.
|
||||
*/
|
||||
public final static int DO_NOT_TRUST = 0;
|
||||
|
||||
/**
|
||||
* Result of user interaction. User will always trust this certificate.
|
||||
*/
|
||||
public final static int TRUST_ALWAYS = 1;
|
||||
|
||||
/**
|
||||
* Result of user interaction. User will trust this certificate
|
||||
* only for the current session.
|
||||
*/
|
||||
public final static int TRUST_THIS_SESSION_ONLY = 2;
|
||||
|
||||
/**
|
||||
* Get an SSL Context that validates certificates based on the JRE default
|
||||
* check and asks the user when the JRE check fails.
|
||||
*
|
||||
* CAUTION: Only the certificate itself is validated, no check is performed
|
||||
* whether it is valid for a specific server or client.
|
||||
*
|
||||
* @return An SSL context based on a user confirming trust manager.
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public SSLContext getSSLContext() throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Get an SSL Context with the specified trustmanager.
|
||||
*
|
||||
* @param trustManager The trustmanager that will be used by the created
|
||||
* SSLContext
|
||||
* @return An SSL context based on the supplied trust manager.
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public SSLContext getSSLContext(X509TrustManager trustManager)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Creates a trustmanager that validates the certificate based on the JRE
|
||||
* default check and asks the user when the JRE check fails. When
|
||||
* <tt>null</tt> is passed as the <tt>identityToTest</tt> then no check is
|
||||
* performed whether the certificate is valid for a specific server or
|
||||
* client. The passed identities are checked by applying a behavior similar
|
||||
* to the on regular browsers use.
|
||||
*
|
||||
* @param identitiesToTest when not <tt>null</tt>, the values are assumed
|
||||
* to be hostnames for invocations of checkServerTrusted and
|
||||
* e-mail addresses for invocations of checkClientTrusted
|
||||
* @return TrustManager to use in an SSLContext
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public X509TrustManager getTrustManager(Iterable<String> identitiesToTest)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* @see #getTrustManager(Iterable)
|
||||
*
|
||||
* @param identityToTest when not <tt>null</tt>, the value is assumed to
|
||||
* be a hostname for invocations of checkServerTrusted and an
|
||||
* e-mail address for invocations of checkClientTrusted
|
||||
* @return TrustManager to use in an SSLContext
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public X509TrustManager getTrustManager(String identityToTest)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* @see #getTrustManager(Iterable, CertificateMatcher, CertificateMatcher)
|
||||
*
|
||||
* @param identityToTest The identity to match against the supplied
|
||||
* verifiers.
|
||||
* @param clientVerifier The verifier to use in calls to checkClientTrusted
|
||||
* @param serverVerifier The verifier to use in calls to checkServerTrusted
|
||||
* @return TrustManager to use in an SSLContext
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public X509TrustManager getTrustManager(
|
||||
final String identityToTest,
|
||||
final CertificateMatcher clientVerifier,
|
||||
final CertificateMatcher serverVerifier)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Creates a trustmanager that validates the certificate based on the JRE
|
||||
* default check and asks the user when the JRE check fails. When
|
||||
* <tt>null</tt> is passed as the <tt>identityToTest</tt> then no check is
|
||||
* performed whether the certificate is valid for a specific server or
|
||||
* client.
|
||||
*
|
||||
* @param identitiesToTest The identities to match against the supplied
|
||||
* verifiers.
|
||||
* @param clientVerifier The verifier to use in calls to checkClientTrusted
|
||||
* @param serverVerifier The verifier to use in calls to checkServerTrusted
|
||||
* @return TrustManager to use in an SSLContext
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public X509TrustManager getTrustManager(
|
||||
final Iterable<String> identitiesToTest,
|
||||
final CertificateMatcher clientVerifier,
|
||||
final CertificateMatcher serverVerifier)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Adds a certificate to the local trust store.
|
||||
*
|
||||
* @param cert The certificate to add to the trust store.
|
||||
* @param trustMode Whether to trust the certificate permanently or only
|
||||
* for the current session.
|
||||
* @throws CertificateException when the thumbprint could not be calculated
|
||||
*/
|
||||
public void addCertificateToTrust(Certificate cert, String trustFor,
|
||||
int trustMode) throws CertificateException;
|
||||
}
|
||||
Loading…
Reference in new issue