Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 81 additions & 6 deletions pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.io.IOException;

import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.io.Streams;

/**
* Base class for OpenPGP secret (primary) keys.
Expand Down Expand Up @@ -64,14 +63,25 @@ public class SecretKeyPacket
* Users should migrate to AEAD with all due speed.
*/
public static final int USAGE_AEAD = 0xfd;


/**
* Externally-backed secret key material.
* S2K-usage octet indicating that the secret key material is stored externally, e.g. on a hardware device.
* The draft specification is an alternative to GnuPGs proprietary {@link S2K#GNU_DUMMY_S2K} mechanism.
*
* @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
* OpenPGP External Secret Keys</a>
*/
public static final int USAGE_EXTERNAL = 0xfc;

private PublicKeyPacket pubKeyPacket;
private byte[] secKeyData;
private int s2kUsage;
private int encAlgorithm;
private int aeadAlgorithm;
private S2K s2k;
private byte[] iv;
private byte[] externalKeyLocatorHint;

/**
* Parse a primary OpenPGP secret key packet from the given OpenPGP {@link BCPGInputStream}.
Expand Down Expand Up @@ -159,13 +169,19 @@ public class SecretKeyPacket
s2kUsage = in.read();

int conditionalParameterLength = -1;
if (version == PublicKeyPacket.LIBREPGP_5 ||
if (version == PublicKeyPacket.LIBREPGP_5 ||
(version == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE))
{
// TODO: Use length to parse unknown parameters
conditionalParameterLength = in.read();
}

if (s2kUsage == USAGE_EXTERNAL)
{
externalKeyLocatorHint = in.readAll();
return;
}

if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD)
{
encAlgorithm = in.read();
Expand Down Expand Up @@ -224,7 +240,7 @@ public class SecretKeyPacket
if (encAlgorithm < 7)
{
iv = new byte[8];
}
}
else
{
iv = new byte[16];
Expand All @@ -233,7 +249,7 @@ public class SecretKeyPacket
}
}
}

if (version == PublicKeyPacket.LIBREPGP_5)
{
long keyOctetCount = ((long) in.read() << 24) | ((long) in.read() << 16) | ((long) in.read() << 8) | in.read();
Expand All @@ -252,6 +268,40 @@ public class SecretKeyPacket
}
}

/**
* Create a SecretKeyPacket representing an external secret key ({@link #USAGE_EXTERNAL}).
*
* @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
* OpenPGP External Secret Keys</a>
* @param pubKeyPacket public key packet
* @param locatorHint optional external key locator hint
*/
public SecretKeyPacket(
PublicKeyPacket pubKeyPacket,
byte[] locatorHint)
{
this(SECRET_KEY, pubKeyPacket, locatorHint);
}


/**
* Create a SecretKeyPacket representing an external secret key ({@link #USAGE_EXTERNAL}).
*
* @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
* OpenPGP External Secret Keys</a>
* @param keyTag key packet type
* @param pubKeyPacket public key packet
* @param locatorHint optional external key locator hint
*/
protected SecretKeyPacket(
int keyTag,
PublicKeyPacket pubKeyPacket,
byte[] locatorHint)
{
this(keyTag, pubKeyPacket, 0, 0, USAGE_EXTERNAL, null, null, null);
this.externalKeyLocatorHint = locatorHint == null ? new byte[0] : Arrays.clone(locatorHint);
}

/**
* Construct a {@link SecretKeyPacket}.
* Note: <pre>secKeyData</pre> needs to be prepared by applying encryption/checksum beforehand.
Expand Down Expand Up @@ -445,6 +495,27 @@ public byte[] getSecretKeyData()
return secKeyData;
}

/**
* If the key has external private key material (s2k usage {@link #USAGE_EXTERNAL}), return the locator hint data.
* If the locator hint is empty, it is referred to as "best effort".
* Otherwise, the first octet indicates the type of locator hint.
*
* @see <a href="https://www.ietf.org/archive/id/draft-dkg-openpgp-external-secrets-02.html#name-openpgp-external-secret-key">
* OpenPGP External Secret Key Locator Hint type registry</a>
* @return locator hints data
*/
public byte[] getExternalKeyLocatorHint()
{
if (s2kUsage == USAGE_EXTERNAL)
{
return externalKeyLocatorHint;
}
else
{
return null;
}
}

