@@ -63,14 +63,25 @@ public class SecretKeyPacket
6363 * Users should migrate to AEAD with all due speed.
6464 */
6565 public static final int USAGE_AEAD = 0xfd ;
66-
66+
67+ /**
68+ * Externally-backed secret key material.
69+ * S2K-usage octet indicating that the secret key material is stored externally, e.g. on a hardware device.
70+ * The draft specification is an alternative to GnuPGs proprietary {@link S2K#GNU_DUMMY_S2K} mechanism.
71+ *
72+ * @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
73+ * OpenPGP External Secret Keys</a>
74+ */
75+ public static final int USAGE_EXTERNAL = 0xfc ;
76+
6777 private PublicKeyPacket pubKeyPacket ;
6878 private byte [] secKeyData ;
6979 private int s2kUsage ;
7080 private int encAlgorithm ;
7181 private int aeadAlgorithm ;
7282 private S2K s2k ;
7383 private byte [] iv ;
84+ private byte [] externalKeyLocatorHint ;
7485
7586 /**
7687 * Parse a primary OpenPGP secret key packet from the given OpenPGP {@link BCPGInputStream}.
@@ -140,13 +151,19 @@ public class SecretKeyPacket
140151 s2kUsage = in .read ();
141152
142153 int conditionalParameterLength = -1 ;
143- if (version == PublicKeyPacket .LIBREPGP_5 ||
154+ if (version == PublicKeyPacket .LIBREPGP_5 ||
144155 (version == PublicKeyPacket .VERSION_6 && s2kUsage != USAGE_NONE ))
145156 {
146157 // TODO: Use length to parse unknown parameters
147158 conditionalParameterLength = in .read ();
148159 }
149160
161+ if (s2kUsage == USAGE_EXTERNAL )
162+ {
163+ externalKeyLocatorHint = in .readAll ();
164+ return ;
165+ }
166+
150167 if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD )
151168 {
152169 encAlgorithm = in .read ();
@@ -205,7 +222,7 @@ public class SecretKeyPacket
205222 if (encAlgorithm < 7 )
206223 {
207224 iv = new byte [8 ];
208- }
225+ }
209226 else
210227 {
211228 iv = new byte [16 ];
@@ -214,7 +231,7 @@ public class SecretKeyPacket
214231 }
215232 }
216233 }
217-
234+
218235 if (version == PublicKeyPacket .LIBREPGP_5 )
219236 {
220237 long keyOctetCount = ((long ) in .read () << 24 ) | ((long ) in .read () << 16 ) | ((long ) in .read () << 8 ) | in .read ();
@@ -233,6 +250,40 @@ public class SecretKeyPacket
233250 }
234251 }
235252
253+ /**
254+ * Create a SecretKeyPacket representing an external secret key ({@link #USAGE_EXTERNAL}).
255+ *
256+ * @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
257+ * OpenPGP External Secret Keys</a>
258+ * @param pubKeyPacket public key packet
259+ * @param locatorHint optional external key locator hint
260+ */
261+ public SecretKeyPacket (
262+ PublicKeyPacket pubKeyPacket ,
263+ byte [] locatorHint )
264+ {
265+ this (SECRET_KEY , pubKeyPacket , locatorHint );
266+ }
267+
268+
269+ /**
270+ * Create a SecretKeyPacket representing an external secret key ({@link #USAGE_EXTERNAL}).
271+ *
272+ * @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
273+ * OpenPGP External Secret Keys</a>
274+ * @param keyTag key packet type
275+ * @param pubKeyPacket public key packet
276+ * @param locatorHint optional external key locator hint
277+ */
278+ protected SecretKeyPacket (
279+ int keyTag ,
280+ PublicKeyPacket pubKeyPacket ,
281+ byte [] locatorHint )
282+ {
283+ this (keyTag , pubKeyPacket , 0 , 0 , USAGE_EXTERNAL , null , null , null );
284+ this .externalKeyLocatorHint = locatorHint == null ? new byte [0 ] : Arrays .clone (locatorHint );
285+ }
286+
236287 /**
237288 * Construct a {@link SecretKeyPacket}.
238289 * Note: <pre>secKeyData</pre> needs to be prepared by applying encryption/checksum beforehand.
@@ -426,6 +477,27 @@ public byte[] getSecretKeyData()
426477 return secKeyData ;
427478 }
428479
480+ /**
481+ * If the key has external private key material (s2k usage {@link #USAGE_EXTERNAL}), return the locator hint data.
482+ * If the locator hint is empty, it is referred to as "best effort".
483+ * Otherwise, the first octet indicates the type of locator hint.
484+ *
485+ * @see <a href="https://www.ietf.org/archive/id/draft-dkg-openpgp-external-secrets-02.html#name-openpgp-external-secret-key">
486+ * OpenPGP External Secret Key Locator Hint type registry</a>
487+ * @return locator hints data
488+ */
489+ public byte [] getExternalKeyLocatorHint ()
490+ {
491+ if (s2kUsage == USAGE_EXTERNAL )
492+ {
493+ return externalKeyLocatorHint ;
494+ }
495+ else
496+ {
497+ return null ;
498+ }
499+ }
500+
429501 /**
430502 * Return the encoded packet content without packet frame.
431503 * @return encoded packet contents
@@ -443,7 +515,7 @@ public byte[] getEncodedContents()
443515
444516 // conditional parameters
445517 byte [] conditionalParameters = encodeConditionalParameters ();
446- if (pubKeyPacket .getVersion () == PublicKeyPacket .LIBREPGP_5 ||
518+ if (pubKeyPacket .getVersion () == PublicKeyPacket .LIBREPGP_5 ||
447519 (pubKeyPacket .getVersion () == PublicKeyPacket .VERSION_6 && s2kUsage != USAGE_NONE ))
448520 {
449521 pOut .write (conditionalParameters .length );
@@ -476,6 +548,10 @@ private byte[] encodeConditionalParameters()
476548 {
477549 ByteArrayOutputStream conditionalParameters = new ByteArrayOutputStream ();
478550 boolean hasS2KSpecifier = s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD ;
551+ if (s2kUsage == USAGE_EXTERNAL )
552+ {
553+ return getExternalKeyLocatorHint ();
554+ }
479555
480556 if (hasS2KSpecifier )
481557 {
0 commit comments