[java-opensaml] 17/19: Add support for auto registering all NamedCurves known to Bouncy Castle.

Brent Putman putmanb at georgetown.edu
Mon Mar 1 05:56:54 UTC 2021


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch dev/OSJ-82
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=029e9b8c82457490e4a05bfc1bb895793d295541

commit 029e9b8c82457490e4a05bfc1bb895793d295541
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Feb 10 22:48:30 2021 -0500

    Add support for auto registering all NamedCurves known to Bouncy Castle.
---
 .../GlobalNamedCurveRegistryInitializer.java       | 36 +++++++++-
 .../org/opensaml/security/crypto/ec/ECSupport.java | 47 +++++++++++-
 .../security/crypto/ec/curves/BasicNamedCurve.java | 84 ++++++++++++++++++++++
 .../opensaml/security/crypto/ec/ECSupportTest.java |  8 +++
 .../security/crypto/ec/NamedCurveRegistryTest.java | 30 ++++++++
 5 files changed, 201 insertions(+), 4 deletions(-)

diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/config/GlobalNamedCurveRegistryInitializer.java b/opensaml-security-api/src/main/java/org/opensaml/security/config/GlobalNamedCurveRegistryInitializer.java
index e4eac5caa..31edfbdc7 100644
--- a/opensaml-security-api/src/main/java/org/opensaml/security/config/GlobalNamedCurveRegistryInitializer.java
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/config/GlobalNamedCurveRegistryInitializer.java
@@ -18,11 +18,15 @@
 package org.opensaml.security.config;
 
 import java.util.Iterator;
+import java.util.Properties;
 import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.opensaml.core.config.ConfigurationService;
 import org.opensaml.core.config.InitializationException;
 import org.opensaml.core.config.Initializer;
+import org.opensaml.security.crypto.ec.ECSupport;
 import org.opensaml.security.crypto.ec.NamedCurve;
 import org.opensaml.security.crypto.ec.NamedCurveRegistry;
 import org.slf4j.Logger;
@@ -36,6 +40,10 @@ import net.shibboleth.utilities.java.support.component.InitializableComponent;
  */
 public class GlobalNamedCurveRegistryInitializer implements Initializer {
     
+    /** Configuration property name for registering curves from Bouncy Castle. */
+    public static final String CONFIG_PROPERTY_REGISTER_BOUNCY_CASTLE_CURVES =
+            "opensaml.config.ec.registerBouncyCastleCurves";
+    
     /** Logger. */
     private Logger log = LoggerFactory.getLogger(GlobalNamedCurveRegistryInitializer.class);
 
@@ -52,15 +60,39 @@ public class GlobalNamedCurveRegistryInitializer implements Initializer {
                     InitializableComponent.class.cast(curve).initialize();
                 }
             } catch (final ComponentInitializationException e) {
-                log.warn("Error initing NamedCurve with name '{}', OID '{}', URI '{}': {}",
+                log.warn("Error initing NamedCurve, name '{}', OID '{}', URI '{}': {}",
                         curve.getName(), curve.getObjectIdentifier(), curve.getURI(), curve.getClass().getName());
                 continue;
             }
-            log.debug("Registering NamedCurve with name '{}', OID '{}' and name '{}': {}'",
+            log.debug("Registering NamedCurve, name '{}', OID '{}', URI '{}': {}'",
                     curve.getName(), curve.getObjectIdentifier(), curve.getURI(), curve.getClass().getName());
             registry.register(curve);
         }
         
+        final Properties props = ConfigurationService.getConfigurationProperties(); 
+        final boolean registerBCCurves =
+                (props != null) ? Boolean.parseBoolean(props.getProperty(CONFIG_PROPERTY_REGISTER_BOUNCY_CASTLE_CURVES))
+                        : false;
+        
+        if (registerBCCurves) {
+            // Don't register if already done above. Use the OID as the canonical unique identifier.  Names may differ.
+            final Set<String> oids = registry.getRegisteredCurves().stream()
+                    .map(NamedCurve::getObjectIdentifier)
+                    .collect(Collectors.toSet());
+            
+            final Set<NamedCurve> curves = ECSupport.getCurvesFromBouncyCastle();
+            for (final NamedCurve curve : curves) {
+                if (!oids.contains(curve.getObjectIdentifier())) {
+                    log.debug("Registering BC NamedCurve, name '{}', OID '{}', URI '{}': {}'",
+                            curve.getName(), curve.getObjectIdentifier(), curve.getURI(), curve.getClass().getName());
+                    registry.register(curve);
+                } else {
+                    log.debug("Skipping BC NamedCurve because already registered, name '{}', OID '{}', URI '{}': {}'",
+                            curve.getName(), curve.getObjectIdentifier(), curve.getURI(), curve.getClass().getName());
+                }
+            }
+        }
+        
         ConfigurationService.register(NamedCurveRegistry.class, registry);
     }
 
diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/ECSupport.java b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/ECSupport.java
index 30c7fbebb..e36da818f 100644
--- a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/ECSupport.java
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/ECSupport.java
@@ -30,6 +30,9 @@ import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
 import java.security.spec.EllipticCurve;
 import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -41,9 +44,12 @@ import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 import org.opensaml.core.config.ConfigurationService;
 import org.opensaml.security.crypto.JCAConstants;
 import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.security.crypto.ec.curves.BasicNamedCurve;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
@@ -164,7 +170,6 @@ public final class ECSupport {
     @Nullable public static String getNamedCurveURI(@Nonnull final ECPublicKey publicKey) {
         Constraint.isNotNull(publicKey, "ECPublicKey was null");
         
-        //TODO implement fallback to do nasty parsing of the ASN.1 encoded form for the OID?
         final NamedCurve namedCurve = getNamedCurve(publicKey);
         if (namedCurve == null) {
             LOG.warn("Could not resolve NamedCurve for ECPublicKey");
@@ -185,7 +190,6 @@ public final class ECSupport {
     @Nullable public static ECParameterSpec getParameterSpecForURI(@Nonnull final String uri) {
         Constraint.isNotNull(uri, "NamedCurve URI was null");
         
-        //TODO implement fallback to use BCNamedCurveTable to lookup up OID -> param spec?
         final NamedCurve namedCurve = getNamedCurve(uri);
         if (namedCurve == null) {
             LOG.warn("Could not resolve NamedCurve for URI: {}", uri);
@@ -302,5 +306,44 @@ public final class ECSupport {
                 bcSpec.getN(),
                 bcSpec.getH().intValue());
     }
+    
+    /**
+     * Return a set of all curves known to Bouncy Castle as instances of {@link NamedCurve}.
+     * 
+     * @return the set of curves known to Bouncy Castle
+     */
+    @Nonnull @NonnullElements @NotLive
+    public static Set<NamedCurve> getCurvesFromBouncyCastle() {
+        final HashSet<NamedCurve> curves = new HashSet<>();
+        
+        // There seems to be duplication between the main and custom curve tables, so use OID to only find unique ones.
+        final HashSet<String> oids = new HashSet<>();
+        
+        final Enumeration<String> standardNames = org.bouncycastle.asn1.x9.ECNamedCurveTable.getNames();
+        while (standardNames.hasMoreElements()) {
+            final String name = standardNames.nextElement();
+            final String oid = org.bouncycastle.asn1.x9.ECNamedCurveTable.getOID(name).getId();
+            if (!oids.contains(oid)) {
+                final ECParameterSpec paramSpec =
+                        EC5Util.convertToSpec(org.bouncycastle.asn1.x9.ECNamedCurveTable.getByName(name));
+                curves.add(new BasicNamedCurve(oid, name, paramSpec));
+                oids.add(oid);
+            }
+        }
+        
+        final Enumeration<String> customNames = org.bouncycastle.crypto.ec.CustomNamedCurves.getNames();
+        while (customNames.hasMoreElements()) {
+            final String name = customNames.nextElement();
+            final String oid = org.bouncycastle.crypto.ec.CustomNamedCurves.getOID(name).getId();
+            if (!oids.contains(oid)) {
+                final ECParameterSpec paramSpec =
+                        EC5Util.convertToSpec(org.bouncycastle.crypto.ec.CustomNamedCurves.getByName(name));
+                curves.add(new BasicNamedCurve(oid, name, paramSpec));
+                oids.add(oid);
+            }
+        }
+        
+        return curves;
+    }
 
 } 
\ No newline at end of file
diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/curves/BasicNamedCurve.java b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/curves/BasicNamedCurve.java
new file mode 100644
index 000000000..09b534a6b
--- /dev/null
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/ec/curves/BasicNamedCurve.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.security.crypto.ec.curves;
+
+import java.security.spec.ECParameterSpec;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.crypto.ec.NamedCurve;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Basic implementations of {@link NamedCurve} where all required properties are simply supplied at
+ * construction time.
+ */
+public class BasicNamedCurve implements NamedCurve {
+    
+    /** Curve name. */
+    @Nonnull private String name;
+    
+    /** Curve OID. */
+    @Nonnull private String oid;
+    
+    /** Curve's parameters as an instance of {@link ECParameterSpec}. */
+    @Nonnull private ECParameterSpec params;
+
+    /**
+     * Constructor.
+     *
+     * @param objectIdentifier the curve's object identifier (OID)
+     * @param standardName the curve's standard name
+     * @param parameters the curve's parameters as an {@link ECParameterSpec}
+     */
+    public BasicNamedCurve(@Nonnull final String objectIdentifier, @Nonnull final String standardName,
+            @Nonnull final ECParameterSpec parameters) {
+        oid = Constraint.isNotNull(StringSupport.trimOrNull(objectIdentifier), "Curve identifier was null");
+        name = Constraint.isNotNull(StringSupport.trimOrNull(standardName), "Curve name was null");
+        params = Constraint.isNotNull(parameters, "Curve parameters was null");
+    }
+
+    /** {@inheritDoc} */
+    public String getObjectIdentifier() {
+        return oid;
+    }
+
+    /** {@inheritDoc} */
+    public String getName() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    public ECParameterSpec getParameterSpec() {
+        return params;
+    }
+    
+    /** {@inheritDoc} */
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("name", getName())
+                .add("OID", getObjectIdentifier())
+                .toString();
+    }
+    
+}
+
diff --git a/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/ECSupportTest.java b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/ECSupportTest.java
index b7dc1cedd..82d0d564c 100644
--- a/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/ECSupportTest.java
+++ b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/ECSupportTest.java
@@ -25,6 +25,7 @@ import java.security.interfaces.ECPublicKey;
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
+import java.util.Set;
 
 import org.bouncycastle.jce.ECNamedCurveTable;
 import org.opensaml.security.crypto.JCAConstants;
@@ -107,4 +108,11 @@ public class ECSupportTest extends BaseNamedCurveTest {
         Assert.assertNotNull(decoded);
         Assert.assertEquals(decoded, spec.getGenerator());
     }
+    
+    @Test
+    public void getCurvesFromBouncyCastle() {
+        Set<NamedCurve> curves = ECSupport.getCurvesFromBouncyCastle();
+        Assert.assertNotNull(curves);
+        Assert.assertFalse(curves.isEmpty());
+    }
 }
diff --git a/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/NamedCurveRegistryTest.java b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/NamedCurveRegistryTest.java
index 6ac04dead..3731e8497 100644
--- a/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/NamedCurveRegistryTest.java
+++ b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/ec/NamedCurveRegistryTest.java
@@ -18,10 +18,14 @@
 package org.opensaml.security.crypto.ec;
 
 import java.security.spec.ECParameterSpec;
+import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.bouncycastle.jce.ECNamedCurveTable;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.config.provider.ThreadLocalConfigurationPropertiesHolder;
+import org.opensaml.security.config.GlobalNamedCurveRegistryInitializer;
 import org.opensaml.security.crypto.ec.curves.Secp256r1;
 import org.opensaml.security.crypto.ec.curves.Secp384r1;
 import org.opensaml.security.crypto.ec.curves.Secp521r1;
@@ -159,5 +163,31 @@ public class NamedCurveRegistryTest extends BaseNamedCurveTest {
         Assert.assertTrue(curveNames.contains("secp384r1"));
         Assert.assertTrue(curveNames.contains("secp521r1"));
     }
+    
+    @Test
+    public void registerBouncyCastleCurves() throws Exception {
+        String propName = GlobalNamedCurveRegistryInitializer.CONFIG_PROPERTY_REGISTER_BOUNCY_CASTLE_CURVES;
+        NamedCurveRegistry origRegistry = ECSupport.getGlobalNamedCurveRegistry();
+        try {
+            Properties props = new Properties();
+            props.setProperty(propName, "true");
+            ThreadLocalConfigurationPropertiesHolder.setProperties(props);
+            
+            new GlobalNamedCurveRegistryInitializer().init();
+            NamedCurveRegistry bcRegistry = ECSupport.getGlobalNamedCurveRegistry();
+            Assert.assertNotSame(bcRegistry, origRegistry);
+            
+            Set<NamedCurve> origCurves = origRegistry.getRegisteredCurves();
+            Set<NamedCurve> bcCurves = bcRegistry.getRegisteredCurves();
+            Assert.assertTrue(bcCurves.size() > origCurves.size());
+            
+            Set<String> origOIDs = origCurves.stream().map(NamedCurve::getObjectIdentifier).collect(Collectors.toSet());
+            Set<String> bcOIDs = bcCurves.stream().map(NamedCurve::getObjectIdentifier).collect(Collectors.toSet());
+            Assert.assertTrue(bcOIDs.containsAll(origOIDs));
+        } finally {
+            ThreadLocalConfigurationPropertiesHolder.clear();
+            ConfigurationService.register(NamedCurveRegistry.class, origRegistry);
+        }
+    }
 
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list