Java tutorial
/* * Copyright (C) 2013 University of Edinburgh. * * 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 uk.org.ukfederation.mda.validate; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import net.shibboleth.metadata.Item; import net.shibboleth.metadata.pipeline.StageProcessingException; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.component.ComponentSupport; import net.shibboleth.utilities.java.support.logic.Constraint; import net.shibboleth.utilities.java.support.resource.Resource; import net.shibboleth.utilities.java.support.resource.ResourceException; import org.apache.commons.codec.binary.Hex; /** * Validator class to check RSA moduli in X.509 certificates against a OpenSSL-format * blacklist. Appropriate blacklists are available as part of the Debian 7.x * openssl-blacklist and openssl-blacklist-extra packages. */ @ThreadSafe public class X509RSAOpenSSLBlacklistValidator extends AbstractX509Validator { /** Sequence of bytes put on the front of the string to be hashed. */ private final byte[] openSSLprefix = { 'M', 'o', 'd', 'u', 'l', 'u', 's', '=', }; /** Resource that provides the blacklist. */ private Resource blacklistResource; /** Restrict checking to a given key size. Default: no restriction (0). */ private int keySize; /** Set of digest values blacklisted by this validator. */ private final Set<String> blacklistedValues = new HashSet<>(); /** * Constructor. */ public X509RSAOpenSSLBlacklistValidator() { super(); setId("OpenSSLBlacklist"); } /** * Gets the resource that provides the blacklist. * * @return resource that provides the blacklist */ @Nullable public Resource getBlacklistResource() { return blacklistResource; } /** * Sets the resource that provides the blacklist. * * @param resource resource that provides the blacklist */ public synchronized void setBlacklistResource(@Nonnull final Resource resource) { ComponentSupport.ifDestroyedThrowDestroyedComponentException(this); ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); blacklistResource = Constraint.isNotNull(resource, "blacklist resource can not be null"); } /** * Sets a key size restriction for this blacklist. * * @param size restricted key size, or 0 for no restriction */ public void setKeySize(final int size) { keySize = size; } /** * Gets the key size restriction for this blacklist. * * @return restricted key size for this blacklist, or 0 if no restriction */ public int getKeySize() { return keySize; } /** * Computes the OpenSSL digest value for the given modulus. * * @param modulus RSA public modulus to be digested * @return value to be compared against the blacklist * @throws StageProcessingException if SHA1 digester can not be acquired, or for internal * errors related to {@link ByteArrayOutputStream} */ @Nonnull private String openSSLDigest(@Nonnull final BigInteger modulus) throws StageProcessingException { try { // Acquire a representation of the modulus byte[] modulusBytes = modulus.toByteArray(); if (modulusBytes[0] == 0) { // drop first 00 byte of modulus representation modulusBytes = Arrays.copyOfRange(modulusBytes, 1, modulusBytes.length); } // Encode the modulus into upper-case hex characters final char[] encodedModulus = Hex.encodeHex(modulusBytes, false); // Now construct the thing we want to hash ByteArrayOutputStream bb = new ByteArrayOutputStream(); try { bb.write(openSSLprefix); for (char c : encodedModulus) { bb.write((byte) c); } bb.write('\n'); } catch (IOException e) { throw new StageProcessingException("internal error writing to ByteArrayStream", e); } //System.out.println("To be digested: " + bb.toString()); // Make the digest final MessageDigest digest = MessageDigest.getInstance("SHA1"); digest.update(bb.toByteArray()); final byte[] bytes = digest.digest(); // Convert the digest to a lower-case hex string char[] encodedDigest = Hex.encodeHex(bytes, true); final String strValue = String.valueOf(encodedDigest); final String trimmed = strValue.substring(20); //System.out.println("Digest: " + strValue + " trimmed " + trimmed); return trimmed; } catch (NoSuchAlgorithmException e) { throw new StageProcessingException("could not create message digester", e); } } /** {@inheritDoc} */ @Override public void validate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item, @Nonnull final String stageId) throws StageProcessingException { ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this); final PublicKey key = cert.getPublicKey(); if ("RSA".equals(key.getAlgorithm())) { final RSAPublicKey rsaKey = (RSAPublicKey) key; final BigInteger modulus = rsaKey.getModulus(); if (keySize == 0 || keySize == modulus.bitLength()) { final String value = openSSLDigest(modulus); if (blacklistedValues.contains(value)) { addError("RSA modulus included in key blacklist (" + value + ")", item, stageId); } } } } /** {@inheritDoc} */ @Override protected void doDestroy() { blacklistResource.destroy(); blacklistResource = null; blacklistedValues.clear(); super.doDestroy(); } /** {@inheritDoc} */ @Override protected void doInitialize() throws ComponentInitializationException { super.doInitialize(); if (blacklistResource == null) { throw new ComponentInitializationException( "Unable to initialize " + getId() + ", blacklistResource must not be null"); } if (!blacklistResource.isInitialized()) { blacklistResource.initialize(); } try { if (!blacklistResource.exists()) { throw new ComponentInitializationException("Unable to initialize " + getId() + ", blacklistResource " + blacklistResource.getLocation() + " does not exist"); } try (BufferedReader reader = new BufferedReader( new InputStreamReader(blacklistResource.getInputStream()))) { while (true) { final String line = reader.readLine(); if (line == null) { break; } // Ignore lines consisting only of whitespace, including blank lines. if (line.trim().length() == 0) { continue; } // Ignore comments. if (line.charAt(0) != '#') { blacklistedValues.add(line); } } } catch (IOException e) { throw new ComponentInitializationException("Unable to initialize " + getId() + ", error reading blacklistResource " + blacklistResource.getLocation() + " information", e); } } catch (ResourceException e) { throw new ComponentInitializationException("Unable to initialize " + getId() + ", error reading blacklistResource " + blacklistResource.getLocation() + " information", e); } } }