[java-opensaml] branch main updated: Enhance DEREncodedKeyValue processing by parsing the ASN.1 encoded form
Brent Putman
putmanb at georgetown.edu
Thu Mar 11 20:13:19 UTC 2021
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch main
in repository java-opensaml.
View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=3a8aaf6fa9878535d5e937ad7699b4b9eaedac8c
The following commit(s) were added to refs/heads/main by this push:
new 3a8aaf6fa Enhance DEREncodedKeyValue processing by parsing the ASN.1 encoded form
3a8aaf6fa is described below
commit 3a8aaf6fa9878535d5e937ad7699b4b9eaedac8c
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Mar 11 14:20:27 2021 -0500
Enhance DEREncodedKeyValue processing by parsing the ASN.1 encoded form
Parse the OID and map to the associated JCA key algorithm directly.
If this fails, then fallback to the previous methodology of trying all
key algos in order until one appears to succeed.
---
.../opensaml/xmlsec/keyinfo/KeyInfoSupport.java | 64 ++++++++++++++-
.../xmlsec/keyinfo/tests/KeyInfoSupportTest.java | 91 ++++++++++++++++++++++
2 files changed, 151 insertions(+), 4 deletions(-)
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java
index f7624d1a9..d07f232d4 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java
@@ -51,6 +51,8 @@ import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHPublicKeySpec;
import org.apache.xml.security.utils.XMLUtils;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.opensaml.core.xml.XMLObjectBuilder;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
@@ -1062,8 +1064,9 @@ public class KeyInfoSupport {
@Nonnull public static PublicKey getKey(@Nonnull final DEREncodedKeyValue keyValue) throws KeyException{
// Note: Testing shows DH must come before DSA. If you attempt to decode a DH key as DSA,
// it "works", b/c they have similar structures, but it's probably not correct. DSA does not decode as DH,
- // so this ordering is what works at present. If this methodology has a problem in the future, we'll likely
- // need to switch to explicit ASN.1 parsing of the key's type, instead of "try and return what doesn't fail".
+ // so this ordering is what works at present.
+ // This is now only relevant if the direct parsing of the key type below fails, and we fallback to
+ // "try everything and pick the first that doesn't fail" approach.
final String[] supportedKeyTypes = {
JCAConstants.KEY_ALGO_RSA,
JCAConstants.KEY_ALGO_EC,
@@ -1081,8 +1084,11 @@ public class KeyInfoSupport {
throw new KeyException("DEREncodedKeyValue could not be base64 decoded",e);
}
- // Iterate over the supported key types until one produces a public key.
- for (final String keyType : supportedKeyTypes) {
+ final String parsedKeyType = parseKeyType(encodedKey);
+
+ final String[] keyTypes = parsedKeyType != null ? new String[]{parsedKeyType} : supportedKeyTypes;
+
+ for (final String keyType : keyTypes) {
getLogger().trace("Attempting to decode DER key as type: {}", keyType);
try {
final KeyFactory keyFactory = KeyFactory.getInstance(keyType);
@@ -1098,6 +1104,56 @@ public class KeyInfoSupport {
}
throw new KeyException("DEREncodedKeyValue did not contain a supported key type");
}
+
+ /**
+ * Parse the JCA key algorithm type from the ASN.1 encoded form of the public key.
+ *
+ * <p>
+ * Methodology is to parse the ASN.1 data to the <code>SubjectPublicKeyInfo</code>, read the
+ * <code>AlgorithmIdentifier</code> for the key type's OID, then map the OID to the JCA
+ * key algorithm.
+ * </p>
+ *
+ * @param encodedKey the ASN.1 encoded key
+ *
+ * @return the JCA key algorithm, or null if the OID parsing or OID-to-algorithm mapping fails
+ */
+ private static String parseKeyType(@Nonnull final byte[] encodedKey) {
+ try (final ASN1InputStream input = new ASN1InputStream(encodedKey)) {
+ final SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(input.readObject());
+ final String keyTypeOID = spki.getAlgorithm().getAlgorithm().getId();
+ getLogger().debug("Parsed key type OID: {}", keyTypeOID);
+
+ String parsedKeyType = null;
+ switch(keyTypeOID) {
+ case "1.2.840.113549.1.1.1":
+ parsedKeyType = JCAConstants.KEY_ALGO_RSA;
+ break;
+ case "1.2.840.10045.2.1":
+ parsedKeyType = JCAConstants.KEY_ALGO_EC;
+ break;
+ case "1.2.840.10040.4.1":
+ parsedKeyType = JCAConstants.KEY_ALGO_DSA;
+ break;
+ // There are apparently 2 OIDS in use for DH public keys.
+ // This is what's defined in the specs for DH keys. See RFC 3279, section 2.3.3.
+ case "1.2.840.10046.2.1":
+ // This is what's defined in the specs for DH key agreement. See PKCS #3.
+ // Which sounds wrong but: It's the one returned by Java KeyPairGenerator, etc
+ case "1.2.840.113549.1.3.1":
+ parsedKeyType = JCAConstants.KEY_ALGO_DIFFIE_HELLMAN;
+ break;
+ default:
+ parsedKeyType = null;
+ }
+
+ getLogger().debug("Parsed key type: {}", parsedKeyType);
+ return parsedKeyType;
+ } catch (final Exception e) {
+ getLogger().warn("Error parsing encoded key, can not determine key type", e);
+ return null;
+ }
+ }
/**
* Get the Java certificate factory singleton.
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java
index 440344917..be0db14c9 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java
@@ -907,6 +907,97 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
keyInfo.getDEREncodedKeyValues().clear();
}
+ /** Tests adding a public key as a DEREncodedKeyValue to KeyInfo. */
+ @Test
+ public void testAddDEREncodedECPublicKeyWithNamedCurve() {
+ keyInfo.getXMLObjects(DEREncodedKeyValue.DEFAULT_ELEMENT_NAME).clear();
+
+ try {
+ KeyInfoSupport.addDEREncodedPublicKey(keyInfo, javaECPubKey_NamedCurve1);
+ } catch (NoSuchAlgorithmException e) {
+ Assert.fail("Unsupported key algorithm: " + e);
+ } catch (InvalidKeySpecException e) {
+ Assert.fail("Unsupported key specification: " + e);
+ }
+ DEREncodedKeyValue kv = keyInfo.getDEREncodedKeyValues().get(0);
+ Assert.assertNotNull(kv, "DEREncodedKeyValue was null");
+
+ ECPublicKey javaKey = null;
+ try {
+ javaKey = (ECPublicKey) KeyInfoSupport.getKey(kv);
+ } catch (KeyException e) {
+ Assert.fail("Extraction of Java key failed: " + e);
+ }
+
+ Assert.assertEquals(javaECPubKey_NamedCurve1, javaKey, "Inserted EC public key was not the expected value");
+
+ keyInfo.getDEREncodedKeyValues().clear();
+ }
+
+ /** Tests adding a public key as a DEREncodedKeyValue to KeyInfo. */
+ @Test
+ public void testAddDEREncodedECPublicKeyWithExplictParams() {
+ keyInfo.getXMLObjects(DEREncodedKeyValue.DEFAULT_ELEMENT_NAME).clear();
+
+ try {
+ // As of this writing SunEC provider doesn't support explicit params,
+ // and so the Java ECPublicKeys wind up not being equal below. So for this test only,
+ // register BC as the preferred provider, and unregister at the end.
+ // (Note: Provider positions are 1-based, not 0-based).
+ Security.insertProviderAt(new BouncyCastleProvider(), 1);
+
+ try {
+ KeyInfoSupport.addDEREncodedPublicKey(keyInfo, javaECPubKey_ExplicitParams1);
+ } catch (NoSuchAlgorithmException e) {
+ Assert.fail("Unsupported key algorithm: " + e);
+ } catch (InvalidKeySpecException e) {
+ Assert.fail("Unsupported key specification: " + e);
+ }
+ DEREncodedKeyValue kv = keyInfo.getDEREncodedKeyValues().get(0);
+ Assert.assertNotNull(kv, "DEREncodedKeyValue was null");
+
+ ECPublicKey javaKey = null;
+ try {
+ javaKey = (ECPublicKey) KeyInfoSupport.getKey(kv);
+ } catch (KeyException e) {
+ Assert.fail("Extraction of Java key failed: " + e);
+ }
+
+ Assert.assertEquals(javaECPubKey_ExplicitParams1, javaKey, "Inserted EC public key was not the expected value");
+ } finally {
+ Security.removeProvider("BC");
+ }
+
+ keyInfo.getDEREncodedKeyValues().clear();
+ }
+
+ /** Tests adding a public key as a DEREncodedKeyValue to KeyInfo. */
+ @Test
+ public void testAddDEREncodedDHPublicKey() {
+ keyInfo.getXMLObjects(DEREncodedKeyValue.DEFAULT_ELEMENT_NAME).clear();
+
+ try {
+ KeyInfoSupport.addDEREncodedPublicKey(keyInfo, javaDHPubKey1);
+ } catch (NoSuchAlgorithmException e) {
+ Assert.fail("Unsupported key algorithm: " + e);
+ } catch (InvalidKeySpecException e) {
+ Assert.fail("Unsupported key specification: " + e);
+ }
+ DEREncodedKeyValue kv = keyInfo.getDEREncodedKeyValues().get(0);
+ Assert.assertNotNull(kv, "DEREncodedKeyValue was null");
+
+ DHPublicKey javaKey = null;
+ try {
+ javaKey = (DHPublicKey) KeyInfoSupport.getKey(kv);
+ } catch (KeyException e) {
+ Assert.fail("Extraction of Java key failed: " + e);
+ }
+
+ Assert.assertEquals(javaDHPubKey1, javaKey, "Inserted DH public key was not the expected value");
+
+ keyInfo.getDEREncodedKeyValues().clear();
+ }
+
/**
* Tests adding a certificate as a X509Data/X509Certificate to KeyInfo.
*
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list