You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:18 UTC

[19/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring project structure to better isolate extensions

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java
new file mode 100644
index 0000000..b14cb61
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java
@@ -0,0 +1,670 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.security.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.util.KeyStoreUtils;
+import org.apache.nifi.registry.security.util.KeystoreType;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.Socket;
+import java.net.URL;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public final class CertificateUtils {
+    private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class);
+    private static final String PEER_NOT_AUTHENTICATED_MSG = "peer not authenticated";
+    private static final Map<ASN1ObjectIdentifier, Integer> dnOrderMap = createDnOrderMap();
+
+    static {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    /**
+     * The time in milliseconds that the last unique serial number was generated
+     */
+    private static long lastSerialNumberMillis = 0L;
+
+    /**
+     * An incrementor to add uniqueness to serial numbers generated in the same millisecond
+     */
+    private static int serialNumberIncrementor = 0;
+
+    /**
+     * BigInteger value to use for the base of the unique serial number
+     */
+    private static BigInteger millisecondBigInteger;
+
+    private static Map<ASN1ObjectIdentifier, Integer> createDnOrderMap() {
+        Map<ASN1ObjectIdentifier, Integer> orderMap = new HashMap<>();
+        int count = 0;
+        orderMap.put(BCStyle.CN, count++);
+        orderMap.put(BCStyle.L, count++);
+        orderMap.put(BCStyle.ST, count++);
+        orderMap.put(BCStyle.O, count++);
+        orderMap.put(BCStyle.OU, count++);
+        orderMap.put(BCStyle.C, count++);
+        orderMap.put(BCStyle.STREET, count++);
+        orderMap.put(BCStyle.DC, count++);
+        orderMap.put(BCStyle.UID, count++);
+        return Collections.unmodifiableMap(orderMap);
+    }
+
+    public enum ClientAuth {
+        NONE(0, "none"),
+        WANT(1, "want"),
+        NEED(2, "need");
+
+        private int value;
+        private String description;
+
+        ClientAuth(int value, String description) {
+            this.value = value;
+            this.description = description;
+        }
+
+        @Override
+        public String toString() {
+            return "Client Auth: " + this.description + " (" + this.value + ")";
+        }
+    }
+
+    /**
+     * Returns true if the given keystore can be loaded using the given keystore type and password. Returns false otherwise.
+     *
+     * @param keystore     the keystore to validate
+     * @param keystoreType the type of the keystore
+     * @param password     the password to access the keystore
+     * @return true if valid; false otherwise
+     */
+    public static boolean isStoreValid(final URL keystore, final KeystoreType keystoreType, final char[] password) {
+
+        if (keystore == null) {
+            throw new IllegalArgumentException("keystore may not be null");
+        } else if (keystoreType == null) {
+            throw new IllegalArgumentException("keystore type may not be null");
+        } else if (password == null) {
+            throw new IllegalArgumentException("password may not be null");
+        }
+
+        BufferedInputStream bis = null;
+        final KeyStore ks;
+        try {
+
+            // load the keystore
+            bis = new BufferedInputStream(keystore.openStream());
+            ks = KeyStoreUtils.getKeyStore(keystoreType.name());
+            ks.load(bis, password);
+
+            return true;
+
+        } catch (Exception e) {
+            return false;
+        } finally {
+            if (bis != null) {
+                try {
+                    bis.close();
+                } catch (final IOException ioe) {
+                    logger.warn("Failed to close input stream", ioe);
+                }
+            }
+        }
+    }
+
+    /**
+     * Extracts the username from the specified DN. If the username cannot be extracted because the CN is in an unrecognized format, the entire CN is returned. If the CN cannot be extracted because
+     * the DN is in an unrecognized format, the entire DN is returned.
+     *
+     * @param dn the dn to extract the username from
+     * @return the exatracted username
+     */
+    public static String extractUsername(String dn) {
+        String username = dn;
+
+        // ensure the dn is specified
+        if (StringUtils.isNotBlank(dn)) {
+            // determine the separate
+            final String separator = StringUtils.indexOfIgnoreCase(dn, "/cn=") > 0 ? "/" : ",";
+
+            // attempt to locate the cd
+            final String cnPattern = "cn=";
+            final int cnIndex = StringUtils.indexOfIgnoreCase(dn, cnPattern);
+            if (cnIndex >= 0) {
+                int separatorIndex = StringUtils.indexOf(dn, separator, cnIndex);
+                if (separatorIndex > 0) {
+                    username = StringUtils.substring(dn, cnIndex + cnPattern.length(), separatorIndex);
+                } else {
+                    username = StringUtils.substring(dn, cnIndex + cnPattern.length());
+                }
+            }
+        }
+
+        return username;
+    }
+
+    /**
+     * Returns a list of subject alternative names. Any name that is represented as a String by X509Certificate.getSubjectAlternativeNames() is converted to lowercase and returned.
+     *
+     * @param certificate a certificate
+     * @return a list of subject alternative names; list is never null
+     * @throws CertificateParsingException if parsing the certificate failed
+     */
+    public static List<String> getSubjectAlternativeNames(final X509Certificate certificate) throws CertificateParsingException {
+
+        final Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
+        if (altNames == null) {
+            return new ArrayList<>();
+        }
+
+        final List<String> result = new ArrayList<>();
+        for (final List<?> generalName : altNames) {
+            /**
+             * generalName has the name type as the first element a String or byte array for the second element. We return any general names that are String types.
+             *
+             * We don't inspect the numeric name type because some certificates incorrectly put IPs and DNS names under the wrong name types.
+             */
+            final Object value = generalName.get(1);
+            if (value instanceof String) {
+                result.add(((String) value).toLowerCase());
+            }
+
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the DN extracted from the peer certificate (the server DN if run on the client; the client DN (if available) if run on the server).
+     *
+     * If the client auth setting is WANT or NONE and a client certificate is not present, this method will return {@code null}.
+     * If the client auth is NEED, it will throw a {@link CertificateException}.
+     *
+     * @param socket the SSL Socket
+     * @return the extracted DN
+     * @throws CertificateException if there is a problem parsing the certificate
+     */
+    public static String extractPeerDNFromSSLSocket(Socket socket) throws CertificateException {
+        String dn = null;
+        if (socket instanceof SSLSocket) {
+            final SSLSocket sslSocket = (SSLSocket) socket;
+
+            boolean clientMode = sslSocket.getUseClientMode();
+            logger.debug("SSL Socket in {} mode", clientMode ? "client" : "server");
+            ClientAuth clientAuth = getClientAuthStatus(sslSocket);
+            logger.debug("SSL Socket client auth status: {}", clientAuth);
+
+            if (clientMode) {
+                logger.debug("This socket is in client mode, so attempting to extract certificate from remote 'server' socket");
+               dn = extractPeerDNFromServerSSLSocket(sslSocket);
+            } else {
+                logger.debug("This socket is in server mode, so attempting to extract certificate from remote 'client' socket");
+               dn = extractPeerDNFromClientSSLSocket(sslSocket);
+            }
+        }
+
+        return dn;
+    }
+
+    /**
+     * Returns the DN extracted from the client certificate.
+     *
+     * If the client auth setting is WANT or NONE and a certificate is not present (and {@code respectClientAuth} is {@code true}), this method will return {@code null}.
+     * If the client auth is NEED, it will throw a {@link CertificateException}.
+     *
+     * @param sslSocket the SSL Socket
+     * @return the extracted DN
+     * @throws CertificateException if there is a problem parsing the certificate
+     */
+    private static String extractPeerDNFromClientSSLSocket(SSLSocket sslSocket) throws CertificateException {
+        String dn = null;
+
+            /** The clientAuth value can be "need", "want", or "none"
+             * A client must send client certificates for need, should for want, and will not for none.
+             * This method should throw an exception if none are provided for need, return null if none are provided for want, and return null (without checking) for none.
+             */
+
+            ClientAuth clientAuth = getClientAuthStatus(sslSocket);
+            logger.debug("SSL Socket client auth status: {}", clientAuth);
+
+            if (clientAuth != ClientAuth.NONE) {
+                try {
+                    final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
+                    if (certChains != null && certChains.length > 0) {
+                        X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
+                        dn = x509Certificate.getSubjectDN().getName().trim();
+                        logger.debug("Extracted DN={} from client certificate", dn);
+                    }
+                } catch (SSLPeerUnverifiedException e) {
+                    if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+                        logger.error("The incoming request did not contain client certificates and thus the DN cannot" +
+                                " be extracted. Check that the other endpoint is providing a complete client certificate chain");
+                    }
+                    if (clientAuth == ClientAuth.WANT) {
+                        logger.warn("Suppressing missing client certificate exception because client auth is set to 'want'");
+                        return dn;
+                    }
+                    throw new CertificateException(e);
+                }
+            }
+        return dn;
+    }
+
+    /**
+     * Returns the DN extracted from the server certificate.
+     *
+     * @param socket the SSL Socket
+     * @return the extracted DN
+     * @throws CertificateException if there is a problem parsing the certificate
+     */
+    private static String extractPeerDNFromServerSSLSocket(Socket socket) throws CertificateException {
+        String dn = null;
+        if (socket instanceof SSLSocket) {
+            final SSLSocket sslSocket = (SSLSocket) socket;
+                try {
+                    final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
+                    if (certChains != null && certChains.length > 0) {
+                        X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
+                        dn = x509Certificate.getSubjectDN().getName().trim();
+                        logger.debug("Extracted DN={} from server certificate", dn);
+                    }
+                } catch (SSLPeerUnverifiedException e) {
+                    if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+                        logger.error("The server did not present a certificate and thus the DN cannot" +
+                                " be extracted. Check that the other endpoint is providing a complete certificate chain");
+                    }
+                    throw new CertificateException(e);
+                }
+        }
+        return dn;
+    }
+
+    private static ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
+        return sslSocket.getNeedClientAuth() ? ClientAuth.NEED : sslSocket.getWantClientAuth() ? ClientAuth.WANT : ClientAuth.NONE;
+    }
+
+    /**
+     * Accepts a legacy {@link javax.security.cert.X509Certificate} and returns an {@link X509Certificate}. The {@code javax.*} package certificate classes are for legacy compatibility and should
+     * not be used for new development.
+     *
+     * @param legacyCertificate the {@code javax.security.cert.X509Certificate}
+     * @return a new {@code java.security.cert.X509Certificate}
+     * @throws CertificateException if there is an error generating the new certificate
+     */
+    public static X509Certificate convertLegacyX509Certificate(javax.security.cert.X509Certificate legacyCertificate) throws CertificateException {
+        if (legacyCertificate == null) {
+            throw new IllegalArgumentException("The X.509 certificate cannot be null");
+        }
+
+        try {
+            return formX509Certificate(legacyCertificate.getEncoded());
+        } catch (javax.security.cert.CertificateEncodingException e) {
+            throw new CertificateException(e);
+        }
+    }
+
+    /**
+     * Accepts an abstract {@link Certificate} and returns an {@link X509Certificate}. Because {@code sslSocket.getSession().getPeerCertificates()} returns an array of the
+     * abstract certificates, they must be translated to X.509 to replace the functionality of {@code sslSocket.getSession().getPeerCertificateChain()}.
+     *
+     * @param abstractCertificate the {@code java.security.cert.Certificate}
+     * @return a new {@code java.security.cert.X509Certificate}
+     * @throws CertificateException if there is an error generating the new certificate
+     */
+    public static X509Certificate convertAbstractX509Certificate(Certificate abstractCertificate) throws CertificateException {
+        if (abstractCertificate == null || !(abstractCertificate instanceof X509Certificate)) {
+            throw new IllegalArgumentException("The certificate cannot be null and must be an X.509 certificate");
+        }
+
+        try {
+            return formX509Certificate(abstractCertificate.getEncoded());
+        } catch (java.security.cert.CertificateEncodingException e) {
+            throw new CertificateException(e);
+        }
+    }
+
+    private static X509Certificate formX509Certificate(byte[] encodedCertificate) throws CertificateException {
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            ByteArrayInputStream bais = new ByteArrayInputStream(encodedCertificate);
+            return (X509Certificate) cf.generateCertificate(bais);
+        } catch (CertificateException e) {
+            logger.error("Error converting the certificate", e);
+            throw e;
+        }
+    }
+
+    /**
+     * Reorders DN to the order the elements appear in the RFC 2253 table
+     *
+     * https://www.ietf.org/rfc/rfc2253.txt
+     *
+     * String  X.500 AttributeType
+     * ------------------------------
+     * CN      commonName
+     * L       localityName
+     * ST      stateOrProvinceName
+     * O       organizationName
+     * OU      organizationalUnitName
+     * C       countryName
+     * STREET  streetAddress
+     * DC      domainComponent
+     * UID     userid
+     *
+     * @param dn a possibly unordered DN
+     * @return the ordered dn
+     */
+    public static String reorderDn(String dn) {
+        RDN[] rdNs = new X500Name(dn).getRDNs();
+        Arrays.sort(rdNs, new Comparator<RDN>() {
+            @Override
+            public int compare(RDN o1, RDN o2) {
+                AttributeTypeAndValue o1First = o1.getFirst();
+                AttributeTypeAndValue o2First = o2.getFirst();
+
+                ASN1ObjectIdentifier o1Type = o1First.getType();
+                ASN1ObjectIdentifier o2Type = o2First.getType();
+
+                Integer o1Rank = dnOrderMap.get(o1Type);
+                Integer o2Rank = dnOrderMap.get(o2Type);
+                if (o1Rank == null) {
+                    if (o2Rank == null) {
+                        int idComparison = o1Type.getId().compareTo(o2Type.getId());
+                        if (idComparison != 0) {
+                            return idComparison;
+                        }
+                        return String.valueOf(o1Type).compareTo(String.valueOf(o2Type));
+                    }
+                    return 1;
+                } else if (o2Rank == null) {
+                    return -1;
+                }
+                return o1Rank - o2Rank;
+            }
+        });
+        return new X500Name(rdNs).toString();
+    }
+
+    /**
+     * Reverses the X500Name in order make the certificate be in the right order
+     * [see http://stackoverflow.com/questions/7567837/attributes-reversed-in-certificate-subject-and-issuer/12645265]
+     *
+     * @param x500Name the X500Name created with the intended order
+     * @return the X500Name reversed
+     */
+    private static X500Name reverseX500Name(X500Name x500Name) {
+        List<RDN> rdns = Arrays.asList(x500Name.getRDNs());
+        Collections.reverse(rdns);
+        return new X500Name(rdns.toArray(new RDN[rdns.size()]));
+    }
+
+    /**
+     * Generates a unique serial number by using the current time in milliseconds left shifted 32 bits (to make room for incrementor) with an incrementor added
+     *
+     * @return a unique serial number (technically unique to this classloader)
+     */
+    protected static synchronized BigInteger getUniqueSerialNumber() {
+        final long currentTimeMillis = System.currentTimeMillis();
+        final int incrementorValue;
+
+        if (lastSerialNumberMillis != currentTimeMillis) {
+            // We can only get into this block once per millisecond
+            millisecondBigInteger = BigInteger.valueOf(currentTimeMillis).shiftLeft(32);
+            lastSerialNumberMillis = currentTimeMillis;
+            incrementorValue = 0;
+            serialNumberIncrementor = 1;
+        } else {
+            // Already created at least one serial number this millisecond
+            incrementorValue = serialNumberIncrementor++;
+        }
+
+        return millisecondBigInteger.add(BigInteger.valueOf(incrementorValue));
+    }
+
+    /**
+     * Generates a self-signed {@link X509Certificate} suitable for use as a Certificate Authority.
+     *
+     * @param keyPair                 the {@link KeyPair} to generate the {@link X509Certificate} for
+     * @param dn                      the distinguished name to user for the {@link X509Certificate}
+     * @param signingAlgorithm        the signing algorithm to use for the {@link X509Certificate}
+     * @param certificateDurationDays the duration in days for which the {@link X509Certificate} should be valid
+     * @return a self-signed {@link X509Certificate} suitable for use as a Certificate Authority
+     * @throws CertificateException      if there is an generating the new certificate
+     */
+    public static X509Certificate generateSelfSignedX509Certificate(KeyPair keyPair, String dn, String signingAlgorithm, int certificateDurationDays)
+            throws CertificateException {
+        try {
+            ContentSigner sigGen = new JcaContentSignerBuilder(signingAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
+            SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
+            Date startDate = new Date();
+            Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(certificateDurationDays));
+
+            X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
+                    reverseX500Name(new X500Name(dn)),
+                    getUniqueSerialNumber(),
+                    startDate, endDate,
+                    reverseX500Name(new X500Name(dn)),
+                    subPubKeyInfo);
+
+            // Set certificate extensions
+            // (1) digitalSignature extension
+            certBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment
+                    | KeyUsage.keyAgreement | KeyUsage.nonRepudiation | KeyUsage.cRLSign | KeyUsage.keyCertSign));
+
+            certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
+
+            certBuilder.addExtension(Extension.subjectKeyIdentifier, false, new JcaX509ExtensionUtils().createSubjectKeyIdentifier(keyPair.getPublic()));
+
+            certBuilder.addExtension(Extension.authorityKeyIdentifier, false, new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.getPublic()));
+
+            // (2) extendedKeyUsage extension
+            certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}));
+
+            // Sign the certificate
+            X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
+            return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder);
+        } catch (CertIOException | NoSuchAlgorithmException | OperatorCreationException e) {
+            throw new CertificateException(e);
+        }
+    }
+
+    /**
+     * Generates an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
+     *
+     * @param dn the distinguished name to use
+     * @param publicKey the public key to issue the certificate to
+     * @param issuer the issuer's certificate
+     * @param issuerKeyPair the issuer's keypair
+     * @param signingAlgorithm the signing algorithm to use
+     * @param days the number of days it should be valid for
+     * @return an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
+     * @throws CertificateException if there is an error issuing the certificate
+     */
+    public static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, X509Certificate issuer, KeyPair issuerKeyPair, String signingAlgorithm, int days)
+            throws CertificateException {
+        return generateIssuedCertificate(dn, publicKey, null, issuer, issuerKeyPair, signingAlgorithm, days);
+    }
+
+    /**
+     * Generates an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
+     *
+     * @param dn the distinguished name to use
+     * @param publicKey the public key to issue the certificate to
+     * @param extensions extensions extracted from the CSR
+     * @param issuer the issuer's certificate
+     * @param issuerKeyPair the issuer's keypair
+     * @param signingAlgorithm the signing algorithm to use
+     * @param days the number of days it should be valid for
+     * @return an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
+     * @throws CertificateException if there is an error issuing the certificate
+     */
+    public static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, Extensions extensions, X509Certificate issuer, KeyPair issuerKeyPair, String signingAlgorithm, int days)
+            throws CertificateException {
+        try {
+            ContentSigner sigGen = new JcaContentSignerBuilder(signingAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(issuerKeyPair.getPrivate());
+            SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
+            Date startDate = new Date();
+            Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(days));
+
+            X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
+                    reverseX500Name(new X500Name(issuer.getSubjectX500Principal().getName())),
+                    getUniqueSerialNumber(),
+                    startDate, endDate,
+                    reverseX500Name(new X500Name(dn)),
+                    subPubKeyInfo);
+
+            certBuilder.addExtension(Extension.subjectKeyIdentifier, false, new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey));
+
+            certBuilder.addExtension(Extension.authorityKeyIdentifier, false, new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(issuerKeyPair.getPublic()));
+            // Set certificate extensions
+            // (1) digitalSignature extension
+            certBuilder.addExtension(Extension.keyUsage, true,
+                    new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement | KeyUsage.nonRepudiation));
+
+            certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
+
+            // (2) extendedKeyUsage extension
+            certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}));
+
+            // (3) subjectAlternativeName
+            if(extensions != null && extensions.getExtension(Extension.subjectAlternativeName) != null) {
+                certBuilder.addExtension(Extension.subjectAlternativeName, false, extensions.getExtensionParsedValue(Extension.subjectAlternativeName));
+            }
+
+            X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
+            return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder);
+        } catch (CertIOException | NoSuchAlgorithmException | OperatorCreationException e) {
+            throw new CertificateException(e);
+        }
+    }
+
+    /**
+     * Returns true if the two provided DNs are equivalent, regardless of the order of the elements. Returns false if one or both are invalid DNs.
+     *
+     * Example:
+     *
+     * CN=test1, O=testOrg, C=US compared to CN=test1, O=testOrg, C=US -> true
+     * CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test1, C=US -> true
+     * CN=test1, O=testOrg, C=US compared to CN=test2, O=testOrg, C=US -> false
+     * CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test2, C=US -> false
+     * CN=test1, O=testOrg, C=US compared to                           -> false
+     *                           compared to                           -> true
+     *
+     * @param dn1 the first DN to compare
+     * @param dn2 the second DN to compare
+     * @return true if the DNs are equivalent, false otherwise
+     */
+    public static boolean compareDNs(String dn1, String dn2) {
+        if (dn1 == null) {
+            dn1 = "";
+        }
+
+        if (dn2 == null) {
+            dn2 = "";
+        }
+
+        if (StringUtils.isEmpty(dn1) || StringUtils.isEmpty(dn2)) {
+            return dn1.equals(dn2);
+        }
+        try {
+            List<Rdn> rdn1 = new LdapName(dn1).getRdns();
+            List<Rdn> rdn2 = new LdapName(dn2).getRdns();
+
+            return rdn1.size() == rdn2.size() && rdn1.containsAll(rdn2);
+        } catch (InvalidNameException e) {
+            logger.warn("Cannot compare DNs: {} and {} because one or both is not a valid DN", dn1, dn2);
+            return false;
+        }
+    }
+
+    /**
+     * Extract extensions from CSR object
+     */
+    public static Extensions getExtensionsFromCSR(JcaPKCS10CertificationRequest csr) {
+        Attribute[] attributess = csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        for (Attribute attribute : attributess) {
+            ASN1Set attValue = attribute.getAttrValues();
+            if (attValue != null) {
+                ASN1Encodable extension = attValue.getObjectAt(0);
+                if (extension instanceof Extensions) {
+                    return (Extensions) extension;
+                } else if (extension instanceof DERSequence) {
+                    return Extensions.getInstance(extension);
+                }
+            }
+        }
+        return null;
+    }
+
+    private CertificateUtils() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CryptoUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CryptoUtils.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CryptoUtils.java
new file mode 100644
index 0000000..cd2d3e3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CryptoUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.security.util;
+
+import javax.crypto.Cipher;
+import java.security.NoSuchAlgorithmException;
+
+public class CryptoUtils {
+
+    /**
+     *  Required Cipher transformations according to Java SE 8 {@link Cipher} docs
+     */
+    private static final String[] standardCryptoTransformations = {
+        "AES/CBC/NoPadding",
+        "AES/CBC/PKCS5Padding",
+        "AES/ECB/NoPadding",
+        "AES/ECB/PKCS5Padding",
+        "DES/CBC/NoPadding",
+        "DES/CBC/PKCS5Padding",
+        "DES/ECB/NoPadding",
+        "DES/ECB/PKCS5Padding",
+        "DESede/CBC/NoPadding",
+        "DESede/CBC/PKCS5Padding",
+        "DESede/ECB/NoPadding",
+        "DESede/ECB/PKCS5Padding",
+        "RSA/ECB/PKCS1Padding",
+        "RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
+        "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
+    };
+
+    /**
+     * Check if cryptographic strength available in this Java Runtime is restricted.
+     *
+     * Not every Java Platform supports "unlimited strength encryption",
+     * so this convenience method provides a way to check if strength of crypto
+     * functions (i.e., max key length) is unlimited or restricted in the
+     * current Java runtime environment.
+     *
+     * @return true if it can be determined that max key lengths are less than unlimited
+     *         false if key lengths are restricted
+     *         null if max key length cannot be determined for any known Cipher transformations */
+    public static Boolean isCryptoRestricted() {
+
+        Boolean isCryptoRestricted = null;
+
+        for (String transformation : standardCryptoTransformations) {
+            try {
+                return Cipher.getMaxAllowedKeyLength(transformation) < Integer.MAX_VALUE;
+            } catch (final NoSuchAlgorithmException e) {
+                // Unexpected as we are pulling from a list of transforms that every
+                // java platform is required to support, but try the next one
+            }
+        }
+
+        // Tried every standard Cipher transformation and none were available,
+        // so crypto strength restrictions cannot be determined.
+        return null;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeyStoreUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeyStoreUtils.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeyStoreUtils.java
new file mode 100644
index 0000000..71f1ce0
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeyStoreUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.security.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+
+public class KeyStoreUtils {
+    private static final Logger logger = LoggerFactory.getLogger(KeyStoreUtils.class);
+
+    static {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    /**
+     * Returns the provider that will be used for the given keyStoreType
+     *
+     * @param keyStoreType the keyStoreType
+     * @return the provider that will be used
+     */
+    public static String getKeyStoreProvider(String keyStoreType) {
+        if (KeystoreType.PKCS12.toString().equalsIgnoreCase(keyStoreType)) {
+            return BouncyCastleProvider.PROVIDER_NAME;
+        }
+        return null;
+    }
+
+    /**
+     * Returns an empty KeyStore backed by the appropriate provider
+     *
+     * @param keyStoreType the keyStoreType
+     * @return an empty KeyStore
+     * @throws KeyStoreException if a KeyStore of the given type cannot be instantiated
+     */
+    public static KeyStore getKeyStore(String keyStoreType) throws KeyStoreException {
+        String keyStoreProvider = getKeyStoreProvider(keyStoreType);
+        if (StringUtils.isNotEmpty(keyStoreProvider)) {
+            try {
+                return KeyStore.getInstance(keyStoreType, keyStoreProvider);
+            } catch (Exception e) {
+                logger.error("Unable to load " + keyStoreProvider + " " + keyStoreType
+                        + " keystore.  This may cause issues getting trusted CA certificates as well as Certificate Chains for use in TLS.", e);
+            }
+        }
+        return KeyStore.getInstance(keyStoreType);
+    }
+
+    /**
+     * Returns an empty KeyStore intended for use as a TrustStore backed by the appropriate provider
+     *
+     * @param trustStoreType the trustStoreType
+     * @return an empty KeyStore
+     * @throws KeyStoreException if a KeyStore of the given type cannot be instantiated
+     */
+    public static KeyStore getTrustStore(String trustStoreType) throws KeyStoreException {
+        if (KeystoreType.PKCS12.toString().equalsIgnoreCase(trustStoreType)) {
+            logger.warn(trustStoreType + " truststores are deprecated.  " + KeystoreType.JKS.toString() + " is preferred.");
+        }
+        return getKeyStore(trustStoreType);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeystoreType.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeystoreType.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeystoreType.java
new file mode 100644
index 0000000..f143e5a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/KeystoreType.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.security.util;
+
+/**
+ * Keystore types.
+ */
+public enum KeystoreType {
+    PKCS12,
+    JKS;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/ProxiedEntitiesUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/ProxiedEntitiesUtils.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/ProxiedEntitiesUtils.java
new file mode 100644
index 0000000..f850341
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/ProxiedEntitiesUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.security.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ProxiedEntitiesUtils {
+    private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class);
+
+    public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain";
+    public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted";
+    public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails";
+
+    private static final String GT = ">";
+    private static final String ESCAPED_GT = "\\\\>";
+    private static final String LT = "<";
+    private static final String ESCAPED_LT = "\\\\<";
+
+    private static final String ANONYMOUS_CHAIN = "<>";
+
+    /**
+     * Formats the specified DN to be set as a HTTP header using well known conventions.
+     *
+     * @param dn raw dn
+     * @return the dn formatted as an HTTP header
+     */
+    public static String formatProxyDn(String dn) {
+        return LT + sanitizeDn(dn) + GT;
+    }
+
+    /**
+     * If a user provides a DN with the sequence '><', they could escape the tokenization process and impersonate another user.
+     * <p>
+     * Example:
+     * <p>
+     * Provided DN: {@code jdoe><alopresto} -> {@code <jdoe><alopresto><proxy...>} would allow the user to impersonate jdoe
+     *
+     * @param rawDn the unsanitized DN
+     * @return the sanitized DN
+     */
+    private static String sanitizeDn(String rawDn) {
+        if (StringUtils.isEmpty(rawDn)) {
+            return rawDn;
+        } else {
+            String sanitizedDn = rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT);
+            if (!sanitizedDn.equals(rawDn)) {
+                logger.warn("The provided DN [" + rawDn + "] contained dangerous characters that were escaped to [" + sanitizedDn + "]");
+            }
+            return sanitizedDn;
+        }
+    }
+
+    /**
+     * Reconstitutes the original DN from the sanitized version passed in the proxy chain.
+     * <p>
+     * Example:
+     * <p>
+     * {@code alopresto\>\<proxy1} -> {@code alopresto><proxy1}
+     *
+     * @param sanitizedDn the sanitized DN
+     * @return the original DN
+     */
+    private static String unsanitizeDn(String sanitizedDn) {
+        if (StringUtils.isEmpty(sanitizedDn)) {
+            return sanitizedDn;
+        } else {
+            String unsanitizedDn = sanitizedDn.replaceAll(ESCAPED_GT, GT).replaceAll(ESCAPED_LT, LT);
+            if (!unsanitizedDn.equals(sanitizedDn)) {
+                logger.warn("The provided DN [" + sanitizedDn + "] had been escaped, and was reconstituted to the dangerous DN [" + unsanitizedDn + "]");
+            }
+            return unsanitizedDn;
+        }
+    }
+
+    /**
+     * Tokenizes the specified proxy chain.
+     *
+     * @param rawProxyChain raw chain
+     * @return tokenized proxy chain
+     */
+    public static List<String> tokenizeProxiedEntitiesChain(String rawProxyChain) {
+        final List<String> proxyChain = new ArrayList<>();
+        if (!StringUtils.isEmpty(rawProxyChain)) {
+            // Split the String on the >< token
+            List<String> elements = Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(rawProxyChain, "><"));
+
+            // Unsanitize each DN and collect back
+            elements = elements.stream().map(ProxiedEntitiesUtils::unsanitizeDn).collect(Collectors.toList());
+
+            // Remove the leading < from the first element
+            elements.set(0, elements.get(0).replaceFirst(LT, ""));
+
+            // Remove the trailing > from the last element
+            int last = elements.size() - 1;
+            String lastElement = elements.get(last);
+            if (lastElement.endsWith(GT)) {
+                elements.set(last, lastElement.substring(0, lastElement.length() - 1));
+            }
+
+            proxyChain.addAll(elements);
+        }
+
+        return proxyChain;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/SslContextFactory.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/SslContextFactory.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/SslContextFactory.java
new file mode 100644
index 0000000..9ed8ace
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/SslContextFactory.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.security.util;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+/**
+ * A factory for creating SSL contexts using the application's security
+ * properties.
+ *
+ */
+public final class SslContextFactory {
+
+    public static enum ClientAuth {
+
+        WANT,
+        REQUIRED,
+        NONE
+    }
+
+    /**
+     * Creates a SSLContext instance using the given information. The password for the key is assumed to be the same
+     * as the password for the keystore. If this is not the case, the {@link #createSslContext(String, char[], chart[], String, String, char[], String, ClientAuth, String)}
+     * method should be used instead
+     *
+     * @param keystore the full path to the keystore
+     * @param keystorePasswd the keystore password
+     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
+     * @param truststore the full path to the truststore
+     * @param truststorePasswd the truststore password
+     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
+     * @param clientAuth the type of client authentication
+     * @param protocol         the protocol to use for the SSL connection
+     *
+     * @return a SSLContext instance
+     * @throws KeyStoreException if any issues accessing the keystore
+     * @throws IOException for any problems loading the keystores
+     * @throws NoSuchAlgorithmException if an algorithm is found to be used but is unknown
+     * @throws CertificateException if there is an issue with the certificate
+     * @throws UnrecoverableKeyException if the key is insufficient
+     * @throws KeyManagementException if unable to manage the key
+     */
+    public static SSLContext createSslContext(
+            final String keystore, final char[] keystorePasswd, final String keystoreType,
+            final String truststore, final char[] truststorePasswd, final String truststoreType,
+            final ClientAuth clientAuth, final String protocol)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
+            UnrecoverableKeyException, KeyManagementException {
+
+        // Pass the keystore password as both the keystore password and the key password.
+        return createSslContext(keystore, keystorePasswd, keystorePasswd, keystoreType, truststore, truststorePasswd, truststoreType, clientAuth, protocol);
+    }
+
+    /**
+     * Creates a SSLContext instance using the given information.
+     *
+     * @param keystore the full path to the keystore
+     * @param keystorePasswd the keystore password
+     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
+     * @param truststore the full path to the truststore
+     * @param truststorePasswd the truststore password
+     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
+     * @param clientAuth the type of client authentication
+     * @param protocol         the protocol to use for the SSL connection
+     *
+     * @return a SSLContext instance
+     * @throws KeyStoreException if any issues accessing the keystore
+     * @throws IOException for any problems loading the keystores
+     * @throws NoSuchAlgorithmException if an algorithm is found to be used but is unknown
+     * @throws CertificateException if there is an issue with the certificate
+     * @throws UnrecoverableKeyException if the key is insufficient
+     * @throws KeyManagementException if unable to manage the key
+     */
+    public static SSLContext createSslContext(
+            final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType,
+            final String truststore, final char[] truststorePasswd, final String truststoreType,
+            final ClientAuth clientAuth, final String protocol)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
+            UnrecoverableKeyException, KeyManagementException {
+
+        // prepare the keystore
+        final KeyStore keyStore = KeyStoreUtils.getKeyStore(keystoreType);
+        try (final InputStream keyStoreStream = new FileInputStream(keystore)) {
+            keyStore.load(keyStoreStream, keystorePasswd);
+        }
+        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        if (keyPasswd == null) {
+            keyManagerFactory.init(keyStore, keystorePasswd);
+        } else {
+            keyManagerFactory.init(keyStore, keyPasswd);
+        }
+
+        // prepare the truststore
+        final KeyStore trustStore = KeyStoreUtils.getTrustStore(truststoreType);
+        try (final InputStream trustStoreStream = new FileInputStream(truststore)) {
+            trustStore.load(trustStoreStream, truststorePasswd);
+        }
+        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        trustManagerFactory.init(trustStore);
+
+        // initialize the ssl context
+        final SSLContext sslContext = SSLContext.getInstance(protocol);
+        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
+        if (ClientAuth.REQUIRED == clientAuth) {
+            sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
+        } else if (ClientAuth.WANT == clientAuth) {
+            sslContext.getDefaultSSLParameters().setWantClientAuth(true);
+        } else {
+            sslContext.getDefaultSSLParameters().setWantClientAuth(false);
+        }
+
+        return sslContext;
+
+    }
+
+    /**
+     * Creates a SSLContext instance using the given information. This method assumes that the key password is
+     * the same as the keystore password. If this is not the case, use the {@link #createSslContext(String, char[], char[], String, String)}
+     * method instead.
+     *
+     * @param keystore the full path to the keystore
+     * @param keystorePasswd the keystore password
+     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
+     * @param protocol the protocol to use for the SSL connection
+     *
+     * @return a SSLContext instance
+     * @throws KeyStoreException if any issues accessing the keystore
+     * @throws IOException for any problems loading the keystores
+     * @throws NoSuchAlgorithmException if an algorithm is found to be used but is unknown
+     * @throws CertificateException if there is an issue with the certificate
+     * @throws UnrecoverableKeyException if the key is insufficient
+     * @throws KeyManagementException if unable to manage the key
+     */
+    public static SSLContext createSslContext(
+        final String keystore, final char[] keystorePasswd, final String keystoreType, final String protocol)
+        throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
+        UnrecoverableKeyException, KeyManagementException {
+
+        // create SSL Context passing keystore password as the key password
+        return createSslContext(keystore, keystorePasswd, keystorePasswd, keystoreType, protocol);
+    }
+
+    /**
+     * Creates a SSLContext instance using the given information.
+     *
+     * @param keystore the full path to the keystore
+     * @param keystorePasswd the keystore password
+     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
+     * @param protocol the protocol to use for the SSL connection
+     *
+     * @return a SSLContext instance
+     * @throws KeyStoreException if any issues accessing the keystore
+     * @throws IOException for any problems loading the keystores
+     * @throws NoSuchAlgorithmException if an algorithm is found to be used but is unknown
+     * @throws CertificateException if there is an issue with the certificate
+     * @throws UnrecoverableKeyException if the key is insufficient
+     * @throws KeyManagementException if unable to manage the key
+     */
+    public static SSLContext createSslContext(
+        final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType, final String protocol)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
+            UnrecoverableKeyException, KeyManagementException {
+
+        // prepare the keystore
+        final KeyStore keyStore = KeyStoreUtils.getKeyStore(keystoreType);
+        try (final InputStream keyStoreStream = new FileInputStream(keystore)) {
+            keyStore.load(keyStoreStream, keystorePasswd);
+        }
+        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        if (keyPasswd == null) {
+            keyManagerFactory.init(keyStore, keystorePasswd);
+        } else {
+            keyManagerFactory.init(keyStore, keyPasswd);
+        }
+
+        // initialize the ssl context
+        final SSLContext ctx = SSLContext.getInstance(protocol);
+        ctx.init(keyManagerFactory.getKeyManagers(), new TrustManager[0], new SecureRandom());
+
+        return ctx;
+
+    }
+
+    /**
+     * Creates a SSLContext instance using the given information.
+     *
+     * @param truststore the full path to the truststore
+     * @param truststorePasswd the truststore password
+     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
+     * @param protocol the protocol to use for the SSL connection
+     *
+     * @return a SSLContext instance
+     * @throws KeyStoreException if any issues accessing the keystore
+     * @throws IOException for any problems loading the keystores
+     * @throws NoSuchAlgorithmException if an algorithm is found to be used but is unknown
+     * @throws CertificateException if there is an issue with the certificate
+     * @throws UnrecoverableKeyException if the key is insufficient
+     * @throws KeyManagementException if unable to manage the key
+     */
+    public static SSLContext createTrustSslContext(
+            final String truststore, final char[] truststorePasswd, final String truststoreType, final String protocol)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
+            UnrecoverableKeyException, KeyManagementException {
+
+        // prepare the truststore
+        final KeyStore trustStore = KeyStoreUtils.getTrustStore(truststoreType);
+        try (final InputStream trustStoreStream = new FileInputStream(truststore)) {
+            trustStore.load(trustStoreStream, truststorePasswd);
+        }
+        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        trustManagerFactory.init(trustStore);
+
+        // initialize the ssl context
+        final SSLContext ctx = SSLContext.getInstance(protocol);
+        ctx.init(new KeyManager[0], trustManagerFactory.getTrustManagers(), new SecureRandom());
+
+        return ctx;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/pom.xml b/nifi-registry-core/nifi-registry-utils/pom.xml
new file mode 100644
index 0000000..99f196d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+    license agreements. See the NOTICE file distributed with this work for additional 
+    information regarding copyright ownership. The ASF licenses this file to 
+    You 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi.registry</groupId>
+        <artifactId>nifi-registry-core</artifactId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+    
+    <artifactId>nifi-registry-utils</artifactId>
+    <packaging>jar</packaging>
+    
+    <dependencies>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java
new file mode 100644
index 0000000..21aa9a7
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public enum DataUnit {
+
+    /**
+     * Bytes
+     */
+    B {
+                @Override
+                public double toB(double value) {
+                    return value;
+                }
+
+                @Override
+                public double toKB(double value) {
+                    return value / POWERS[1];
+                }
+
+                @Override
+                public double toMB(double value) {
+                    return value / POWERS[2];
+                }
+
+                @Override
+                public double toGB(double value) {
+                    return value / POWERS[3];
+                }
+
+                @Override
+                public double toTB(double value) {
+                    return value / POWERS[4];
+                }
+
+                @Override
+                public double convert(double sourceSize, DataUnit sourceUnit) {
+                    return sourceUnit.toB(sourceSize);
+                }
+            },
+    /**
+     * Kilobytes
+     */
+    KB {
+                @Override
+                public double toB(double value) {
+                    return value * POWERS[1];
+                }
+
+                @Override
+                public double toKB(double value) {
+                    return value;
+                }
+
+                @Override
+                public double toMB(double value) {
+                    return value / POWERS[1];
+                }
+
+                @Override
+                public double toGB(double value) {
+                    return value / POWERS[2];
+                }
+
+                @Override
+                public double toTB(double value) {
+                    return value / POWERS[3];
+                }
+
+                @Override
+                public double convert(double sourceSize, DataUnit sourceUnit) {
+                    return sourceUnit.toKB(sourceSize);
+                }
+            },
+    /**
+     * Megabytes
+     */
+    MB {
+                @Override
+                public double toB(double value) {
+                    return value * POWERS[2];
+                }
+
+                @Override
+                public double toKB(double value) {
+                    return value * POWERS[1];
+                }
+
+                @Override
+                public double toMB(double value) {
+                    return value;
+                }
+
+                @Override
+                public double toGB(double value) {
+                    return value / POWERS[1];
+                }
+
+                @Override
+                public double toTB(double value) {
+                    return value / POWERS[2];
+                }
+
+                @Override
+                public double convert(double sourceSize, DataUnit sourceUnit) {
+                    return sourceUnit.toMB(sourceSize);
+                }
+            },
+    /**
+     * Gigabytes
+     */
+    GB {
+                @Override
+                public double toB(double value) {
+                    return value * POWERS[3];
+                }
+
+                @Override
+                public double toKB(double value) {
+                    return value * POWERS[2];
+                }
+
+                @Override
+                public double toMB(double value) {
+                    return value * POWERS[1];
+                }
+
+                @Override
+                public double toGB(double value) {
+                    return value;
+                }
+
+                @Override
+                public double toTB(double value) {
+                    return value / POWERS[1];
+                }
+
+                @Override
+                public double convert(double sourceSize, DataUnit sourceUnit) {
+                    return sourceUnit.toGB(sourceSize);
+                }
+            },
+    /**
+     * Terabytes
+     */
+    TB {
+                @Override
+                public double toB(double value) {
+                    return value * POWERS[4];
+                }
+
+                @Override
+                public double toKB(double value) {
+                    return value * POWERS[3];
+                }
+
+                @Override
+                public double toMB(double value) {
+                    return value * POWERS[2];
+                }
+
+                @Override
+                public double toGB(double value) {
+                    return value * POWERS[1];
+                }
+
+                @Override
+                public double toTB(double value) {
+                    return value;
+                }
+
+                @Override
+                public double convert(double sourceSize, DataUnit sourceUnit) {
+                    return sourceUnit.toTB(sourceSize);
+                }
+            };
+
+    public double convert(final double sourceSize, final DataUnit sourceUnit) {
+        throw new AbstractMethodError();
+    }
+
+    public double toB(double size) {
+        throw new AbstractMethodError();
+    }
+
+    public double toKB(double size) {
+        throw new AbstractMethodError();
+    }
+
+    public double toMB(double size) {
+        throw new AbstractMethodError();
+    }
+
+    public double toGB(double size) {
+        throw new AbstractMethodError();
+    }
+
+    public double toTB(double size) {
+        throw new AbstractMethodError();
+    }
+
+    public static final double[] POWERS = {1,
+        1024D,
+        1024 * 1024D,
+        1024 * 1024 * 1024D,
+        1024 * 1024 * 1024 * 1024D};
+
+    public static final String DATA_SIZE_REGEX = "(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB|TB)";
+    public static final Pattern DATA_SIZE_PATTERN = Pattern.compile(DATA_SIZE_REGEX);
+
+    public static Double parseDataSize(final String value, final DataUnit units) {
+        if (value == null) {
+            return null;
+        }
+
+        final Matcher matcher = DATA_SIZE_PATTERN.matcher(value.toUpperCase());
+        if (!matcher.find()) {
+            throw new IllegalArgumentException("Invalid data size: " + value);
+        }
+
+        final String sizeValue = matcher.group(1);
+        final String unitValue = matcher.group(2);
+
+        final DataUnit sourceUnit = DataUnit.valueOf(unitValue);
+        final double size = Double.parseDouble(sizeValue);
+        return units.convert(size, sourceUnit);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/EscapeUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/EscapeUtils.java b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/EscapeUtils.java
new file mode 100644
index 0000000..b42538a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/EscapeUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.registry.util;
+
+public class EscapeUtils {
+
+    /**
+     * Escapes the specified html by replacing &amp;, &lt;, &gt;, &quot;, &#39;, &#x2f; with their corresponding html entity. If html is null, null is returned.
+     *
+     * @param html to escape
+     * @return escaped html
+     */
+    public static String escapeHtml(String html) {
+        if (html == null) {
+            return null;
+        }
+
+        html = html.replace("&", "&amp;");
+        html = html.replace("<", "&lt;");
+        html = html.replace(">", "&gt;");
+        html = html.replace("\"", "&quot;");
+        html = html.replace("'", "&#39;");
+        html = html.replace("/", "&#x2f;");
+
+        return html;
+    }
+}