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