From 98da41ff60f187be6e7906d61886410c4d565071 Mon Sep 17 00:00:00 2001 From: CGantert345 <57003061+CGantert345@users.noreply.github.com> Date: Fri, 20 May 2022 13:50:53 +0200 Subject: experimental implementation of Elliptic Curve Public Key Encoding with X9.62 compressed and uncompressed --- src/main/java/org/uic/barcode/Encoder.java | 25 ++ .../dynamicFrame/api/SimpleDynamicFrame.java | 10 +- .../java/org/uic/barcode/utils/ECKeyEncoder.java | 284 +++++++++++++++++++++ .../org/uic/barcode/utils/EllipticCurveNames.java | 67 +++++ .../java/org/uic/barcode/utils/SecurityUtils.java | 29 +-- 5 files changed, 383 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/uic/barcode/utils/ECKeyEncoder.java create mode 100644 src/main/java/org/uic/barcode/utils/EllipticCurveNames.java (limited to 'src/main/java/org') diff --git a/src/main/java/org/uic/barcode/Encoder.java b/src/main/java/org/uic/barcode/Encoder.java index 9afddce..b01ca14 100644 --- a/src/main/java/org/uic/barcode/Encoder.java +++ b/src/main/java/org/uic/barcode/Encoder.java @@ -27,6 +27,7 @@ import org.uic.barcode.staticFrame.ticketLayoutBarcode.TicketLayout; import org.uic.barcode.ticket.EncodingFormatException; import org.uic.barcode.ticket.UicRailTicketCoder; import org.uic.barcode.ticket.api.spec.IUicRailTicket; +import org.uic.barcode.utils.ECKeyEncoder; /** @@ -278,6 +279,30 @@ public class Encoder { } } + /** + * Sets the level 2 algorithm Is. + * + * @param level2SigningAlg the level 2 signing algorithm (OID) + * @param level2KeyAlg the level 2 key algorithm (OID) + * @param publicKey the public key of the level 2 signature + * @param publicKeyEncodingFormat "X509", for elliptic curve keys only: "X962_UNCOMPRESSED", "X962_COMPRESSED" constants defined in class ECKeyEncoder. + **/ + public void setLevel2Algs(String level2SigningAlg, String level2KeyAlg, PublicKey publicKey, String publicKeyEncodingFormat) { + if (dynamicFrame != null) { + if (dynamicFrame.getLevel2Data() == null) { + dynamicFrame.setLevel2Data(new SimpleLevel2Data()); + } + if (dynamicFrame.getLevel2Data().getLevel1Data() == null) { + dynamicFrame.getLevel2Data().setLevel1Data(new SimpleLevel1Data()); + } + dynamicFrame.getLevel2Data().getLevel1Data().setLevel2SigningAlg(level2SigningAlg); + dynamicFrame.getLevel2Data().getLevel1Data().setLevel2KeyAlg(level2KeyAlg); + if (publicKey != null) { + dynamicFrame.getLevel2Data().getLevel1Data().setLevel2publicKey(ECKeyEncoder.getEncoded(publicKey, publicKeyEncodingFormat)); + } + } + } + public void setDynamicData(IUicDynamicContent content) throws EncodingFormatException { if (dynamicFrame != null) { if (dynamicFrame.getLevel2Data() == null) { diff --git a/src/main/java/org/uic/barcode/dynamicFrame/api/SimpleDynamicFrame.java b/src/main/java/org/uic/barcode/dynamicFrame/api/SimpleDynamicFrame.java index ae1b4e2..15f169b 100644 --- a/src/main/java/org/uic/barcode/dynamicFrame/api/SimpleDynamicFrame.java +++ b/src/main/java/org/uic/barcode/dynamicFrame/api/SimpleDynamicFrame.java @@ -8,8 +8,6 @@ import java.security.Provider; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.util.Date; import org.uic.barcode.dynamicContent.api.DynamicContentCoder; @@ -20,6 +18,7 @@ import org.uic.barcode.dynamicFrame.v1.DynamicFrameCoderV1; import org.uic.barcode.dynamicFrame.v2.DynamicFrameCoderV2; import org.uic.barcode.ticket.EncodingFormatException; import org.uic.barcode.utils.AlgorithmNameResolver; +import org.uic.barcode.utils.ECKeyEncoder; import org.uic.barcode.utils.SecurityUtils; @@ -180,15 +179,12 @@ public class SimpleDynamicFrame implements IDynamicFrame { } KeyFactory keyFactory = KeyFactory.getInstance(keyAlgName,provider); if (keyFactory != null) { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - key = keyFactory.generatePublic(keySpec); + key = ECKeyEncoder.fromEncoded(keyBytes,level2KeyAlg, provider); } else { return Constants.LEVEL2_VALIDATION_KEY_ALG_NOT_IMPLEMENTED; } - } catch (InvalidKeySpecException e1) { - return Constants.LEVEL2_VALIDATION_KEY_ALG_NOT_IMPLEMENTED; - } catch (NoSuchAlgorithmException e1) { + } catch (Exception e1) { return Constants.LEVEL2_VALIDATION_KEY_ALG_NOT_IMPLEMENTED; } diff --git a/src/main/java/org/uic/barcode/utils/ECKeyEncoder.java b/src/main/java/org/uic/barcode/utils/ECKeyEncoder.java new file mode 100644 index 0000000..96038d3 --- /dev/null +++ b/src/main/java/org/uic/barcode/utils/ECKeyEncoder.java @@ -0,0 +1,284 @@ +package org.uic.barcode.utils; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.KeyFactory; +import java.security.Provider; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + + +public class ECKeyEncoder { + + + private static final byte X962_UNCOMPRESSED_POINT_INDICATOR = 0x04; + private static final byte X962_ODD = 0x02; + private static final byte X962_EVEN = 0x03; + + public static final String ENCODING_X509 = "X509"; + public static final String ENCODING_X962_UNCOMPESSED = "X962_UNCOMPRESSED"; + public static final String ENCODING_X962_COMPRESSED = "X962_COMPRESSED"; + + public static PublicKey fromEncoded (byte[] keyBytes, String oid, Provider provider) { + + PublicKey key = null; + + + String keyAlgName = null; + try { + keyAlgName = AlgorithmNameResolver.getName(AlgorithmNameResolver.TYPE_KEY_GENERATOR_ALG, oid,provider); + } catch (Exception e1) { + throw new IllegalArgumentException("algorithm unknown in: " + provider.getName()); + } + + if (keyAlgName == null || keyAlgName.length() == 0) { + throw new IllegalArgumentException("algorithm unknown in: " + provider.getName()); + } + + //try standard X.509 encoding first + + try { + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + + if (keyAlgName != null && keyAlgName.length() > 0) { + + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgName,provider); + + key = keyFactory.generatePublic(keySpec); + + } + + } catch (Exception e) { + + //try next option + + } + + if (key != null) return key; + + if (keyBytes[0] == X962_UNCOMPRESSED_POINT_INDICATOR) { + + + + } + + //maybe a compressed X9.62 eliptic key + if (keyBytes[0] == X962_ODD || keyBytes[0] == X962_EVEN) { + + + try { + + //we need to know the curve! + String curveName = EllipticCurveNames.getInstance().getName(oid); + + //get the curve parameters + AlgorithmParameters parameters = AlgorithmParameters.getInstance(keyAlgName, provider); + parameters.init(new ECGenParameterSpec(curveName)); + ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); + EllipticCurve curve = ecParameters.getCurve(); + + //reconstruct the uncompressed version with the curve + byte[] uncompressed = decompressPubkey(keyBytes, curve); + + //decode the uncompressed key + return fromUncompressedPoint(uncompressed, ecParameters); + + } catch (Exception e) { + key = null; + // failed + } + + + } + + //try X962 uncompressed + if (keyBytes[0] == X962_UNCOMPRESSED_POINT_INDICATOR) { + + try { + //we need to know the curve! + String curveName = EllipticCurveNames.getInstance().getName(oid); + + //get the curve parameters + AlgorithmParameters parameters = AlgorithmParameters.getInstance(keyAlgName, provider); + parameters.init(new ECGenParameterSpec(curveName)); + ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); + + //decode the uncompressed key + return fromUncompressedPoint(keyBytes, ecParameters); + + } catch(Exception e) { + //failed + } + } + + + throw new IllegalArgumentException("public key format unknown"); + } + + + + public static byte[] getEncoded(PublicKey key, String encoding){ + + if (encoding.equals(ENCODING_X509)) { + return key.getEncoded(); + } else if (encoding.equals(ENCODING_X962_UNCOMPESSED)) { + + if (key instanceof ECPublicKey) { + + return toUncompressedPoint((ECPublicKey) key); + + } + + } else if (encoding.equals(ENCODING_X962_COMPRESSED)) { + + if (key instanceof ECPublicKey) { + + ECPoint point = ((ECPublicKey) key).getW(); + + byte[] x = toUnsignedBytes(point.getAffineX()); + + BigInteger y = point.getAffineY(); + + byte[] compressed = new byte[x.length + 1]; + + //compression indicator + if (y.testBit(0)) { + compressed[0] = 0x03; + } else { + compressed[0] = 0x02; + } + System.arraycopy(x, 0, compressed, 1, x.length); + + return compressed; + } + } + + throw new IllegalArgumentException("unknown encoding"); + + + } + + + private static ECPublicKey fromUncompressedPoint( + final byte[] uncompressedPoint, final ECParameterSpec params) + throws Exception { + + int offset = 0; + if (uncompressedPoint[offset++] != X962_UNCOMPRESSED_POINT_INDICATOR) { + throw new IllegalArgumentException( + "Invalid uncompressedPoint encoding, no uncompressed point indicator"); + } + + int keySizeBytes = (params.getOrder().bitLength() + Byte.SIZE - 1) + / Byte.SIZE; + + if (uncompressedPoint.length != 1 + 2 * keySizeBytes) { + throw new IllegalArgumentException( + "Invalid uncompressedPoint encoding, not the correct size"); + } + + final BigInteger x = new BigInteger(1, Arrays.copyOfRange( + uncompressedPoint, offset, offset + keySizeBytes)); + offset += keySizeBytes; + final BigInteger y = new BigInteger(1, Arrays.copyOfRange( + uncompressedPoint, offset, offset + keySizeBytes)); + final ECPoint w = new ECPoint(x, y); + final ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(w, params); + final KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return (ECPublicKey) keyFactory.generatePublic(ecPublicKeySpec); + } + + private static byte[] toUncompressedPoint(final ECPublicKey publicKey) { + + int keySizeBytes = (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1) + / Byte.SIZE; + + final byte[] uncompressedPoint = new byte[1 + 2 * keySizeBytes]; + int offset = 0; + uncompressedPoint[offset++] = 0x04; + + final byte[] x = publicKey.getW().getAffineX().toByteArray(); + if (x.length <= keySizeBytes) { + System.arraycopy(x, 0, uncompressedPoint, offset + keySizeBytes + - x.length, x.length); + } else if (x.length == keySizeBytes + 1 && x[0] == 0) { + System.arraycopy(x, 1, uncompressedPoint, offset, keySizeBytes); + } else { + throw new IllegalStateException("x value is too large"); + } + offset += keySizeBytes; + + final byte[] y = publicKey.getW().getAffineY().toByteArray(); + if (y.length <= keySizeBytes) { + System.arraycopy(y, 0, uncompressedPoint, offset + keySizeBytes + - y.length, y.length); + } else if (y.length == keySizeBytes + 1 && y[0] == 0) { + System.arraycopy(y, 1, uncompressedPoint, offset, keySizeBytes); + } else { + throw new IllegalStateException("y value is too large"); + } + + return uncompressedPoint; + } + + static final BigInteger MODULUS = + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16); + + static final BigInteger POW = MODULUS.add(BigInteger.ONE).shiftRight(2); + + // Given a 33-byte compressed public key, this returns a 65-byte uncompressed key. + private static byte[] decompressPubkey(byte[] compressedKey, EllipticCurve curve ) { + + // Check array length and type indicator byte + if (compressedKey.length != 33 || compressedKey[0] != 2 && compressedKey[0] != 3) + throw new IllegalArgumentException(); + + final byte[] xCoordBytes = Arrays.copyOfRange(compressedKey, 1, compressedKey.length); + final BigInteger xCoord = new BigInteger(1, xCoordBytes); // Range [0, 2^256) + + + BigInteger temp = xCoord.pow(2).add(curve.getA()); + temp = temp.multiply(xCoord); + temp = temp.add(curve.getB()); + temp = temp.modPow(POW, MODULUS); + + //temp = sqrtMod(temp.add(curveParamB)); + + boolean tempIsOdd = temp.testBit(0); + boolean yShouldBeOdd = compressedKey[0] == 3; + if (tempIsOdd != yShouldBeOdd) + temp = temp.negate().mod(MODULUS); + final BigInteger yCoord = temp; + + // Copy the x coordinate into the new + // uncompressed key, and change the type byte + byte[] result = Arrays.copyOf(compressedKey, 65); + result[0] = 0x04; + + // Carefully copy the y coordinate into uncompressed key + final byte[] yCoordBytes = yCoord.toByteArray(); + for (int i = 0; i < 32 && i < yCoordBytes.length; i++) + result[result.length - 1 - i] = yCoordBytes[yCoordBytes.length - 1 - i]; + + return result; + } + + private static byte[] toUnsignedBytes(BigInteger i) { + byte[] b = i.abs().toByteArray(); + //remove top sign bit + if (b[0] == 0) { + b = Arrays.copyOfRange(b, 1, b.length); + } + return b; + } + + +} diff --git a/src/main/java/org/uic/barcode/utils/EllipticCurveNames.java b/src/main/java/org/uic/barcode/utils/EllipticCurveNames.java new file mode 100644 index 0000000..41353b1 --- /dev/null +++ b/src/main/java/org/uic/barcode/utils/EllipticCurveNames.java @@ -0,0 +1,67 @@ +package org.uic.barcode.utils; + +import java.util.HashMap; + +// TODO: Auto-generated Javadoc +/** + * The Class ElipticCurves. + */ +public class EllipticCurveNames { + + + /** The oit to name. */ + public HashMap oitToName = new HashMap(); + + /** The me. */ + private static EllipticCurveNames me = null; + + + /** + * Gets the single instance of ElipticCurves. + * + * @return single instance of ElipticCurves + */ + public static EllipticCurveNames getInstance() { + + if (me == null) { + me = new EllipticCurveNames(); + + me.oitToName.put("1.3.132.0.15", "sect163r2"); + me.oitToName.put("1.3.132.0.33", "secp224r1"); + me.oitToName.put("1.3.132.0.26", "sect233k1"); + me.oitToName.put("1.3.132.0.27", "sect233r1"); + me.oitToName.put("1.3.132.0.16", "sect283k1"); + me.oitToName.put("1.3.132.0.17", "sect283r1"); + me.oitToName.put("1.3.132.0.34", "secp384r1"); + me.oitToName.put("1.3.132.0.36", "sect409k1"); + me.oitToName.put("1.3.132.0.37", "sect409r1"); + me.oitToName.put("1.3.132.0.35", "secp521r1"); + me.oitToName.put("1.3.132.0.38", "sect571k1"); + me.oitToName.put("1.3.132.0.39", "sect571r1"); + me.oitToName.put("1.3.132.0.10", "secp256k1"); + + } + return me; + } + + + /** + * Adds the oid to name mapping. + * + * @param oid the oid + * @param name the name + */ + public void addOidToNameMapping(String oid, String name) { + oitToName.put(oid, name); + } + + /** + * Gets the name. + * + * @param oid the oid + * @return the name + */ + public String getName(String oid) { + return oitToName.get(oid); + } +} diff --git a/src/main/java/org/uic/barcode/utils/SecurityUtils.java b/src/main/java/org/uic/barcode/utils/SecurityUtils.java index fc6a135..5fdbda7 100644 --- a/src/main/java/org/uic/barcode/utils/SecurityUtils.java +++ b/src/main/java/org/uic/barcode/utils/SecurityUtils.java @@ -24,37 +24,16 @@ public class SecurityUtils { * @return the provider */ public static Provider findPublicKeyProvider(String keyAlgorithmOid, byte[] keyBytes) { - - - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - - String name; - try { - name = AlgorithmNameResolver.getAlgorithmName(AlgorithmNameResolver.TYPE_KEY_GENERATOR_ALG, keyAlgorithmOid, null); - } catch (Exception e2) { - return null; - } - - KeyFactory keyFactory = null; - + Provider[] provs = Security.getProviders(); for (Provider provider : provs) { try { - keyFactory = KeyFactory.getInstance(name,provider); - } catch (NoSuchAlgorithmException e1) { + PublicKey key = ECKeyEncoder.fromEncoded(keyBytes, keyAlgorithmOid, provider); + if (key != null) return provider; + } catch (Exception e1) { //try next } - if (keyFactory != null) { - try { - keyFactory.generatePublic(keySpec); - return provider; - } catch (Exception e) { - provider = null; - //try next - } - } } - return null; } -- cgit v1.2.3