- Implement one-shot methods in EdDSAEngine so we don't copy
   the data if all the data is available (ticket #1750)
 - Use EdDSA one-shot methods in DSAEngine
 - Fix API violation if EdDSAEngine object is reused for signing (ticket #1750)
 - Javadocs
This commit is contained in:
zzz
2016-02-03 18:39:49 +00:00
parent 7901784a71
commit 6be7c46038
5 changed files with 361 additions and 64 deletions

View File

@@ -515,15 +515,20 @@ public final class DSAEngine {
if (type == SigType.DSA_SHA1)
return altVerifySigSHA1(signature, data, offset, len, verifyingKey);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(type.getDigestInstance());
else
jsig = java.security.Signature.getInstance(type.getAlgorithmName());
PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
jsig.initVerify(pubKey);
jsig.update(data, offset, len);
boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
byte[] sigbytes = SigUtil.toJavaSig(signature);
boolean rv;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
jsig.initVerify(pubKey);
rv = jsig.verifyOneShot(data, offset, len, sigbytes);
} else {
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initVerify(pubKey);
jsig.update(data, offset, len);
rv = jsig.verify(sigbytes);
}
return rv;
}
@@ -563,15 +568,21 @@ public final class DSAEngine {
if (type.getHashLen() != hashlen)
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
String algo = getRawAlgo(type);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification.
else
jsig = java.security.Signature.getInstance(algo);
jsig.initVerify(pubKey);
jsig.update(hash.getData());
boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
byte[] sigbytes = SigUtil.toJavaSig(signature);
boolean rv;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
// Ignore algo, EdDSAKey includes a hash specification.
EdDSAEngine jsig = new EdDSAEngine();
jsig.initVerify(pubKey);
rv = jsig.verifyOneShot(hash.getData(), sigbytes);
} else {
String algo = getRawAlgo(type);
java.security.Signature jsig = java.security.Signature.getInstance(algo);
jsig.initVerify(pubKey);
jsig.update(hash.getData());
rv = jsig.verify(sigbytes);
}
return rv;
}
@@ -606,15 +617,20 @@ public final class DSAEngine {
if (type == SigType.DSA_SHA1)
return altSignSHA1(data, offset, len, privateKey);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(type.getDigestInstance());
else
jsig = java.security.Signature.getInstance(type.getAlgorithmName());
PrivateKey privKey = SigUtil.toJavaKey(privateKey);
jsig.initSign(privKey, _context.random());
jsig.update(data, offset, len);
return SigUtil.fromJavaSig(jsig.sign(), type);
byte[] sigbytes;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
jsig.initSign(privKey);
sigbytes = jsig.signOneShot(data, offset, len);
} else {
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initSign(privKey, _context.random());
jsig.update(data, offset, len);
sigbytes = jsig.sign();
}
return SigUtil.fromJavaSig(sigbytes, type);
}
/**
@@ -649,14 +665,20 @@ public final class DSAEngine {
if (type.getHashLen() != hashlen)
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification.
else
jsig = java.security.Signature.getInstance(algo);
jsig.initSign(privKey, _context.random());
jsig.update(hash.getData());
return SigUtil.fromJavaSig(jsig.sign(), type);
byte[] sigbytes;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
// Ignore algo, EdDSAKey includes a hash specification.
EdDSAEngine jsig = new EdDSAEngine();
jsig.initSign(privKey);
sigbytes = jsig.signOneShot(hash.getData());
} else {
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initSign(privKey, _context.random());
jsig.update(hash.getData());
sigbytes = jsig.sign();
}
return SigUtil.fromJavaSig(sigbytes, type);
}
/**

View File

@@ -664,7 +664,6 @@ public final class SigUtil {
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for this before you
* add a SigType with bigger signatures.
*
* @param sig length must be even
* @throws IllegalArgumentException if too big
* @since 0.9.25, split out from sigBytesToASN1(byte[])
*/

View File