/**
* Return the encoded packet content without packet frame.
* @return encoded packet contents
Expand All @@ -462,7 +533,7 @@ public byte[] getEncodedContents()

// conditional parameters
byte[] conditionalParameters = encodeConditionalParameters();
if (pubKeyPacket.getVersion() == PublicKeyPacket.LIBREPGP_5 ||
if (pubKeyPacket.getVersion() == PublicKeyPacket.LIBREPGP_5 ||
(pubKeyPacket.getVersion() == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE))
{
pOut.write(conditionalParameters.length);
Expand Down Expand Up @@ -495,6 +566,10 @@ private byte[] encodeConditionalParameters()
{
ByteArrayOutputStream conditionalParameters = new ByteArrayOutputStream();
boolean hasS2KSpecifier = s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD;
if (s2kUsage == USAGE_EXTERNAL)
{
return getExternalKeyLocatorHint();
}

if (hasS2KSpecifier)
{
Expand Down
17 changes: 17 additions & 0 deletions pg/src/main/java/org/bouncycastle/bcpg/SecretSubkeyPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ public class SecretSubkeyPacket
{
super(SECRET_SUBKEY, in, newPacketFormat);
}

/**
* Create a SecretSubkeyPacket which has external private key material.
*
* @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-external-secrets/">
* OpenPGP External Secret Keys</a>
*
* @param publicKeyPacket public key material
* @param locatorHints optional external key locator hints
*/
public SecretSubkeyPacket(
PublicSubkeyPacket publicKeyPacket,
byte[] locatorHints)
{
super(SECRET_SUBKEY, publicKeyPacket, locatorHints);
}

/**
* Create a secret subkey packet.
* If the encryption algorithm is NOT {@link SymmetricKeyAlgorithmTags#NULL},
Expand Down
21 changes: 21 additions & 0 deletions pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,26 @@ public boolean isMasterKey()
*/
public boolean isPrivateKeyEmpty()
{
if (isExternalKey())
{
return true;
}

byte[] secKeyData = secret.getSecretKeyData();

return (secKeyData == null || secKeyData.length < 1);
}

public boolean isExternalKey()
{
return secret.getS2KUsage() == SecretKeyPacket.USAGE_EXTERNAL;
}

public byte[] getExternalKeyLocatorHint()
{
return secret.getExternalKeyLocatorHint();
}

/**
* return the algorithm the key is encrypted with.
*
Expand Down Expand Up @@ -510,6 +525,7 @@ public byte[] getFingerprint()
* <li>{@link SecretKeyPacket#USAGE_CHECKSUM}: Password-protected using malleable CFB (deprecated)</li>
* <li>{@link SecretKeyPacket#USAGE_SHA1}: Password-protected using CFB</li>
* <li>{@link SecretKeyPacket#USAGE_AEAD}: Password-protected using AEAD (recommended)</li>
* <li>{@link SecretKeyPacket#USAGE_EXTERNAL}: Externally-backed private key, e.g. hardware token</li>
* </ul>
*
* @return the key's S2K usage
Expand Down Expand Up @@ -564,6 +580,11 @@ private byte[] extractKeyData(PBESecretKeyDecryptor decryptorFactory)
{
byte[] encData = secret.getSecretKeyData();

if (isExternalKey())
{
throw new PGPException("Key is externally-backed and key-data cannot be extracted.");
}

if (secret.getEncAlgorithm() == SymmetricKeyAlgorithmTags.NULL)
{
return encData;
Expand Down
Loading