/* |
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package sun.security.ec; |
|
import sun.security.ec.point.*; |
|
import sun.security.util.ArrayUtil; |
|
import sun.security.util.math.*; |
|
import static sun.security.ec.ECOperations.IntermediateValueException; |
|
import java.security.ProviderException; |
|
import java.security.spec.*; |
|
import java.util.Optional; |
|
public class ECDSAOperations { |
|
public static class Seed { |
|
private final byte[] seedValue; |
|
public Seed(byte[] seedValue) { |
|
this.seedValue = seedValue; |
|
} |
|
public byte[] getSeedValue() { |
|
return seedValue; |
|
} |
|
} |
|
public static class Nonce { |
|
private final byte[] nonceValue; |
|
public Nonce(byte[] nonceValue) { |
|
this.nonceValue = nonceValue; |
|
} |
|
public byte[] getNonceValue() { |
|
return nonceValue; |
|
} |
|
} |
|
private final ECOperations ecOps; |
|
private final AffinePoint basePoint; |
|
public ECDSAOperations(ECOperations ecOps, ECPoint basePoint) { |
|
this.ecOps = ecOps; |
|
this.basePoint = toAffinePoint(basePoint, ecOps.getField()); |
|
} |
|
public ECOperations getEcOperations() { |
|
return ecOps; |
|
} |
|
public AffinePoint basePointMultiply(byte[] scalar) { |
|
return ecOps.multiply(basePoint, scalar).asAffine(); |
|
} |
|
public static AffinePoint toAffinePoint(ECPoint point, |
|
IntegerFieldModuloP field) { |
|
ImmutableIntegerModuloP affineX = field.getElement(point.getAffineX()); |
|
ImmutableIntegerModuloP affineY = field.getElement(point.getAffineY()); |
|
return new AffinePoint(affineX, affineY); |
|
} |
|
public static |
|
Optional<ECDSAOperations> forParameters(ECParameterSpec ecParams) { |
|
Optional<ECOperations> curveOps = |
|
ECOperations.forParameters(ecParams); |
|
return curveOps.map( |
|
ops -> new ECDSAOperations(ops, ecParams.getGenerator()) |
|
); |
|
} |
|
/** |
|
* |
|
* Sign a digest using the provided private key and seed. |
|
* IMPORTANT: The private key is a scalar represented using a |
|
* little-endian byte array. This is backwards from the conventional |
|
* representation in ECDSA. The routines that produce and consume this |
|
* value uses little-endian, so this deviation from convention removes |
|
* the requirement to swap the byte order. The returned signature is in |
|
* the conventional byte order. |
|
* |
|
* @param privateKey the private key scalar as a little-endian byte array |
|
* @param digest the digest to be signed |
|
* @param seed the seed that will be used to produce the nonce. This object |
|
* should contain an array that is at least 64 bits longer than |
|
* the number of bits required to represent the group order. |
|
* @return the ECDSA signature value |
|
* @throws IntermediateValueException if the signature cannot be produced |
|
* due to an unacceptable intermediate or final value. If this |
|
* exception is thrown, then the caller should discard the nonnce and |
|
* try again with an entirely new nonce value. |
|
*/ |
|
public byte[] signDigest(byte[] privateKey, byte[] digest, Seed seed) |
|
throws IntermediateValueException { |
|
byte[] nonceArr = ecOps.seedToScalar(seed.getSeedValue()); |
|
Nonce nonce = new Nonce(nonceArr); |
|
return signDigest(privateKey, digest, nonce); |
|
} |
|
/** |
|
* |
|
* Sign a digest using the provided private key and nonce. |
|
* IMPORTANT: The private key and nonce are scalars represented by a |
|
* little-endian byte array. This is backwards from the conventional |
|
* representation in ECDSA. The routines that produce and consume these |
|
* values use little-endian, so this deviation from convention removes |
|
* the requirement to swap the byte order. The returned signature is in |
|
* the conventional byte order. |
|
* |
|
* @param privateKey the private key scalar as a little-endian byte array |
|
* @param digest the digest to be signed |
|
* @param nonce the nonce object containing a little-endian scalar value. |
|
* @return the ECDSA signature value |
|
* @throws IntermediateValueException if the signature cannot be produced |
|
* due to an unacceptable intermediate or final value. If this |
|
* exception is thrown, then the caller should discard the nonnce and |
|
* try again with an entirely new nonce value. |
|
*/ |
|
public byte[] signDigest(byte[] privateKey, byte[] digest, Nonce nonce) |
|
throws IntermediateValueException { |
|
IntegerFieldModuloP orderField = ecOps.getOrderField(); |
|
int orderBits = orderField.getSize().bitLength(); |
|
if (orderBits % 8 != 0 && orderBits < digest.length * 8) { |
|
// This implementation does not support truncating digests to |
|
// a length that is not a multiple of 8. |
|
throw new ProviderException("Invalid digest length"); |
|
} |
|
byte[] k = nonce.getNonceValue(); |
|
// check nonce length |
|
int length = (orderField.getSize().bitLength() + 7) / 8; |
|
if (k.length != length) { |
|
throw new ProviderException("Incorrect nonce length"); |
|
} |
|
MutablePoint R = ecOps.multiply(basePoint, k); |
|
IntegerModuloP r = R.asAffine().getX(); |
|
// put r into the correct field by fully reducing to an array |
|
byte[] temp = new byte[length]; |
|
r.asByteArray(temp); |
|
r = orderField.getElement(temp); |
|
// store r in result |
|
r.asByteArray(temp); |
|
byte[] result = new byte[2 * length]; |
|
ArrayUtil.reverse(temp); |
|
System.arraycopy(temp, 0, result, 0, length); |
|
// compare r to 0 |
|
if (ECOperations.allZero(temp)) { |
|
throw new IntermediateValueException(); |
|
} |
|
IntegerModuloP dU = orderField.getElement(privateKey); |
|
int lengthE = Math.min(length, digest.length); |
|
byte[] E = new byte[lengthE]; |
|
System.arraycopy(digest, 0, E, 0, lengthE); |
|
ArrayUtil.reverse(E); |
|
IntegerModuloP e = orderField.getElement(E); |
|
IntegerModuloP kElem = orderField.getElement(k); |
|
IntegerModuloP kInv = kElem.multiplicativeInverse(); |
|
MutableIntegerModuloP s = r.mutable(); |
|
s.setProduct(dU).setSum(e).setProduct(kInv); |
|
// store s in result |
|
s.asByteArray(temp); |
|
ArrayUtil.reverse(temp); |
|
System.arraycopy(temp, 0, result, length, length); |
|
// compare s to 0 |
|
if (ECOperations.allZero(temp)) { |
|
throw new IntermediateValueException(); |
|
} |
|
return result; |
|
} |
|
} |