55import java .io .IOException ;
66
77import org .bouncycastle .util .Arrays ;
8- import org .bouncycastle .util .io .Streams ;
98
109/**
1110 * Base class for OpenPGP secret (primary) keys.
@@ -64,14 +63,25 @@ public class SecretKeyPacket
6463 * Users should migrate to AEAD with all due speed.
6564 */
6665 public static final int USAGE_AEAD = 0xfd ;
67-
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+
6877 private PublicKeyPacket pubKeyPacket ;
6978 private byte [] secKeyData ;
7079 private int s2kUsage ;
7180 private int encAlgorithm ;
7281 private int aeadAlgorithm ;
7382 private S2K s2k ;
7483 private byte [] iv ;
84+ private byte [] externalKeyLocatorHint ;
7585
7686 /**
7787 * Parse a primary OpenPGP secret key packet from the given OpenPGP {@link BCPGInputStream}.
@@ -159,13 +169,19 @@ public class SecretKeyPacket
159169 s2kUsage = in .read ();
160170
161171 int conditionalParameterLength = -1 ;
162- if (version == PublicKeyPacket .LIBREPGP_5 ||
172+ if (version == PublicKeyPacket .LIBREPGP_5 ||
163173 (version == PublicKeyPacket .VERSION_6 && s2kUsage != USAGE_NONE ))
164174 {
165175 // TODO: Use length to parse unknown parameters
166176 conditionalParameterLength = in .read ();
167177 }
168178
179+ if (s2kUsage == USAGE_EXTERNAL )
180+ {
181+ externalKeyLocatorHint = in .readAll ();
182+ return ;
183+ }
184+
169185 if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD )
170186 {
171187 encAlgorithm = in .read ();
@@ -224,7 +240,7 @@ public class SecretKeyPacket
224240 if (encAlgorithm < 7 )
225241 {
226242 iv = new byte [8 ];
227- }
243+ }
228244 else
229245 {
230246 iv = new byte [16 ];
@@ -233,7 +249,7 @@ public class SecretKeyPacket
233249 }
234250 }
235251 }
236-
252+
237253 if (version == PublicKeyPacket .LIBREPGP_5 )
238254 {
239255 long keyOctetCount = ((long ) in .read () << 24 ) | ((long ) in .read () << 16 ) | ((long ) in .read () << 8 ) | in .read ();
@@ -252,6 +268,40 @@ public class SecretKeyPacket
252268 }
253269 }
254270
271+ /**
272+ * Create a SecretKeyPacket representing an external secret key ({@link #USAGE_EXTERNAL}).
273+ *
274+ * @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
275+ * OpenPGP External Secret Keys</a>
276+ * @param pubKeyPacket public key packet
277+ * @param locatorHint optional external key locator hint
278+ */
279+ public SecretKeyPacket (
280+ PublicKeyPacket pubKeyPacket ,
281+ byte [] locatorHint )
282+ {
283+ this (SECRET_KEY , pubKeyPacket , locatorHint );
284+ }
285+
286+
287+ /**
288+ * Create a SecretKeyPacket representing an external secret key ({@link #USAGE_EXTERNAL}).
289+ *
290+ * @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
291+ * OpenPGP External Secret Keys</a>
292+ * @param keyTag key packet type
293+ * @param pubKeyPacket public key packet
294+ * @param locatorHint optional external key locator hint
295+ */
296+ protected SecretKeyPacket (
297+ int keyTag ,
298+ PublicKeyPacket pubKeyPacket ,
299+ byte [] locatorHint )
300+ {
301+ this (keyTag , pubKeyPacket , 0 , 0 , USAGE_EXTERNAL , null , null , null );
302+ this .externalKeyLocatorHint = locatorHint == null ? new byte [0 ] : Arrays .clone (locatorHint );
303+ }
304+
255305 /**
256306 * Construct a {@link SecretKeyPacket}.
257307 * Note: <pre>secKeyData</pre> needs to be prepared by applying encryption/checksum beforehand.
@@ -445,6 +495,27 @@ public byte[] getSecretKeyData()
445495 return secKeyData ;
446496 }
447497
498+ /**
499+ * If the key has external private key material (s2k usage {@link #USAGE_EXTERNAL}), return the locator hint data.
500+ * If the locator hint is empty, it is referred to as "best effort".
501+ * Otherwise, the first octet indicates the type of locator hint.
502+ *
503+ * @see <a href="https://www.ietf.org/archive/id/draft-dkg-openpgp-external-secrets-02.html#name-openpgp-external-secret-key">
504+ * OpenPGP External Secret Key Locator Hint type registry</a>
505+ * @return locator hints data
506+ */
507+ public byte [] getExternalKeyLocatorHint ()
508+ {
509+ if (s2kUsage == USAGE_EXTERNAL )
510+ {
511+ return externalKeyLocatorHint ;
512+ }
513+ else
514+ {
515+ return null ;
516+ }
517+ }
518+
448519 /**
449520 * Return the encoded packet content without packet frame.
450521 * @return encoded packet contents
@@ -462,7 +533,7 @@ public byte[] getEncodedContents()
462533
463534 // conditional parameters
464535 byte [] conditionalParameters = encodeConditionalParameters ();
465- if (pubKeyPacket .getVersion () == PublicKeyPacket .LIBREPGP_5 ||
536+ if (pubKeyPacket .getVersion () == PublicKeyPacket .LIBREPGP_5 ||
466537 (pubKeyPacket .getVersion () == PublicKeyPacket .VERSION_6 && s2kUsage != USAGE_NONE ))
467538 {
468539 pOut .write (conditionalParameters .length );
@@ -495,6 +566,10 @@ private byte[] encodeConditionalParameters()
495566 {
496567 ByteArrayOutputStream conditionalParameters = new ByteArrayOutputStream ();
497568 boolean hasS2KSpecifier = s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD ;
569+ if (s2kUsage == USAGE_EXTERNAL )
570+ {
571+ return getExternalKeyLocatorHint ();
572+ }
498573
499574 if (hasS2KSpecifier )
500575 {
0 commit comments