Skip to content

Commit e76d843

Browse files
committed
Merge branch '1787-v6-signature' into 'main'
#1787 Implement generation of OpenPGP v6 Signatures See merge request root/bc-java!21
2 parents 4472df4 + 19ecb09 commit e76d843

File tree

7 files changed

+574
-59
lines changed

7 files changed

+574
-59
lines changed

pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureCreationTime.java

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public SignatureCreationTime(
4141
super(SignatureSubpacketTags.CREATION_TIME, critical, false, timeToBytes(date));
4242
}
4343

44+
public SignatureCreationTime(
45+
Date date)
46+
{
47+
this(true, date);
48+
}
49+
4450
public Date getTime()
4551
{
4652
long time = Utils.timeFromBytes(data);

pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ public int getKeyAlgorithm()
210210
}
211211

212212
/**
213-
* Return true, if the signature is contains any signatures that follow.
214-
* An bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself
213+
* Return true, if the signature contains any signatures that follow.
214+
* A bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself
215215
* and its corresponding signature (it is an attestation for contained signatures).
216216
*
217217
* @return true if containing, false otherwise

pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.bouncycastle.bcpg.MPInteger;
1717
import org.bouncycastle.bcpg.Packet;
1818
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
19+
import org.bouncycastle.bcpg.PublicKeyPacket;
1920
import org.bouncycastle.bcpg.SignaturePacket;
2021
import org.bouncycastle.bcpg.SignatureSubpacket;
2122
import org.bouncycastle.bcpg.TrustPacket;
@@ -310,6 +311,17 @@ public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPP
310311
{
311312
throw new PGPException("Illegal signature type 0xFF provided.");
312313
}
314+
315+
if (getVersion() == SignaturePacket.VERSION_6 && pubKey.getVersion() != PublicKeyPacket.VERSION_6)
316+
{
317+
throw new PGPException("MUST NOT verify v6 signature with non-v6 key.");
318+
}
319+
320+
if (getVersion() == SignaturePacket.VERSION_4 && pubKey.getVersion() != PublicKeyPacket.VERSION_4)
321+
{
322+
throw new PGPException("MUST NOT verify v4 signature with non-v4 key.");
323+
}
324+
313325
PGPContentVerifierBuilder verifierBuilder = createVerifierProvider(verifierBuilderProvider);
314326

315327
init(verifierBuilder.build(pubKey));

pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java

+131-46
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
import java.math.BigInteger;
66
import java.util.Date;
77

8+
import org.bouncycastle.bcpg.HashUtils;
89
import org.bouncycastle.bcpg.MPInteger;
910
import org.bouncycastle.bcpg.OnePassSignaturePacket;
1011
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
1112
import org.bouncycastle.bcpg.SignaturePacket;
1213
import org.bouncycastle.bcpg.SignatureSubpacket;
1314
import org.bouncycastle.bcpg.SignatureSubpacketTags;
15+
import org.bouncycastle.bcpg.sig.IssuerFingerprint;
1416
import org.bouncycastle.bcpg.sig.IssuerKeyID;
1517
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
18+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
1619
import org.bouncycastle.openpgp.operator.PGPContentSigner;
1720
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
1821
import org.bouncycastle.util.Arrays;
@@ -31,6 +34,7 @@ public class PGPSignatureGenerator
3134
//private int providedKeyAlgorithm = -1;
3235
private int providedKeyAlgorithm = -1;
3336
private PGPPublicKey signingPubKey;
37+
private byte[] salt;
3438

3539
/**
3640
* Create a version 4 signature generator built on the passed in contentSignerBuilder.
@@ -88,8 +92,8 @@ public PGPSignatureGenerator(
8892
/**
8993
* Initialise the generator for signing.
9094
*
91-
* @param signatureType
92-
* @param key
95+
* @param signatureType type of signature
96+
* @param key private signing key
9397
* @throws PGPException
9498
*/
9599
public void init(
@@ -106,12 +110,37 @@ public void init(
106110
sigType = contentSigner.getType();
107111
lastb = 0;
108112

109-
// if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
110-
// {
111-
// throw new PGPException("key algorithm mismatch");
112-
// }
113+
if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
114+
{
115+
throw new PGPException("key algorithm mismatch");
116+
}
117+
118+
if (key.getPublicKeyPacket().getVersion() != version)
119+
{
120+
throw new PGPException("Key version mismatch.");
121+
}
122+
123+
if (version == SignaturePacket.VERSION_6)
124+
{
125+
int saltSize = HashUtils.getV6SignatureSaltSizeInBytes(contentSigner.getHashAlgorithm());
126+
salt = new byte[saltSize];
127+
CryptoServicesRegistrar.getSecureRandom().nextBytes(salt);
128+
try
129+
{
130+
sigOut.write(salt);
131+
}
132+
catch (IOException e)
133+
{
134+
throw new PGPException("Cannot update signature with salt.");
135+
}
136+
}
113137
}
114138

139+
/**
140+
* Set the hashed signature subpackets.
141+
* Hashed signature subpackets are covered by the signature.
142+
* @param hashedPcks hashed signature subpackets
143+
*/
115144
public void setHashedSubpackets(
116145
PGPSignatureSubpacketVector hashedPcks)
117146
{
@@ -124,6 +153,11 @@ public void setHashedSubpackets(
124153
hashed = hashedPcks.toSubpacketArray();
125154
}
126155

156+
/**
157+
* Set the unhashed signature subpackets.
158+
* Unhashed signature subpackets are not covered by the signature.
159+
* @param unhashedPcks unhashed signature subpackets
160+
*/
127161
public void setUnhashedSubpackets(
128162
PGPSignatureSubpacketVector unhashedPcks)
129163
{
@@ -147,7 +181,26 @@ public PGPOnePassSignature generateOnePassVersion(
147181
boolean isNested)
148182
throws PGPException
149183
{
150-
return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
184+
if (version == SignaturePacket.VERSION_6)
185+
{
186+
return new PGPOnePassSignature(v6OPSPacket(isNested));
187+
}
188+
else
189+
{
190+
return new PGPOnePassSignature(v3OPSPacket(isNested));
191+
}
192+
}
193+
194+
private OnePassSignaturePacket v3OPSPacket(boolean isNested)
195+
{
196+
return new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(),
197+
contentSigner.getKeyID(), isNested);
198+
}
199+
200+
private OnePassSignaturePacket v6OPSPacket(boolean isNested)
201+
{
202+
return new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(),
203+
salt, signingPubKey.getFingerprint(), isNested);
151204
}
152205

153206
/**
@@ -159,66 +212,51 @@ public PGPOnePassSignature generateOnePassVersion(
159212
public PGPSignature generate()
160213
throws PGPException
161214
{
162-
MPInteger[] sigValues;
163-
int version = 4;
164-
ByteArrayOutputStream sOut = new ByteArrayOutputStream();
165-
SignatureSubpacket[] hPkts, unhPkts;
166-
167-
if (packetNotPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
168-
{
169-
hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date()));
170-
}
171-
else
172-
{
173-
hPkts = hashed;
174-
}
175-
176-
if (packetNotPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && packetNotPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
177-
{
178-
unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
179-
}
180-
else
181-
{
182-
unhPkts = unhashed;
183-
}
215+
prepareSignatureSubpackets();
184216

217+
ByteArrayOutputStream sOut = new ByteArrayOutputStream();
185218
try
186219
{
220+
// hash the "header"
187221
sOut.write((byte)version);
188222
sOut.write((byte)sigType);
189223
sOut.write((byte)contentSigner.getKeyAlgorithm());
190224
sOut.write((byte)contentSigner.getHashAlgorithm());
191225

226+
// hash signature subpackets
192227
ByteArrayOutputStream hOut = new ByteArrayOutputStream();
193-
194-
for (int i = 0; i != hPkts.length; i++)
228+
for (int i = 0; i != hashed.length; i++)
195229
{
196-
hPkts[i].encode(hOut);
230+
hashed[i].encode(hOut);
197231
}
198-
199232
byte[] data = hOut.toByteArray();
200233

234+
if (version == SignaturePacket.VERSION_6)
235+
{
236+
sOut.write((byte) (data.length >> 24));
237+
sOut.write((byte) (data.length >> 16));
238+
}
201239
sOut.write((byte)(data.length >> 8));
202240
sOut.write((byte)data.length);
203241
sOut.write(data);
204-
byte[] hData = sOut.toByteArray();
205242

243+
// hash the "footer"
244+
int dataLen = sOut.toByteArray().length;
206245
sOut.write((byte)version);
207246
sOut.write((byte)0xff);
208-
sOut.write((byte)(hData.length >> 24));
209-
sOut.write((byte)(hData.length >> 16));
210-
sOut.write((byte)(hData.length >> 8));
211-
sOut.write((byte)(hData.length));
247+
sOut.write((byte)(dataLen >> 24));
248+
sOut.write((byte)(dataLen >> 16));
249+
sOut.write((byte)(dataLen >> 8));
250+
sOut.write((byte)(dataLen));
212251
}
213252
catch (IOException e)
214253
{
215254
throw new PGPException("exception encoding hashed data.", e);
216255
}
217256

218-
219257
byte[] trailer = sOut.toByteArray();
220-
221258
blockUpdate(trailer, 0, trailer.length);
259+
MPInteger[] sigValues;
222260
switch (contentSigner.getKeyAlgorithm())
223261
{
224262
case PublicKeyAlgorithmTags.RSA_SIGN:
@@ -253,16 +291,63 @@ public PGPSignature generate()
253291
fingerPrint[0] = digest[0];
254292
fingerPrint[1] = digest[1];
255293

256-
if (sigValues != null)
294+
SignaturePacket sigPckt;
295+
if (sigValues != null) // MPI encoding
257296
{
258-
return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
259-
contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues));
297+
sigPckt = new SignaturePacket(version, sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
298+
contentSigner.getHashAlgorithm(), hashed, unhashed, fingerPrint, sigValues, salt);
260299
}
261-
else
300+
else // native encoding
262301
{
263302
// Ed25519, Ed448 use raw encoding instead of MPI
264-
return new PGPSignature(new SignaturePacket(4, sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
265-
contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, contentSigner.getSignature(), null));
303+
304+
sigPckt = new SignaturePacket(version, sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
305+
contentSigner.getHashAlgorithm(), hashed, unhashed, fingerPrint, contentSigner.getSignature(), salt);
306+
}
307+
return new PGPSignature(sigPckt);
308+
}
309+
310+
protected void prepareSignatureSubpackets()
311+
throws PGPException
312+
{
313+
switch (version)
314+
{
315+
case SignaturePacket.VERSION_4:
316+
case SignaturePacket.VERSION_5:
317+
{
318+
// Insert hashed signature creation time if missing
319+
if (packetNotPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
320+
{
321+
hashed = insertSubpacket(hashed, new SignatureCreationTime(true, new Date()));
322+
}
323+
324+
// Insert unhashed issuer key-ID if missing
325+
if (packetNotPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && packetNotPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
326+
{
327+
unhashed = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
328+
}
329+
330+
break;
331+
}
332+
333+
case SignaturePacket.VERSION_6:
334+
{
335+
// Insert hashed signature creation time if missing
336+
if (packetNotPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
337+
{
338+
hashed = insertSubpacket(hashed, new SignatureCreationTime(true, new Date()));
339+
}
340+
341+
// Insert hashed issuer fingerprint subpacket if missing
342+
if (packetNotPresent(hashed, SignatureSubpacketTags.ISSUER_FINGERPRINT) &&
343+
packetNotPresent(unhashed, SignatureSubpacketTags.ISSUER_FINGERPRINT) &&
344+
signingPubKey != null)
345+
{
346+
hashed = insertSubpacket(hashed, new IssuerFingerprint(true, version, signingPubKey.getFingerprint()));
347+
}
348+
349+
break;
350+
}
266351
}
267352
}
268353

pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -551,12 +551,13 @@ public void performTest()
551551

552552
int[] criticalHashed = hashedPcks.getCriticalTags();
553553

554-
if (criticalHashed.length != 1)
554+
// SignerUserID and SignatureCreationTime are critical.
555+
if (criticalHashed.length != 2)
555556
{
556557
fail("wrong number of critical packets found.");
557558
}
558559

559-
if (criticalHashed[0] != SignatureSubpacketTags.SIGNER_USER_ID)
560+
if (criticalHashed[1] != SignatureSubpacketTags.SIGNER_USER_ID)
560561
{
561562
fail("wrong critical packet found in tag list.");
562563
}

0 commit comments

Comments
 (0)