@@ -2,6 +2,7 @@ package net.i2p.crypto.eddsa;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -9,6 +10,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import net.i2p.crypto.eddsa.math.Curve;
@@ -16,21 +18,68 @@ import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.math.ScalarOps;
/**
* Signing and verification for EdDSA.
*<p>
* The EdDSA sign and verify algorithms do not interact well with
* the Java Signature API, as one or more update() methods must be
* called before sign() or verify(). Using the standard API,
* this implementation must copy and buffer all data passed in
* via update().
*</p><p>
* This implementation offers two ways to avoid this copying,
* but only if all data to be signed or verified is available
* in a single byte array.
*</p><p>
*Option 1:
*</p><ol>
*<li>Call initSign() or initVerify() as usual.
*</li><li>Call setParameter(ONE_SHOT_MOE)
*</li><li>Call update(byte[]) or update(byte[], int, int) exactly once
*</li><li>Call sign() or verify() as usual.
*</li><li>If doing additional one-shot signs or verifies with this object, you must
* call setParameter(ONE_SHOT_MODE) each time
*</li></ol>
*
*<p>
*Option 2:
*</p><ol>
*<li>Call initSign() or initVerify() as usual.
*</li><li>Call one of the signOneShot() or verifyOneShot() methods.
*</li><li>If doing additional one-shot signs or verifies with this object,
* just call signOneShot() or verifyOneShot() again.
*</li></ol>
*
* @since 0.9.15
* @author str4d
*
*/
public final class EdDSAEngine extends Signature {
private MessageDigest digest;
private final ByteArrayOutputStream baos;
private ByteArrayOutputStream baos;
private EdDSAKey key;
private boolean oneShotMode;
private byte[] oneShotBytes;
private int oneShotOffset;
private int oneShotLength;
/**
* To efficiently sign or verify data in one shot, pass this to setParameters()
* after initSign() or initVerify() but BEFORE THE FIRST AND ONLY
* update(data) or update(data, off, len). The data reference will be saved
* and then used in sign() or verify() without copying the data.
* Violate these rules and you will get a SignatureException.
*
* @since 0.9.25
*/
public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec();
private static class OneShotSpec implements AlgorithmParameterSpec {}
/**
* No specific hash requested, allows any EdDSA key.
*/
public EdDSAEngine() {
super("EdDSA");
baos = new ByteArrayOutputStream(256);
}
/**
@@ -42,12 +91,21 @@ public final class EdDSAEngine extends Signature {
this.digest = digest;
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
/**
* @since 0.9.25
*/
private void reset() {
if (digest != null)
digest.reset();
baos.reset();
if (baos != null)
baos.reset();
oneShotMode = false;
oneShotBytes = null;
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
reset();
if (privateKey instanceof EdDSAPrivateKey) {
EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
key = privKey;
@@ -61,21 +119,22 @@ public final class EdDSAEngine extends Signature {
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
digestInitSign(privKey);
} else {
throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass());
}
}
// Preparing for hash
// r = H(h_b,...,h_2b-1,M)
int b = privKey.getParams().getCurve().getField().getb();
digest.update(privKey.getH(), b/8, b/4 - b/8);
} else
throw new InvalidKeyException("cannot identify EdDSA private key.");
private void digestInitSign(EdDSAPrivateKey privKey) {
// Preparing for hash
// r = H(h_b,...,h_2b-1,M)
int b = privKey.getParams().getCurve().getField().getb();
digest.update(privKey.getH(), b/8, b/4 - b/8);
}
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
if (digest != null)
digest.reset();
baos.reset();
reset();
if (publicKey instanceof EdDSAPublicKey) {
key = (EdDSAPublicKey) publicKey;
@@ -88,34 +147,79 @@ public final class EdDSAEngine extends Signature {
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
} else
throw new InvalidKeyException("cannot identify EdDSA public key.");
} else {
throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
}
}
/**
* @throws SignatureException if in one-shot mode
*/
@Override
protected void engineUpdate(byte b) throws SignatureException {
// We need to store the message because it is used in several hashes
// XXX Can this be done more efficiently?
if (oneShotMode)
throw new SignatureException("unsupported in one-shot mode");
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b);
}
/**
* @throws SignatureException if one-shot rules are violated
*/
@Override
protected void engineUpdate(byte[] b, int off, int len)
throws SignatureException {
// We need to store the message because it is used in several hashes
// XXX Can this be done more efficiently?
baos.write(b, off, len);
if (oneShotMode) {
if (oneShotBytes != null)
throw new SignatureException("update() already called");
oneShotBytes = b;
oneShotOffset = off;
oneShotLength = len;
} else {
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b, off, len);
}
}
@Override
protected byte[] engineSign() throws SignatureException {
try {
return x_engineSign();
} finally {
reset();
// must leave the object ready to sign again with
// the same key, as required by the API
EdDSAPrivateKey privKey = (EdDSAPrivateKey) key;
digestInitSign(privKey);
}
}
private byte[] x_engineSign() throws SignatureException {
Curve curve = key.getParams().getCurve();
ScalarOps sc = key.getParams().getScalarOps();
byte[] a = ((EdDSAPrivateKey) key).geta();
byte[] message = baos.toByteArray();
byte[] message;
int offset, length;
if (oneShotMode) {
if (oneShotBytes == null)
throw new SignatureException("update() not called first");
message = oneShotBytes;
offset = oneShotOffset;
length = oneShotLength;
} else {
if (baos == null)
message = new byte[0];
else
message = baos.toByteArray();
offset = 0;
length = message.length;
}
// r = H(h_b,...,h_2b-1,M)
byte[] r = digest.digest(message);
digest.update(message, offset, length);
byte[] r = digest.digest();
// r mod l
// Reduces r from 64 bytes to 32 bytes
@@ -128,7 +232,8 @@ public final class EdDSAEngine extends Signature {
// S = (r + H(Rbar,Abar,M)*a) mod l
digest.update(Rbyte);
digest.update(((EdDSAPrivateKey) key).getAbyte());
byte[] h = digest.digest(message);
digest.update(message, offset, length);
byte[] h = digest.digest();
h = sc.reduce(h);
byte[] S = sc.multiplyAndAdd(h, a, r);
@@ -141,6 +246,14 @@ public final class EdDSAEngine extends Signature {
@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
try {
return x_engineVerify(sigBytes);
} finally {
reset();
}
}
private boolean x_engineVerify(byte[] sigBytes) throws SignatureException {
Curve curve = key.getParams().getCurve();
int b = curve.getField().getb();
if (sigBytes.length != b/4)
@@ -150,8 +263,24 @@ public final class EdDSAEngine extends Signature {
digest.update(sigBytes, 0, b/8);
digest.update(((EdDSAPublicKey) key).getAbyte());
// h = H(Rbar,Abar,M)
byte[] message = baos.toByteArray();
byte[] h = digest.digest(message);
byte[] message;
int offset, length;
if (oneShotMode) {
if (oneShotBytes == null)
throw new SignatureException("update() not called first");
message = oneShotBytes;
offset = oneShotOffset;
length = oneShotLength;
} else {
if (baos == null)
message = new byte[0];
else
message = baos.toByteArray();
offset = 0;
length = message.length;
}
digest.update(message, offset, length);
byte[] h = digest.digest();
// h mod l
h = key.getParams().getScalarOps().reduce(h);
@@ -171,6 +300,140 @@ public final class EdDSAEngine extends Signature {
return true;
}
/**
* To efficiently sign all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* sig = sign()
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public byte[] signOneShot(byte[] data) throws SignatureException {
return signOneShot(data, 0, data.length);
}
/**
* To efficiently sign all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* sig = sign()
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException {
oneShotMode = true;
update(data, off, len);
return sign();
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* ok = verify(signature)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException {
return verifyOneShot(data, 0, data.length, signature, 0, signature.length);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* ok = verify(signature)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException {
return verifyOneShot(data, off, len, signature, 0, signature.length);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* ok = verify(signature, sigoff, siglen)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException {
return verifyOneShot(data, 0, data.length, signature, sigoff, siglen);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* ok = verify(signature, sigoff, siglen)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException {
oneShotMode = true;
update(data, off, len);
return verify(signature, sigoff, siglen);
}
/**
* @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
@Override
protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
if (spec.equals(ONE_SHOT_MODE)) {
if (oneShotBytes != null || baos != null && baos.size() > 0)
throw new InvalidAlgorithmParameterException("update() already called");
oneShotMode = true;
} else {
super.engineSetParameter(spec);
}
}
/**
* @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
*/

View File

@@ -12,6 +12,13 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
/**
* An EdDSA private key.
*<p>
* Warning: Private key encoding is not fully specified in the
* current IETF draft. This implementation uses PKCS#8 encoding,
* and is subject to change. See getEncoded().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*</p>
*
* @since 0.9.15
* @author str4d
@@ -52,9 +59,7 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
}
/**
* This follows the spec at
* https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
* AND the docs from
* This follows the docs from
* java.security.spec.PKCS8EncodedKeySpec
* quote:
*<pre>
@@ -78,6 +83,8 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
* }
*</pre>
*
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*
* Note that the private key encoding is not fully specified in the Josefsson draft version 04,
* and the example could be wrong, as it's lacking Version and AlgorithmIdentifier.
* This will hopefully be clarified in the next draft.

View File

@@ -12,6 +12,12 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
/**
* An EdDSA public key.
*<p>
* Warning: Public key encoding is is based on the
* current IETF draft, and is subject to change. See getEncoded().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*</p>
*
* @since 0.9.15
* @author str4d