/***************************************************************************
* Copyright (c) 2012-2013 VMware, Inc. All Rights Reserved.
* 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 com.vmware.aurora.security;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.X509TrustManager;
import org.apache.log4j.Logger;
/**
* Our custom trust manager is instantiated with expected thumbprints. Instead
* of checking that the server certificate is trusted (which is typically not
* the case, because providers have generated self-signed certificates), we
* check that the thumbprint of the certificate is as expected.
*/
public class ThumbprintTrustManager implements X509TrustManager {
private static final Logger logger = Logger.getLogger(ThumbprintTrustManager.class);
private MessageDigest sha1;
private Map<String, ThumbprintHolder> thumbprints;
static class ThumbprintHolder {
List<Object> owners = null; // keep track of owners
boolean permanent; // always held in thumbprint Hashtable
private void addOwner(Object owner) {
if (permanent) {
return;
}
if (owner == null) {
// promote holder to permanent
permanent = true;
owners = null;
} else {
owners.add(owner);
}
}
private void removeOwner(Object owner) {
if (permanent) {
return;
}
if (owners != null) {
owners.remove(owner);
}
}
public ThumbprintHolder(Object owner) {
permanent = (owner == null);
if (!permanent) {
owners = new ArrayList<Object>();
owners.add(owner);
}
}
private boolean canRemove() {
return !permanent && owners.isEmpty();
}
}
/*
* Add a thumbprint owned by "owner".
* If owner is null, keep the thumbprint until it is removed by force.
*/
public synchronized void add(String thumbprint, Object owner) {
String tp = thumbprint.toLowerCase();
ThumbprintHolder holder = thumbprints.get(tp);
if (holder == null) {
holder = new ThumbprintHolder(owner);
thumbprints.put(tp, holder);
} else {
holder.addOwner(owner);
}
}
public void add(String thumbprint) {
add(thumbprint, null);
}
/*
* Remove a thumbprint owned by "owner".
* If owner is null, force remove the thumbprint.
*/
public synchronized void remove(String thumbprint, Object owner) {
String tp = thumbprint.toLowerCase();
ThumbprintHolder holder = thumbprints.get(tp);
if (holder == null) {
return;
}
holder.removeOwner(owner);
if (owner == null || holder.canRemove()) {
thumbprints.remove(tp);
}
}
public void remove(String thumbprint) {
remove(thumbprint, null);
}
public synchronized boolean hasThumbprint(String thumbprint) {
String tp = thumbprint.toLowerCase();
return thumbprints.get(tp) != null;
}
public ThumbprintTrustManager() throws Exception {
thumbprints = new HashMap<String, ThumbprintHolder>();
sha1 = MessageDigest.getInstance("SHA-1");
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new CertificateException();
}
/**
* Encodes an array of bytes as hex symbols.
*
* @param bytes the array of bytes to encode
* @param separator the separator to use between two bytes, can be null
* @return the resulting hex string
*/
public static String toHex(byte[] bytes, String separator) {
StringBuffer result = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
int unsignedByte = bytes[i] & 0xff;
if (unsignedByte < 16) {
result.append("0");
}
result.append(Integer.toHexString(unsignedByte));
if (separator != null && i + 1 < bytes.length) {
result.append(separator);
}
}
return result.toString();
}
public static String toHex(byte[] bytes) {
return toHex(bytes, null);
}
/**
* Generates a thumbprint for a certificate
*
* @param cert the certificate to generate a thumbprint for
* @return the certificate thumbprint
* @throws CertificateEncodingException
*/
public String certificateToThumbprint(Certificate cert)
throws CertificateEncodingException {
sha1.reset();
try {
return toHex(sha1.digest(cert.getEncoded()), ":");
} catch (CertificateEncodingException e) {
logger.error(e);
throw e;
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
if (chain.length == 0) {
logger.warn("No certificates in chain");
throw new CertificateException();
}
/*
* Usually, there will be only one, self-signed certificate in the list.
* But in the general case, we want to examine the first.
*
* The TLS specification states that: "The sender's certificate must come
* first in the list. Each following certificate must directly certify the
* one preceding it."
*/
String tp = certificateToThumbprint(chain[0]);
if (!hasThumbprint(tp)) {
logger.warn("Invalid SSL thumbprint received: " + tp);
throw new CertificateException();
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}