Skip to content

Commit 19ecb09

Browse files
author
gefeili
committed
#1787 Implement generation of OpenPGP v6 Signatures
1 parent 6114940 commit 19ecb09

File tree

7 files changed

+591
-62
lines changed

7 files changed

+591
-62
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public SignatureCreationTime(
3636
super(SignatureSubpacketTags.CREATION_TIME, critical, false, timeToBytes(date));
3737
}
3838

39+
public SignatureCreationTime(
40+
Date date)
41+
{
42+
this(true, date);
43+
}
44+
3945
public Date getTime()
4046
{
4147
long time = Utils.timeFromBytes(data);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ public int getKeyAlgorithm()
200200
}
201201

202202
/**
203-
* Return true, if the signature is contains any signatures that follow.
204-
* An bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself
203+
* Return true, if the signature contains any signatures that follow.
204+
* A bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself
205205
* and its corresponding signature (it is an attestation for contained signatures).
206206
*
207207
* @return true if containing, false otherwise

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

Lines changed: 21 additions & 1 deletion
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;
@@ -156,6 +157,17 @@ public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPP
156157
{
157158
throw new PGPException("Illegal signature type 0xFF provided.");
158159
}
160+
161+
if (getVersion() == SignaturePacket.VERSION_6 && pubKey.getVersion() != PublicKeyPacket.VERSION_6)
162+
{
163+
throw new PGPException("MUST NOT verify v6 signature with non-v6 key.");
164+
}
165+
166+
if (getVersion() == SignaturePacket.VERSION_4 && pubKey.getVersion() != PublicKeyPacket.VERSION_4)
167+
{
168+
throw new PGPException("MUST NOT verify v4 signature with non-v4 key.");
169+
}
170+
159171
PGPContentVerifierBuilder verifierBuilder = createVerifierProvider(verifierBuilderProvider);
160172

161173
init(verifierBuilder.build(pubKey));
@@ -195,10 +207,18 @@ private void checkSaltSize()
195207
}
196208

197209
private void updateWithSalt()
210+
throws PGPException
198211
{
199212
if (getVersion() == SignaturePacket.VERSION_6)
200213
{
201-
update(sigPck.getSalt());
214+
try
215+
{
216+
sigOut.write(sigPck.getSalt());
217+
}
218+
catch (IOException e)
219+
{
220+
throw new PGPException("Could not update with salt.", e);
221+
}
202222
}
203223
}
204224

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

Lines changed: 131 additions & 46 deletions
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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,12 +542,13 @@ public void performTest()
542542

543543
int[] criticalHashed = hashedPcks.getCriticalTags();
544544

545-
if (criticalHashed.length != 1)
545+
// SignerUserID and SignatureCreationTime are critical.
546+
if (criticalHashed.length != 2)
546547
{
547548
fail("wrong number of critical packets found.");
548549
}
549550

550-
if (criticalHashed[0] != SignatureSubpacketTags.SIGNER_USER_ID)
551+
if (criticalHashed[1] != SignatureSubpacketTags.SIGNER_USER_ID)
551552
{
552553
fail("wrong critical packet found in tag list.");
553554
}

0 commit comments

Comments
 (0)