Skip to content

Commit

Permalink
github bcgit#636 added lightweight EdDSA verification
Browse files Browse the repository at this point in the history
  • Loading branch information
dghgit committed Mar 24, 2020
1 parent 8d5a427 commit ab48c91
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.Wrapper;
Expand Down Expand Up @@ -32,9 +35,11 @@
import org.bouncycastle.crypto.signers.DSADigestSigner;
import org.bouncycastle.crypto.signers.DSASigner;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.crypto.signers.RSADigestSigner;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.util.Arrays;

class BcImplProvider
{
Expand Down Expand Up @@ -78,6 +83,8 @@ static Signer createSigner(int keyAlgorithm, int hashAlgorithm)
return new DSADigestSigner(new DSASigner(), createDigest(hashAlgorithm));
case PublicKeyAlgorithmTags.ECDSA:
return new DSADigestSigner(new ECDSASigner(), createDigest(hashAlgorithm));
case PublicKeyAlgorithmTags.EDDSA:
return new EdDsaSigner(new Ed25519Signer(), createDigest(hashAlgorithm));
default:
throw new PGPException("cannot recognise keyAlgorithm: " + keyAlgorithm);
}
Expand Down Expand Up @@ -170,4 +177,61 @@ static AsymmetricBlockCipher createPublicKeyCipher(int encAlgorithm)

return c;
}

private static class EdDsaSigner
implements Signer
{
private final Signer signer;
private final Digest digest;
private final byte[] digBuf;

EdDsaSigner(Signer signer, Digest digest)
{
this.signer = signer;
this.digest = digest;
this.digBuf = new byte[digest.getDigestSize()];
}

public void init(boolean forSigning, CipherParameters param)
{
this.signer.init(forSigning, param);
this.digest.reset();
}

public void update(byte b)
{
this.digest.update(b);
}

public void update(byte[] in, int off, int len)
{
this.digest.update(in, off, len);
}

public byte[] generateSignature()
throws CryptoException, DataLengthException
{
digest.doFinal(digBuf, 0);

signer.update(digBuf, 0, digBuf.length);

return signer.generateSignature();
}

public boolean verifySignature(byte[] signature)
{
digest.doFinal(digBuf, 0);

signer.update(digBuf, 0, digBuf.length);

return signer.verifySignature(signature);
}

public void reset()
{
Arrays.clear(digBuf);
signer.reset();
digest.reset();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
import org.bouncycastle.bcpg.ECPublicBCPGKey;
import org.bouncycastle.bcpg.ECSecretBCPGKey;
import org.bouncycastle.bcpg.EdDSAPublicBCPGKey;
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
import org.bouncycastle.bcpg.HashAlgorithmTags;
Expand All @@ -33,6 +34,7 @@
import org.bouncycastle.crypto.params.ECNamedDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.params.ElGamalParameters;
import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
Expand All @@ -45,6 +47,7 @@
import org.bouncycastle.openpgp.PGPKdfParameters;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.util.BigIntegers;

public class BcPGPKeyConverter
{
Expand Down Expand Up @@ -223,6 +226,13 @@ public AsymmetricKeyParameter getPublicKey(PGPPublicKey publicKey)

return new ECPublicKeyParameters(BcUtil.decodePoint(ecPub.getEncodedPoint(), x9.getCurve()),
new ECNamedDomainParameters(ecPub.getCurveOID(), x9.getCurve(), x9.getG(), x9.getN(), x9.getH()));
case PublicKeyAlgorithmTags.EDDSA:
EdDSAPublicBCPGKey eddsaK = (EdDSAPublicBCPGKey)publicPk.getKey();

byte[] enc = BigIntegers.asUnsignedByteArray(eddsaK.getEncodedPoint());

// skip leading 0x04
return new Ed25519PublicKeyParameters(enc, 1);
default:
throw new PGPException("unknown public key algorithm encountered");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Security;
import java.security.SignatureException;
import java.util.Iterator;

import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
Expand All @@ -23,12 +27,15 @@
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTest;

public class PGPClearSignedSignatureTest
Expand Down Expand Up @@ -179,6 +186,43 @@ public class PGPClearSignedSignatureTest
+ "=84Nd\r"
+ "-----END PGP SIGNATURE-----\r\n";

final String edDsaSignedMessage =
"-----BEGIN PGP SIGNED MESSAGE-----\n" +
"Hash: SHA256\n" +
"\n" +
"person: First Person\n" +
"address: St James Street\n" +
"address: Burnley\n" +
"address: UK\n" +
"phone: +44 282 420469\n" +
"nic-hdl: FP1-TEST\n" +
"mnt-by: OWNER-MNT\n" +
"source: TEST\n" +
"-----BEGIN PGP SIGNATURE-----\n" +
"Comment: GPGTools - http://gpgtools.org\n" +
"\n" +
"iHUEARYIAB0WIQRiNGNQyuJDPiQAHXKU+mLDZIGuNAUCXiWfSQAKCRCU+mLDZIGu\n" +
"NIiqAQD+sksm61T9mYmoLRPhV+D3jSg2IE19id3WyjaH0vCwXQEA6v5xpZQ7AXQe\n" +
"vbSHvSrRBNBSAUuJfIYQLsAf6l80MAI=\n" +
"=pQ32\n" +
"-----END PGP SIGNATURE-----";

final String edDsaPublicKey =
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Comment: GPGTools - http://gpgtools.org\n" +
"\n" +
"mDMEXiWeSRYJKwYBBAHaRw8BAQdAEo+4wi/WI0xtbQF+PoIGxaDFJw23d+3w/ov+\n" +
"go85qdi0GVRlc3QgVXNlciA8dGVzdEByaXBlLm5ldD6IkAQTFggAOBYhBGI0Y1DK\n" +
"4kM+JAAdcpT6YsNkga40BQJeJZ5JAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA\n" +
"AAoJEJT6YsNkga40WLEBAKGMQaC1zKbmuD5Pav0ssuhxaznoMbuZqJ45VNiGKzLE\n" +
"AQCGFbH+9pAvcEuorOa180+GLDZOpVYgQy40KsGaQgC5Drg4BF4lnkkSCisGAQQB\n" +
"l1UBBQEBB0DFLFEhV9RSM92t1LwC/ClmND/Yw9P0a3paC2XGzTNTAwMBCAeIeAQY\n" +
"FggAIBYhBGI0Y1DK4kM+JAAdcpT6YsNkga40BQJeJZ5JAhsMAAoJEJT6YsNkga40\n" +
"LbQBALZ5BaNX5OxdS++mzwdWAVLZXAPRDFr6Q2otdxbnR0FTAP4ok4PiOpe1BfdF\n" +
"itv84V9zda3NL6zJLhR3kewd30UDCA==\n" +
"=Dxc9\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";

public String getName()
{
return "PGPClearSignedSignature";
Expand Down Expand Up @@ -343,6 +387,146 @@ private void generateTest(
messageTest(new String(bOut.toByteArray()), type);
}

private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line)
{
int end = line.length - 1;

while (end >= 0 && isWhiteSpace(line[end]))
{
end--;
}

return end + 1;
}

private void edDsaTest()
throws Exception
{
ArmoredInputStream aIn = new ArmoredInputStream(new ByteArrayInputStream(Strings.toByteArray(edDsaPublicKey)));

PGPPublicKeyRing pubKeyRing = new PGPPublicKeyRing(aIn, new JcaKeyFingerprintCalculator());

isTrue(areEqual(Hex.decode("6234 6350 CAE2 433E 2400 1D72 94FA 62C3 6481 AE34"), pubKeyRing.getPublicKey().getFingerprint()));

aIn = new ArmoredInputStream(new ByteArrayInputStream(Strings.toByteArray(edDsaSignedMessage)));

ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, aIn);
byte[] lineSep = Strings.toByteArray("\n");

if (lookAhead != -1 && aIn.isClearText())
{
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);

while (lookAhead != -1 && aIn.isClearText())
{
lookAhead = readInputLine(lineOut, lookAhead, aIn);

line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
}

JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
PGPSignature sig = p3.get(0);

PGPPublicKey publicKey = pubKeyRing.getPublicKey(sig.getKeyID());
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), publicKey);

InputStream sigIn = new ByteArrayInputStream(out.toByteArray());

lookAhead = readInputLine(lineOut, sigIn);

processLine(sig, lineOut.toByteArray());

if (lookAhead != -1)
{
do
{
lookAhead = readInputLine(lineOut, lookAhead, sigIn);

sig.update((byte)'\r');
sig.update((byte)'\n');

processLine(sig, lineOut.toByteArray());
}
while (lookAhead != -1);
}

sigIn.close();

isTrue(sig.verify());
}

