You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2021/01/11 10:28:34 UTC

[cxf] 01/01: CXF-8402 - JwkUtils::fromECPublicKey returns key coordinates without leading zero

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch CXF-8402
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit c86dd6e98cb5ac1d8c083721950cd2de055ffc80
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Mon Jan 11 10:27:35 2021 +0000

    CXF-8402 - JwkUtils::fromECPublicKey returns key coordinates without leading zero
---
 .../src/main/appended-resources/META-INF/NOTICE    |  3 +
 .../apache/cxf/rs/security/jose/jwk/JwkUtils.java  | 67 +++++++++++++++++++++-
 .../cxf/rs/security/jose/jwk/JwkUtilsTest.java     | 15 +++++
 .../org/apache/cxf/rs/security/jose/jwk/cert.pem   |  9 +++
 4 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/distribution/src/main/appended-resources/META-INF/NOTICE b/distribution/src/main/appended-resources/META-INF/NOTICE
index 8c51d64..9d6104a 100644
--- a/distribution/src/main/appended-resources/META-INF/NOTICE
+++ b/distribution/src/main/appended-resources/META-INF/NOTICE
@@ -54,4 +54,7 @@ Software Foundation.
 Additional copyright notices and license terms applicable are
 present in the licenses directory of this distribution.
 
+This product includes code from the Nimbus JOSE + JWT project, under the Apache
+license 2.0 (https://github.com/felx/nimbus-jose-jwt). Copyright 2012-2017,
+Connect2id Ltd.
 
diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
index d86781a..0f519e3 100644
--- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
+++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
@@ -377,12 +377,14 @@ public final class JwkUtils {
     }
     public static JsonWebKey fromECPublicKey(ECPublicKey pk, String curve, String kid) {
         JsonWebKey jwk = prepareECJwk(curve, kid);
+        int fieldSize = pk.getParams().getCurve().getField().getFieldSize();
         jwk.setProperty(JsonWebKey.EC_X_COORDINATE,
-                        Base64UrlUtility.encode(pk.getW().getAffineX().toByteArray()));
+                encodeCoordinate(fieldSize, pk.getW().getAffineX()));
         jwk.setProperty(JsonWebKey.EC_Y_COORDINATE,
-                        Base64UrlUtility.encode(pk.getW().getAffineY().toByteArray()));
+                encodeCoordinate(fieldSize, pk.getW().getAffineY()));
         return jwk;
     }
+
     public static JsonWebKey fromECPrivateKey(ECPrivateKey pk, String curve) {
         return fromECPrivateKey(pk, curve, null);
     }
@@ -603,4 +605,65 @@ public final class JwkUtils {
         }
         return parsedKeys;
     }
+
+    /**
+     * Returns the Base64URL encoding of the specified elliptic curve 'x',
+     * 'y' or 'd' coordinate, with leading zero padding up to the specified
+     * field size in bits.
+     * Copied and adapted from nimbus-jose-jwt (ECKey#encodeCoordinate)
+     *
+     * @param fieldSize  The field size in bits.
+     * @param coordinate The elliptic curve coordinate. Must not be
+     *                   {@code null}.
+     *
+     * @return The Base64URL-encoded coordinate, with leading zero padding
+     *         up to the curve's field size.
+     */
+    private static String encodeCoordinate(final int fieldSize, final BigInteger coordinate) {
+        final byte[] notPadded = toIntegerBytes(coordinate);
+        int bytesToOutput = (fieldSize + 7) / 8;
+
+        if (notPadded.length >= bytesToOutput) {
+            // Greater-than check to prevent exception on malformed
+            // key below
+            return Base64UrlUtility.encode(notPadded);
+        }
+
+        final byte[] padded = new byte[bytesToOutput];
+        System.arraycopy(notPadded, 0, padded, bytesToOutput - notPadded.length, notPadded.length);
+        return Base64UrlUtility.encode(padded);
+    }
+
+    /**
+     * Returns a byte-array representation of a {@code BigInteger} without sign bit.
+     * Copied from Apache Commons Codec
+     *
+     * @param bigInt
+     *            {@code BigInteger} to be converted
+     * @return a byte array representation of the BigInteger parameter
+     */
+    private static byte[] toIntegerBytes(final BigInteger bigInt) {
+
+        int bitlen = bigInt.bitLength();
+        // round bitlen
+        bitlen = ((bitlen + 7) >> 3) << 3;
+        final byte[] bigBytes = bigInt.toByteArray();
+
+        if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
+            return bigBytes;
+        }
+        // set up params for copying everything but sign bit
+        int startSrc = 0;
+        int len = bigBytes.length;
+
+        // if bigInt is exactly byte-aligned, just skip signbit in copy
+        if ((bigInt.bitLength() % 8) == 0) {
+            startSrc = 1;
+            len--;
+        }
+        final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
+        final byte[] resizedBytes = new byte[bitlen / 8];
+        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
+        return resizedBytes;
+    }
 }
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java
index 2c0bb14..fa38233 100644
--- a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java
@@ -18,7 +18,10 @@
  */
 package org.apache.cxf.rs.security.jose.jwk;
 
+import java.io.InputStream;
 import java.math.BigInteger;
+import java.security.cert.CertificateFactory;
+import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
 import java.util.Properties;
@@ -203,4 +206,16 @@ public class JwkUtilsTest {
         }
     }
 
+    @Test
+    public void testEcLeadingZeros() throws Exception {
+        try (InputStream inputStream = this.getClass().getResourceAsStream("cert.pem")) {
+            ECPublicKey publicKey = (ECPublicKey) CertificateFactory.getInstance("X.509")
+                    .generateCertificate(inputStream).getPublicKey();
+            JsonWebKey jwk = JwkUtils.fromECPublicKey(publicKey, "P-256");
+            String x = (String)jwk.getProperty(JsonWebKey.EC_X_COORDINATE);
+            String y = (String)jwk.getProperty(JsonWebKey.EC_Y_COORDINATE);
+            assertEquals(x.length(), y.length());
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/rt/rs/security/jose-parent/jose/src/test/resources/org/apache/cxf/rs/security/jose/jwk/cert.pem b/rt/rs/security/jose-parent/jose/src/test/resources/org/apache/cxf/rs/security/jose/jwk/cert.pem
new file mode 100644
index 0000000..801791d
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/resources/org/apache/cxf/rs/security/jose/jwk/cert.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBPTCB5AIJAJumvtnyaTCYMAoGCCqGSM49BAMCMCcxCzAJBgNVBAMMAnh4MQsw
+CQYDVQQKDAJ4eDELMAkGA1UEBhMCWFgwHhcNMjEwMTA3MDgyOTEwWhcNMjIwMTA3
+MDgyOTEwWjAnMQswCQYDVQQDDAJ4eDELMAkGA1UECgwCeHgxCzAJBgNVBAYTAlhY
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAAiurwVih3Bjfd1SUJsZNu4WxzXu
+HRyaXMubdTS+m6gtBlLPT5l2c4cgU3YvH0klTvgW4rXxQkU439myx9epDTAKBggq
+hkjOPQQDAgNIADBFAiEA+ED92OzKrofQFX/f6bX75CNZox8GH/GPJJT8dJUl6ioC
+IFv05HFlcU6+tUK7CKFz/1POx1f2IAySFmSbhH1qF2ua
+-----END CERTIFICATE-----