You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

KeyGrip.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /*
  2. * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.gpg.bc.internal.keys;
  11. import java.math.BigInteger;
  12. import java.nio.charset.StandardCharsets;
  13. import java.text.MessageFormat;
  14. import java.util.Arrays;
  15. import org.bouncycastle.asn1.ASN1ObjectIdentifier;
  16. import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers;
  17. import org.bouncycastle.asn1.x9.ECNamedCurveTable;
  18. import org.bouncycastle.asn1.x9.X9ECParameters;
  19. import org.bouncycastle.bcpg.DSAPublicBCPGKey;
  20. import org.bouncycastle.bcpg.ECPublicBCPGKey;
  21. import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
  22. import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
  23. import org.bouncycastle.bcpg.RSAPublicBCPGKey;
  24. import org.bouncycastle.crypto.ec.CustomNamedCurves;
  25. import org.bouncycastle.math.ec.ECAlgorithms;
  26. import org.bouncycastle.math.field.FiniteField;
  27. import org.bouncycastle.openpgp.PGPException;
  28. import org.bouncycastle.openpgp.PGPPublicKey;
  29. import org.bouncycastle.util.encoders.Hex;
  30. import org.eclipse.jgit.annotations.NonNull;
  31. import org.eclipse.jgit.gpg.bc.internal.BCText;
  32. import org.eclipse.jgit.util.sha1.SHA1;
  33. /**
  34. * Utilities to compute the <em>keygrip</em> of a key. A keygrip is a SHA1 hash
  35. * over the public key parameters and is used internally by the gpg-agent to
  36. * find the secret key belonging to a public key: the secret key is stored in a
  37. * file under ~/.gnupg/private-keys-v1.d/ with a name "&lt;keygrip>.key". While
  38. * this storage organization is an implementation detail of GPG, the way
  39. * keygrips are computed is not; they are computed by libgcrypt and their
  40. * definition is stable.
  41. */
  42. public final class KeyGrip {
  43. // Some OIDs apparently unknown to BouncyCastle.
  44. private static String OID_OPENPGP_ED25519 = "1.3.6.1.4.1.11591.15.1"; //$NON-NLS-1$
  45. private static String OID_RFC8410_CURVE25519 = "1.3.101.110"; //$NON-NLS-1$
  46. private static String OID_RFC8410_ED25519 = "1.3.101.112"; //$NON-NLS-1$
  47. private KeyGrip() {
  48. // No instantiation
  49. }
  50. /**
  51. * Computes the keygrip for a {@link PGPPublicKey}.
  52. *
  53. * @param publicKey
  54. * to get the keygrip of
  55. * @return the keygrip
  56. * @throws PGPException
  57. * if an unknown key type is encountered.
  58. */
  59. @NonNull
  60. public static byte[] getKeyGrip(PGPPublicKey publicKey)
  61. throws PGPException {
  62. SHA1 grip = SHA1.newInstance();
  63. grip.setDetectCollision(false);
  64. switch (publicKey.getAlgorithm()) {
  65. case PublicKeyAlgorithmTags.RSA_GENERAL:
  66. case PublicKeyAlgorithmTags.RSA_ENCRYPT:
  67. case PublicKeyAlgorithmTags.RSA_SIGN:
  68. BigInteger modulus = ((RSAPublicBCPGKey) publicKey
  69. .getPublicKeyPacket().getKey()).getModulus();
  70. hash(grip, modulus.toByteArray());
  71. break;
  72. case PublicKeyAlgorithmTags.DSA:
  73. DSAPublicBCPGKey dsa = (DSAPublicBCPGKey) publicKey
  74. .getPublicKeyPacket().getKey();
  75. hash(grip, dsa.getP().toByteArray(), 'p', true);
  76. hash(grip, dsa.getQ().toByteArray(), 'q', true);
  77. hash(grip, dsa.getG().toByteArray(), 'g', true);
  78. hash(grip, dsa.getY().toByteArray(), 'y', true);
  79. break;
  80. case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
  81. case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
  82. ElGamalPublicBCPGKey eg = (ElGamalPublicBCPGKey) publicKey
  83. .getPublicKeyPacket().getKey();
  84. hash(grip, eg.getP().toByteArray(), 'p', true);
  85. hash(grip, eg.getG().toByteArray(), 'g', true);
  86. hash(grip, eg.getY().toByteArray(), 'y', true);
  87. break;
  88. case PublicKeyAlgorithmTags.ECDH:
  89. case PublicKeyAlgorithmTags.ECDSA:
  90. case PublicKeyAlgorithmTags.EDDSA:
  91. ECPublicBCPGKey ec = (ECPublicBCPGKey) publicKey
  92. .getPublicKeyPacket().getKey();
  93. ASN1ObjectIdentifier curveOID = ec.getCurveOID();
  94. // BC doesn't know these OIDs.
  95. if (OID_OPENPGP_ED25519.equals(curveOID.getId())
  96. || OID_RFC8410_ED25519.equals(curveOID.getId())) {
  97. return hashEd25519(grip, ec.getEncodedPoint());
  98. } else if (CryptlibObjectIdentifiers.curvey25519.equals(curveOID)
  99. || OID_RFC8410_CURVE25519.equals(curveOID.getId())) {
  100. // curvey25519 actually is the OpenPGP OID for Curve25519 and is
  101. // known to BC, but the parameters are for the short Weierstrass
  102. // form. See https://github.com/bcgit/bc-java/issues/399 .
  103. // libgcrypt uses Montgomery form.
  104. return hashCurve25519(grip, ec.getEncodedPoint());
  105. }
  106. X9ECParameters params = getX9Parameters(curveOID);
  107. if (params == null) {
  108. throw new PGPException(MessageFormat
  109. .format(BCText.get().unknownCurve, curveOID.getId()));
  110. }
  111. // Need to write p, a, b, g, n, q
  112. BigInteger q = ec.getEncodedPoint();
  113. byte[] g = params.getG().getEncoded(false);
  114. BigInteger a = params.getCurve().getA().toBigInteger();
  115. BigInteger b = params.getCurve().getB().toBigInteger();
  116. BigInteger n = params.getN();
  117. BigInteger p = null;
  118. FiniteField field = params.getCurve().getField();
  119. if (ECAlgorithms.isFpField(field)) {
  120. p = field.getCharacteristic();
  121. }
  122. if (p == null) {
  123. // Don't know...
  124. throw new PGPException(MessageFormat.format(
  125. BCText.get().unknownCurveParameters, curveOID.getId()));
  126. }
  127. hash(grip, p.toByteArray(), 'p', false);
  128. hash(grip, a.toByteArray(), 'a', false);
  129. hash(grip, b.toByteArray(), 'b', false);
  130. hash(grip, g, 'g', false);
  131. hash(grip, n.toByteArray(), 'n', false);
  132. if (publicKey.getAlgorithm() == PublicKeyAlgorithmTags.EDDSA) {
  133. hashQ25519(grip, q);
  134. } else {
  135. hash(grip, q.toByteArray(), 'q', false);
  136. }
  137. break;
  138. default:
  139. throw new PGPException(
  140. MessageFormat.format(BCText.get().unknownKeyType,
  141. Integer.toString(publicKey.getAlgorithm())));
  142. }
  143. return grip.digest();
  144. }
  145. private static void hash(SHA1 grip, byte[] data) {
  146. // Need to skip leading zero bytes
  147. int i = 0;
  148. while (i < data.length && data[i] == 0) {
  149. i++;
  150. }
  151. int length = data.length - i;
  152. if (i < data.length) {
  153. if ((data[i] & 0x80) != 0) {
  154. grip.update((byte) 0);
  155. }
  156. grip.update(data, i, length);
  157. }
  158. }
  159. private static void hash(SHA1 grip, byte[] data, char id, boolean zeroPad) {
  160. // Need to skip leading zero bytes
  161. int i = 0;
  162. while (i < data.length && data[i] == 0) {
  163. i++;
  164. }
  165. int length = data.length - i;
  166. boolean addZero = false;
  167. if (i < data.length && zeroPad && (data[i] & 0x80) != 0) {
  168. addZero = true;
  169. }
  170. // libgcrypt includes an SExp in the hash
  171. String prefix = "(1:" + id + (addZero ? length + 1 : length) + ':'; //$NON-NLS-1$
  172. grip.update(prefix.getBytes(StandardCharsets.US_ASCII));
  173. // For some items, gcrypt prepends a zero byte if the high bit is set
  174. if (addZero) {
  175. grip.update((byte) 0);
  176. }
  177. if (i < data.length) {
  178. grip.update(data, i, length);
  179. }
  180. grip.update((byte) ')');
  181. }
  182. private static void hashQ25519(SHA1 grip, BigInteger q)
  183. throws PGPException {
  184. byte[] data = q.toByteArray();
  185. switch (data[0]) {
  186. case 0x04:
  187. if (data.length != 65) {
  188. throw new PGPException(MessageFormat.format(
  189. BCText.get().corrupt25519Key, Hex.toHexString(data)));
  190. }
  191. // Uncompressed: should not occur with ed25519 or curve25519
  192. throw new PGPException(MessageFormat.format(
  193. BCText.get().uncompressed25519Key, Hex.toHexString(data)));
  194. case 0x40:
  195. if (data.length != 33) {
  196. throw new PGPException(MessageFormat.format(
  197. BCText.get().corrupt25519Key, Hex.toHexString(data)));
  198. }
  199. // Compressed; normal case. Skip prefix.
  200. hash(grip, Arrays.copyOfRange(data, 1, data.length), 'q', false);
  201. break;
  202. default:
  203. if (data.length != 32) {
  204. throw new PGPException(MessageFormat.format(
  205. BCText.get().corrupt25519Key, Hex.toHexString(data)));
  206. }
  207. // Compressed format without prefix. Should not occur?
  208. hash(grip, data, 'q', false);
  209. break;
  210. }
  211. }
  212. /**
  213. * Computes the keygrip for an ed25519 public key.
  214. * <p>
  215. * Package-visible for tests only.
  216. * </p>
  217. *
  218. * @param grip
  219. * initialized {@link SHA1}
  220. * @param q
  221. * the public key's EC point
  222. * @return the keygrip
  223. * @throws PGPException
  224. * if q indicates uncompressed format
  225. */
  226. @SuppressWarnings("nls")
  227. static byte[] hashEd25519(SHA1 grip, BigInteger q) throws PGPException {
  228. // For the values, see RFC 7748: https://tools.ietf.org/html/rfc7748
  229. // p = 2^255 - 19
  230. hash(grip, Hex.decodeStrict(
  231. "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"),
  232. 'p', false);
  233. // Field: a = 1
  234. hash(grip, new byte[] { 0x01 }, 'a', false);
  235. // Field: b = 121665/121666 (mod p)
  236. // See Berstein et.al., "Twisted Edwards Curves",
  237. // https://doi.org/10.1007/978-3-540-68164-9_26
  238. hash(grip, Hex.decodeStrict(
  239. "2DFC9311D490018C7338BF8688861767FF8FF5B2BEBE27548A14B235ECA6874A"),
  240. 'b', false);
  241. // Generator point with affine X,Y
  242. // @formatter:off
  243. // X(P) = 15112221349535400772501151409588531511454012693041857206046113283949847762202
  244. // Y(P) = 46316835694926478169428394003475163141307993866256225615783033603165251855960
  245. // the "04" signifies uncompressed format.
  246. // @formatter:on
  247. hash(grip, Hex.decodeStrict("04"
  248. + "216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A"
  249. + "6666666666666666666666666666666666666666666666666666666666666658"),
  250. 'g', false);
  251. // order = 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed
  252. hash(grip, Hex.decodeStrict(
  253. "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"),
  254. 'n', false);
  255. hashQ25519(grip, q);
  256. return grip.digest();
  257. }
  258. /**
  259. * Computes the keygrip for a curve25519 public key.
  260. * <p>
  261. * Package-visible for tests only.
  262. * </p>
  263. *
  264. * @param grip
  265. * initialized {@link SHA1}
  266. * @param q
  267. * the public key's EC point
  268. * @return the keygrip
  269. * @throws PGPException
  270. * if q indicates uncompressed format
  271. */
  272. @SuppressWarnings("nls")
  273. static byte[] hashCurve25519(SHA1 grip, BigInteger q) throws PGPException {
  274. hash(grip, Hex.decodeStrict(
  275. "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"),
  276. 'p', false);
  277. // Unclear: RFC 7748 says A = 486662. This value here is (A-2)/4 =
  278. // 121665. Compare ecc-curves.c in libgcrypt:
  279. // https://github.com/gpg/libgcrypt/blob/361a058/cipher/ecc-curves.c#L146
  280. hash(grip, new byte[] { 0x01, (byte) 0xDB, 0x41 }, 'a', false);
  281. hash(grip, new byte[] { 0x01 }, 'b', false);
  282. // libgcrypt uses the old g.y value before the erratum to RFC 7748 for
  283. // the keygrip. The new value would be
  284. // 5F51E65E475F794B1FE122D388B72EB36DC2B28192839E4DD6163A5D81312C14. See
  285. // https://www.rfc-editor.org/errata/eid4730 and
  286. // https://github.com/gpg/libgcrypt/commit/f67b6492e0b0
  287. hash(grip, Hex.decodeStrict("04"
  288. + "0000000000000000000000000000000000000000000000000000000000000009"
  289. + "20AE19A1B8A086B4E01EDD2C7748D14C923D4D7E6D7C61B229E9C5A27ECED3D9"),
  290. 'g', false);
  291. hash(grip, Hex.decodeStrict(
  292. "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"),
  293. 'n', false);
  294. hashQ25519(grip, q);
  295. return grip.digest();
  296. }
  297. private static X9ECParameters getX9Parameters(
  298. ASN1ObjectIdentifier curveOID) {
  299. X9ECParameters params = CustomNamedCurves.getByOID(curveOID);
  300. if (params == null) {
  301. params = ECNamedCurveTable.getByOID(curveOID);
  302. }
  303. return params;
  304. }
  305. }