private void edDsaBcTest()
throws Exception
{
ArmoredInputStream aIn = new ArmoredInputStream(new ByteArrayInputStream(Strings.toByteArray(edDsaPublicKey)));

PGPPublicKeyRing pubKeyRing = new PGPPublicKeyRing(aIn, new JcaKeyFingerprintCalculator());

isTrue(areEqual(Hex.decode("6234 6350 CAE2 433E 2400 1D72 94FA 62C3 6481 AE34"), pubKeyRing.getPublicKey().getFingerprint()));

aIn = new ArmoredInputStream(new ByteArrayInputStream(Strings.toByteArray(edDsaSignedMessage)));

ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, aIn);
byte[] lineSep = Strings.toByteArray("\n");

if (lookAhead != -1 && aIn.isClearText())
{
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);

while (lookAhead != -1 && aIn.isClearText())
{
lookAhead = readInputLine(lineOut, lookAhead, aIn);

line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
}

JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
PGPSignature sig = p3.get(0);

PGPPublicKey publicKey = pubKeyRing.getPublicKey(sig.getKeyID());
sig.init(new BcPGPContentVerifierBuilderProvider(), publicKey);

InputStream sigIn = new ByteArrayInputStream(out.toByteArray());

lookAhead = readInputLine(lineOut, sigIn);

processLine(sig, lineOut.toByteArray());

if (lookAhead != -1)
{
do
{
lookAhead = readInputLine(lineOut, lookAhead, sigIn);

sig.update((byte)'\r');
sig.update((byte)'\n');

processLine(sig, lineOut.toByteArray());
}
while (lookAhead != -1);
}

sigIn.close();

isTrue("sig failed", sig.verify());
}

private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
throws IOException
{
Expand Down Expand Up @@ -449,11 +633,16 @@ public void performTest()
generateTest(nlOnlyMessage, "\\r");
generateTest(crOnlyMessage, "\\n");
generateTest(crNlMessage, "\\r\\n");

edDsaTest();
edDsaBcTest();
}

public static void main(
String[] args)
{
Security.addProvider(new BouncyCastleProvider());

runTest(new PGPClearSignedSignatureTest());
}
}

0 comments on commit ab48c91

Please sign in to comment.