Repository: nifi
Updated Branches:
  refs/heads/master 6e82ec738 -> 23350543f


NIFI-2621 - Generating unique serial numbers for certificates

This closes #909.

Signed-off-by: Andy LoPresto <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/23350543
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/23350543
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/23350543

Branch: refs/heads/master
Commit: 23350543ff638a198a35ae04daac8498f4e9651e
Parents: 6e82ec7
Author: Bryan Rosander <[email protected]>
Authored: Mon Aug 22 12:57:37 2016 -0400
Committer: Andy LoPresto <[email protected]>
Committed: Tue Aug 23 01:37:25 2016 -0700

----------------------------------------------------------------------
 .../nifi/security/util/CertificateUtils.java    | 43 +++++++++++++--
 .../security/util/CertificateUtilsTest.groovy   | 55 ++++++++++++++++++++
 2 files changed, 95 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/23350543/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
 
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index 760e82e..df239fa 100644
--- 
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ 
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -74,9 +74,23 @@ 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();
 
+    /**
+     * 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;
@@ -439,6 +453,29 @@ public final class CertificateUtils {
     }
 
     /**
+     * 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
@@ -458,7 +495,7 @@ public final class CertificateUtils {
 
             X509v3CertificateBuilder certBuilder = new 
X509v3CertificateBuilder(
                     reverseX500Name(new X500Name(dn)),
-                    BigInteger.valueOf(System.currentTimeMillis()),
+                    getUniqueSerialNumber(),
                     startDate, endDate,
                     reverseX500Name(new X500Name(dn)),
                     subPubKeyInfo);
@@ -507,7 +544,7 @@ public final class CertificateUtils {
 
             X509v3CertificateBuilder certBuilder = new 
X509v3CertificateBuilder(
                     reverseX500Name(new 
X500Name(issuer.getSubjectX500Principal().getName())),
-                    BigInteger.valueOf(System.currentTimeMillis()),
+                    getUniqueSerialNumber(),
                     startDate, endDate,
                     reverseX500Name(new X500Name(dn)),
                     subPubKeyInfo);

http://git-wip-us.apache.org/repos/asf/nifi/blob/23350543/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
 
b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
index 6727364..47ac918 100644
--- 
a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
+++ 
b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
@@ -40,9 +40,16 @@ import java.security.SignatureException
 import java.security.cert.Certificate
 import java.security.cert.CertificateException
 import java.security.cert.X509Certificate
+import java.util.concurrent.Callable
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executors
+import java.util.concurrent.Future
 import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
 
 import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
 
 @RunWith(JUnit4.class)
 class CertificateUtilsTest extends GroovyTestCase {
@@ -497,4 +504,52 @@ class CertificateUtilsTest extends GroovyTestCase {
         
assertEquals("$cn,$l,$st,$o,$ou,$c,$street,$dc,$uid,$surname,$givenName,$initials".toString(),
                 
CertificateUtils.reorderDn("$surname,$st,$o,$initials,$givenName,$uid,$street,$c,$cn,$ou,$l,$dc"));
     }
+
+    @Test
+    public void testUniqueSerialNumbers() {
+        def running = new AtomicBoolean(true);
+        def executorService = Executors.newCachedThreadPool()
+        def serialNumbers = Collections.newSetFromMap(new ConcurrentHashMap())
+        try {
+            def futures = new ArrayList<Future>()
+            for (int i = 0; i < 8; i++) {
+                futures.add(executorService.submit(new Callable<Integer>() {
+                    @Override
+                    Integer call() throws Exception {
+                        int count = 0;
+                        while (running.get()) {
+                            def before = System.currentTimeMillis()
+                            def serialNumber = 
CertificateUtils.getUniqueSerialNumber()
+                            def after = System.currentTimeMillis()
+                            def serialNumberMillis = 
serialNumber.shiftRight(32)
+                            assertTrue(serialNumberMillis >= before)
+                            assertTrue(serialNumberMillis <= after)
+                            assertTrue(serialNumbers.add(serialNumber))
+                            count++;
+                        }
+                        return count;
+                    }
+                }));
+            }
+
+            Thread.sleep(1000)
+
+            running.set(false)
+
+            def totalRuns = 0;
+            for (int i = 0; i < futures.size(); i++) {
+                try {
+                    def numTimes = futures.get(i).get()
+                    logger.info("future $i executed $numTimes times")
+                    totalRuns += numTimes;
+                } catch (ExecutionException e) {
+                    throw e.getCause()
+                }
+            }
+            logger.info("Generated ${serialNumbers.size()} unique serial 
numbers")
+            assertEquals(totalRuns, serialNumbers.size())
+        } finally {
+            executorService.shutdown()
+        }
+    }
 }

Reply via email to