From f1c54ea2ab1be18af3221a447d1a2ab7ac711911 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 30 Apr 2024 12:32:07 +0200 Subject: [PATCH] Add FingerprintUtil helper for deriving key-ids from fingerprints --- .../bouncycastle/bcpg/FingerprintUtil.java | 81 +++++++++++++++++++ .../bcpg/test/FingerprintUtilTest.java | 57 +++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java create mode 100644 pg/src/test/java/org/bouncycastle/bcpg/test/FingerprintUtilTest.java diff --git a/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java b/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java new file mode 100644 index 0000000000..f3bc739e2e --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java @@ -0,0 +1,81 @@ +package org.bouncycastle.bcpg; + +public class FingerprintUtil +{ + + /** + * Derive a 64 bit key-id from a version 6 OpenPGP fingerprint. + * For v6 keys, the key-id corresponds to the left-most 8 octets of the fingerprint. + * @param v6Fingerprint 32 byte fingerprint + * @return key-id + */ + public static long keyIdFromV6Fingerprint(byte[] v6Fingerprint) + { + return longFromLeftMostBytes(v6Fingerprint); + } + + /** + * Derive a 64 bit key-id from a version 5 LibrePGP fingerprint. + * For such keys, the key-id corresponds to the left-most 8 octets of the fingerprint. + * @param v5Fingerprint 32 byte fingerprint + * @return key-id + */ + public static long keyIdFromLibrePgpFingerprint(byte[] v5Fingerprint) + { + return longFromLeftMostBytes(v5Fingerprint); + } + + /** + * Derive a 64 bit key-id from a version 4 OpenPGP fingerprint. + * For v4 keys, the key-id corresponds to the right-most 8 octets of the fingerprint. + * @param v4Fingerprint 20 byte fingerprint + * @return key-id + */ + public static long keyIdFromV4Fingerprint(byte[] v4Fingerprint) + { + return longFromRightMostBytes(v4Fingerprint); + } + + /** + * Convert the left-most 8 bytes from the given array to a long. + * @param bytes bytes + * @return long + */ + public static long longFromLeftMostBytes(byte[] bytes) + { + if (bytes.length < 8) + { + throw new IllegalArgumentException("Byte array MUST contain at least 8 bytes"); + } + return ((bytes[0] & 0xffL) << 56) | + ((bytes[1] & 0xffL) << 48) | + ((bytes[2] & 0xffL) << 40) | + ((bytes[3] & 0xffL) << 32) | + ((bytes[4] & 0xffL) << 24) | + ((bytes[5] & 0xffL) << 16) | + ((bytes[6] & 0xffL) << 8) | + ((bytes[7] & 0xffL)); + } + + /** + * Convert the right-most 8 bytes from the given array to a long. + * @param bytes bytes + * @return long + */ + public static long longFromRightMostBytes(byte[] bytes) + { + if (bytes.length < 8) + { + throw new IllegalArgumentException("Byte array MUST contain at least 8 bytes"); + } + int i = bytes.length; + return ((bytes[i - 8] & 0xffL) << 56) | + ((bytes[i - 7] & 0xffL) << 48) | + ((bytes[i - 6] & 0xffL) << 40) | + ((bytes[i - 5] & 0xffL) << 32) | + ((bytes[i - 4] & 0xffL) << 24) | + ((bytes[i - 3] & 0xffL) << 16) | + ((bytes[i - 2] & 0xffL) << 8) | + ((bytes[i - 1] & 0xffL)); + } +} diff --git a/pg/src/test/java/org/bouncycastle/bcpg/test/FingerprintUtilTest.java b/pg/src/test/java/org/bouncycastle/bcpg/test/FingerprintUtilTest.java new file mode 100644 index 0000000000..48eb0dee5c --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/bcpg/test/FingerprintUtilTest.java @@ -0,0 +1,57 @@ +package org.bouncycastle.bcpg.test; + +import org.bouncycastle.bcpg.FingerprintUtil; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +public class FingerprintUtilTest extends SimpleTest { + + private void testKeyIdFromTooShortFails() { + byte[] decoded = new byte[1]; + try { + FingerprintUtil.keyIdFromV4Fingerprint(decoded); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // expected + } + } + + private void testV4KeyIdFromFingerprint() { + String fingerprint = "1D018C772DF8C5EF86A1DCC9B4B509CB5936E03E"; + byte[] decoded = Hex.decode(fingerprint); + isEquals("v4 key-id from fingerprint mismatch", + -5425419407118114754L, FingerprintUtil.keyIdFromV4Fingerprint(decoded)); + } + + private void testV6KeyIdFromFingerprint() { + String fingerprint = "cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc9"; + byte[] decoded = Hex.decode(fingerprint); + isEquals("v6 key-id from fingerprint mismatch", + -3812177997909612905L, FingerprintUtil.keyIdFromV6Fingerprint(decoded)); + } + + private void testLibrePgpKeyIdFromFingerprint() { + // v6 key-ids are derived from fingerprints the same way as LibrePGP does + String fingerprint = "cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc9"; + byte[] decoded = Hex.decode(fingerprint); + isEquals("LibrePGP key-id from fingerprint mismatch", + -3812177997909612905L, FingerprintUtil.keyIdFromLibrePgpFingerprint(decoded)); + } + + @Override + public String getName() { + return "FingerprintUtilTest"; + } + + @Override + public void performTest() throws Exception { + testV4KeyIdFromFingerprint(); + testV6KeyIdFromFingerprint(); + testKeyIdFromTooShortFails(); + testLibrePgpKeyIdFromFingerprint(); + } + + public static void main(String[] args) { + runTest(new FingerprintUtilTest()); + } +}