From 29550c11ba6e189b56459a859b72ab8555918484 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Mon, 14 Dec 2020 09:33:44 +0100 Subject: [PATCH 1/4] Support for parsing EdDSA-related key types from Java objects to OneKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables parsing keys from Java objects into OneKey objects for keys using curves such as Ed25519. Signed-off-by: Rikard Höglund --- src/main/java/COSE/ASN1.java | 37 ++++++++++++++++++++- src/main/java/COSE/OneKey.java | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/main/java/COSE/ASN1.java b/src/main/java/COSE/ASN1.java index 2da80f6..b2142f8 100644 --- a/src/main/java/COSE/ASN1.java +++ b/src/main/java/COSE/ASN1.java @@ -251,6 +251,33 @@ public static TagValue DecodeCompound(int offset, byte[] encoding) throws CoseEx return new TagValue(retTag, result); } + /** + * Decode an array of bytes which is supposed to be an ASN.1 encoded octet string. + * + * @param offset - starting offset in array to begin decoding + * @param encoding - bytes of the ASN.1 encoded value + * @return Decoded structure + * @throws CoseException - ASN.1 encoding errors + */ + public static TagValue DecodeSimple(int offset, byte[] encoding) throws CoseException { + ArrayList result = new ArrayList(); + int retTag = encoding[offset]; + + if (encoding[offset] != 0x04) + throw new CoseException("Invalid structure"); + int[] l = DecodeLength(offset + 1, encoding); + + int sequenceLength = l[1]; + if (offset + 2 + sequenceLength != encoding.length) + throw new CoseException("Invalid sequence"); + + int tag = encoding[offset]; + offset += 1 + l[0]; + result.add(new TagValue(tag, Arrays.copyOfRange(encoding, offset, offset + l[1]))); + + return new TagValue(retTag, result); + } + /** * Encode a private key into a PKCS#8 private key structure. * @@ -371,8 +398,16 @@ public static ArrayList DecodePKCS8EC(ArrayList pkcs8) throw // Decode the contents of the octet string PrivateKey byte[] pk = pkcs8.get(2).value; + TagValue pkd; - TagValue pkd = DecodeCompound(0, pk); + // First check if it can be decoded as a simple value + if (pk[0] == 0x04) { // ASN.1 Octet string + pkd = DecodeSimple(0, pk); + return pkd.list; + } + + // Otherwise proceed to parse as compound value + pkd = DecodeCompound(0, pk); ArrayList pkdl = pkd.list; if (pkd.tag != 0x30) throw new CoseException("Invalid ECPrivateKey"); if (pkdl.size() < 2 || pkdl.size() > 4) throw new CoseException("Invalid ECPrivateKey"); diff --git a/src/main/java/COSE/OneKey.java b/src/main/java/COSE/OneKey.java index 99f536e..44fb00d 100644 --- a/src/main/java/COSE/OneKey.java +++ b/src/main/java/COSE/OneKey.java @@ -108,6 +108,33 @@ else if (Arrays.equals(alg.get(0).value, ASN1.Oid_rsaEncryption)) { keyMap.Add(KeyKeys.RSA_N.AsCBOR(), n.value); keyMap.Add(KeyKeys.RSA_E.AsCBOR(), e.value); + } else if (Arrays.equals(alg.get(0).value, ASN1.Oid_Ed25519) + || Arrays.equals(alg.get(0).value, ASN1.Oid_Ed448) + || Arrays.equals(alg.get(0).value, ASN1.Oid_X25519) + || Arrays.equals(alg.get(0).value, ASN1.Oid_X448)) { + byte[] oid = (byte[]) alg.get(0).value; + if (oid == null) + throw new CoseException("Invalid SPKI structure"); + + // OKP Key + keyMap.Add(KeyKeys.KeyType.AsCBOR(), KeyKeys.KeyType_OKP); + keyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.EDDSA.AsCBOR()); + if (Arrays.equals(oid, ASN1.Oid_X25519)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_X25519); + else if (Arrays.equals(oid, ASN1.Oid_X448)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_X448); + else if (Arrays.equals(oid, ASN1.Oid_Ed25519)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_Ed25519); + else if (Arrays.equals(oid, ASN1.Oid_Ed448)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_Ed448); + else + throw new CoseException("Unsupported curve"); + + byte[] keyData = (byte[]) spki.get(1).value; + if (keyData[0] == 0) { + keyMap.Add(KeyKeys.OKP_X.AsCBOR(), Arrays.copyOfRange(keyData, 1, keyData.length)); + } else + throw new CoseException("Invalid key data"); } else { throw new CoseException("Unsupported Algorithm"); @@ -162,6 +189,39 @@ else if (Arrays.equals(alg.get(0).value, ASN1.Oid_rsaEncryption)) { keyMap.Add(KeyKeys.RSA_QI.AsCBOR(), pkdl.get(8).value); // todo multi prime keys + } else if (Arrays.equals(alg.get(0).value, ASN1.Oid_Ed25519) + || Arrays.equals(alg.get(0).value, ASN1.Oid_Ed448) + || Arrays.equals(alg.get(0).value, ASN1.Oid_X25519) + || Arrays.equals(alg.get(0).value, ASN1.Oid_X448)) { + byte[] oid = (byte[]) alg.get(0).value; + if (oid == null) + throw new CoseException("Invalid PKCS8 structure"); + // OKP Key + if (!keyMap.ContainsKey(KeyKeys.KeyType.AsCBOR())) { + + keyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.EDDSA.AsCBOR()); + if (Arrays.equals(oid, ASN1.Oid_X25519)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_X25519); + else if (Arrays.equals(oid, ASN1.Oid_X448)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_X448); + else if (Arrays.equals(oid, ASN1.Oid_Ed25519)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_Ed25519); + else if (Arrays.equals(oid, ASN1.Oid_Ed448)) + keyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_Ed448); + else + throw new CoseException("Unsupported curve"); + + } else { + if (!this.get(KeyKeys.KeyType).equals(KeyKeys.KeyType_OKP)) { + throw new CoseException("Public/Private key don't match"); + } + } + + ArrayList pkdl = ASN1.DecodePKCS8EC(pkl); + if (pkdl.get(0).tag != 4) + throw new CoseException("Invalid PKCS8 structure"); + byte[] keyData = (byte[]) (pkdl.get(0).value); + keyMap.Add(KeyKeys.OKP_D.AsCBOR(), keyData); } else { throw new CoseException("Unsupported Algorithm"); From 2e79e03030f97d6812e70220a2168847d2aca463 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Wed, 21 Apr 2021 10:26:04 +0200 Subject: [PATCH 2/4] Extracted code for OID comparison in condition to helper method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rikard Höglund --- src/main/java/COSE/ASN1.java | 18 ++++++++++++++++++ src/main/java/COSE/OneKey.java | 10 ++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/COSE/ASN1.java b/src/main/java/COSE/ASN1.java index b2142f8..55b75a9 100644 --- a/src/main/java/COSE/ASN1.java +++ b/src/main/java/COSE/ASN1.java @@ -96,6 +96,24 @@ private static String tagToString(int tag) { private static final byte[] BitStringTag = new byte[]{0x3}; private static final int IntegerTag = 2; + /** + * Determine if a specific OID is matching one used for ECDH or EdDSA. + * See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves + * + * This means it is matching either Ed25519, Ed448, X25519 or X448. + * + * @param oid the OID to check + * @return if it is matching Ed25519, Ed448, X25519 or X448 + */ + public static boolean isEcdhEddsaOid(byte[] oid) { + if (Arrays.equals(oid, ASN1.Oid_Ed25519) || Arrays.equals(oid, ASN1.Oid_Ed448) + || Arrays.equals(oid, ASN1.Oid_X25519) || Arrays.equals(oid, ASN1.Oid_X448)) { + return true; + } else { + return false; + } + } + /** * Encode a subject public key info structure from an OID and the data bytes * for the key diff --git a/src/main/java/COSE/OneKey.java b/src/main/java/COSE/OneKey.java index 44fb00d..bff5ad3 100644 --- a/src/main/java/COSE/OneKey.java +++ b/src/main/java/COSE/OneKey.java @@ -108,10 +108,7 @@ else if (Arrays.equals(alg.get(0).value, ASN1.Oid_rsaEncryption)) { keyMap.Add(KeyKeys.RSA_N.AsCBOR(), n.value); keyMap.Add(KeyKeys.RSA_E.AsCBOR(), e.value); - } else if (Arrays.equals(alg.get(0).value, ASN1.Oid_Ed25519) - || Arrays.equals(alg.get(0).value, ASN1.Oid_Ed448) - || Arrays.equals(alg.get(0).value, ASN1.Oid_X25519) - || Arrays.equals(alg.get(0).value, ASN1.Oid_X448)) { + } else if (ASN1.isEcdhEddsaOid(alg.get(0).value)) { byte[] oid = (byte[]) alg.get(0).value; if (oid == null) throw new CoseException("Invalid SPKI structure"); @@ -189,10 +186,7 @@ else if (Arrays.equals(alg.get(0).value, ASN1.Oid_rsaEncryption)) { keyMap.Add(KeyKeys.RSA_QI.AsCBOR(), pkdl.get(8).value); // todo multi prime keys - } else if (Arrays.equals(alg.get(0).value, ASN1.Oid_Ed25519) - || Arrays.equals(alg.get(0).value, ASN1.Oid_Ed448) - || Arrays.equals(alg.get(0).value, ASN1.Oid_X25519) - || Arrays.equals(alg.get(0).value, ASN1.Oid_X448)) { + } else if (ASN1.isEcdhEddsaOid(alg.get(0).value)) { byte[] oid = (byte[]) alg.get(0).value; if (oid == null) throw new CoseException("Invalid PKCS8 structure"); From 35eebc19c15de74bd4c6451b243e6c0425d1dec6 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Wed, 21 Apr 2021 21:56:53 +0200 Subject: [PATCH 3/4] Simplified helper method for ECDH/EdDSA OID comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rikard Höglund --- src/main/java/COSE/ASN1.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/COSE/ASN1.java b/src/main/java/COSE/ASN1.java index 55b75a9..c25ccec 100644 --- a/src/main/java/COSE/ASN1.java +++ b/src/main/java/COSE/ASN1.java @@ -106,12 +106,8 @@ private static String tagToString(int tag) { * @return if it is matching Ed25519, Ed448, X25519 or X448 */ public static boolean isEcdhEddsaOid(byte[] oid) { - if (Arrays.equals(oid, ASN1.Oid_Ed25519) || Arrays.equals(oid, ASN1.Oid_Ed448) - || Arrays.equals(oid, ASN1.Oid_X25519) || Arrays.equals(oid, ASN1.Oid_X448)) { - return true; - } else { - return false; - } + return Arrays.equals(oid, ASN1.Oid_Ed25519) || Arrays.equals(oid, ASN1.Oid_Ed448) + || Arrays.equals(oid, ASN1.Oid_X25519) || Arrays.equals(oid, ASN1.Oid_X448); } /** From 1f1994e739dc492edd4f9eeb8f4b85c5d6a27545 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Thu, 22 Apr 2021 15:54:40 +0200 Subject: [PATCH 4/4] Rename helper method for OID comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helper method will match against OIDs for Ed25519, Ed448, X25519 or X448. Signed-off-by: Rikard Höglund --- src/main/java/COSE/ASN1.java | 7 ++----- src/main/java/COSE/OneKey.java | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/COSE/ASN1.java b/src/main/java/COSE/ASN1.java index c25ccec..c9220d3 100644 --- a/src/main/java/COSE/ASN1.java +++ b/src/main/java/COSE/ASN1.java @@ -97,15 +97,12 @@ private static String tagToString(int tag) { private static final int IntegerTag = 2; /** - * Determine if a specific OID is matching one used for ECDH or EdDSA. - * See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves - * - * This means it is matching either Ed25519, Ed448, X25519 or X448. + * Determine if a specific OID is matching either Ed25519, Ed448, X25519 or X448. * * @param oid the OID to check * @return if it is matching Ed25519, Ed448, X25519 or X448 */ - public static boolean isEcdhEddsaOid(byte[] oid) { + public static boolean isEdXOid(byte[] oid) { return Arrays.equals(oid, ASN1.Oid_Ed25519) || Arrays.equals(oid, ASN1.Oid_Ed448) || Arrays.equals(oid, ASN1.Oid_X25519) || Arrays.equals(oid, ASN1.Oid_X448); } diff --git a/src/main/java/COSE/OneKey.java b/src/main/java/COSE/OneKey.java index bff5ad3..0048afd 100644 --- a/src/main/java/COSE/OneKey.java +++ b/src/main/java/COSE/OneKey.java @@ -108,7 +108,7 @@ else if (Arrays.equals(alg.get(0).value, ASN1.Oid_rsaEncryption)) { keyMap.Add(KeyKeys.RSA_N.AsCBOR(), n.value); keyMap.Add(KeyKeys.RSA_E.AsCBOR(), e.value); - } else if (ASN1.isEcdhEddsaOid(alg.get(0).value)) { + } else if (ASN1.isEdXOid(alg.get(0).value)) { byte[] oid = (byte[]) alg.get(0).value; if (oid == null) throw new CoseException("Invalid SPKI structure"); @@ -186,7 +186,7 @@ else if (Arrays.equals(alg.get(0).value, ASN1.Oid_rsaEncryption)) { keyMap.Add(KeyKeys.RSA_QI.AsCBOR(), pkdl.get(8).value); // todo multi prime keys - } else if (ASN1.isEcdhEddsaOid(alg.get(0).value)) { + } else if (ASN1.isEdXOid(alg.get(0).value)) { byte[] oid = (byte[]) alg.get(0).value; if (oid == null) throw new CoseException("Invalid PKCS8 structure");