/*
* Copyright (c) 1996, 2004, 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.provider;
import java.io.*;
import java.util.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.*;
import java.security.SecureRandom;
import java.security.interfaces.*;
import java.security.spec.DSAParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import sun.security.util.Debug;
import sun.security.util.DerValue;
import sun.security.util.DerInputStream;
import sun.security.util.DerOutputStream;
import sun.security.x509.AlgIdDSA;
import sun.security.jca.JCAUtil;
/**
* The Digital Signature Standard (using the Digital Signature
* Algorithm), as described in fips186 of the National Instute of
* Standards and Technology (NIST), using fips180-1 (SHA-1).
*
* This file contains both the signature implementation for the
* commonly used SHA1withDSA (DSS) as well as RawDSA, used by TLS
* among others. RawDSA expects the 20 byte SHA-1 digest as input
* via update rather than the original data like other signature
* implementations.
*
* @author Benjamin Renaud
*
* @since 1.1
*
* @see DSAPublicKey
* @see DSAPrivateKey
*/
abstract class DSA extends SignatureSpi {
/* Are we debugging? */
private static final boolean debug = false;
/* The parameter object */
private DSAParams params;
/* algorithm parameters */
private BigInteger presetP, presetQ, presetG;
/* The public key, if any */
private BigInteger presetY;
/* The private key, if any */
private BigInteger presetX;
/* The random seed used to generate k */
private int[] Kseed;
/* The random seed used to generate k (specified by application) */
private byte[] KseedAsByteArray;
/*
* The random seed used to generate k
* (prevent the same Kseed from being used twice in a row
*/
private int[] previousKseed;
/* The RNG used to output a seed for generating k */
private SecureRandom signingRandom;
/**
* Construct a blank DSA object. It must be
* initialized before being usable for signing or verifying.
*/
DSA() {
super();
}
/**
* Return the 20 byte hash value and reset the digest.
*/
abstract byte[] getDigest() throws SignatureException;
/**
* Reset the digest.
*/
abstract void resetDigest();
/**
* Standard SHA1withDSA implementation.
*/
public static final class SHA1withDSA extends DSA {
/* The SHA hash for the data */
private final MessageDigest dataSHA;
public SHA1withDSA() throws NoSuchAlgorithmException {
dataSHA = MessageDigest.getInstance("SHA-1");
}
/**
* Update a byte to be signed or verified.
*/
protected void engineUpdate(byte b) {
dataSHA.update(b);
}
/**
* Update an array of bytes to be signed or verified.
*/
protected void engineUpdate(byte[] data, int off, int len) {
dataSHA.update(data, off, len);
}
protected void engineUpdate(ByteBuffer b) {
dataSHA.update(b);
}
byte[] getDigest() {
return dataSHA.digest();
}
void resetDigest() {
dataSHA.reset();
}
}
/**
* RawDSA implementation.
*
* RawDSA requires the data to be exactly 20 bytes long. If it is
* not, a SignatureException is thrown when sign()/verify() is called
* per JCA spec.
*/
public static final class RawDSA extends DSA {
// length of the SHA-1 digest (20 bytes)
private final static int SHA1_LEN = 20;
// 20 byte digest buffer
private final byte[] digestBuffer;
// offset into the buffer
private int ofs;
public RawDSA() {
digestBuffer = new byte[SHA1_LEN];
}
protected void engineUpdate(byte b) {
if (ofs == SHA1_LEN) {
ofs = SHA1_LEN + 1;
return;
}
digestBuffer[ofs++] = b;
}
protected void engineUpdate(byte[] data, int off, int len) {
if (ofs + len > SHA1_LEN) {
ofs = SHA1_LEN + 1;
return;
}
System.arraycopy(data, off, digestBuffer, ofs, len);
ofs += len;
}
byte[] getDigest() throws SignatureException {
if (ofs != SHA1_LEN) {
throw new SignatureException
("Data for RawDSA must be exactly 20 bytes long");
}
ofs = 0;
return digestBuffer;
}
void resetDigest() {
ofs = 0;
}
}
/**
* Initialize the DSA object with a DSA private key.
*
* @param privateKey the DSA private key
*
* @exception InvalidKeyException if the key is not a valid DSA private
* key.
*/
protected void engineInitSign(PrivateKey privateKey)
throws InvalidKeyException {
if (!(privateKey instanceof java.security.interfaces.DSAPrivateKey)) {
throw new InvalidKeyException("not a DSA private key: " +
privateKey);
}
java.security.interfaces.DSAPrivateKey priv =
(java.security.interfaces.DSAPrivateKey)privateKey;
this.presetX = priv.getX();
this.presetY = null;
initialize(priv.getParams());
}
/**
* Initialize the DSA object with a DSA public key.
*
* @param publicKey the DSA public key.
*
* @exception InvalidKeyException if the key is not a valid DSA public
* key.
*/
protected void engineInitVerify(PublicKey publicKey)
throws InvalidKeyException {
if (!(publicKey instanceof java.security.interfaces.DSAPublicKey)) {
throw new InvalidKeyException("not a DSA public key: " +
publicKey);
}
java.security.interfaces.DSAPublicKey pub =
(java.security.interfaces.DSAPublicKey)publicKey;
this.presetY = pub.getY();
this.presetX = null;
initialize(pub.getParams());
}
private void initialize(DSAParams params) throws InvalidKeyException {
resetDigest();
setParams(params);
}
/**
* Sign all the data thus far updated. The signature is formatted
* according to the Canonical Encoding Rules, returned as a DER
* sequence of Integer, r and s.
*
* @return a signature block formatted according to the Canonical
* Encoding Rules.
*
* @exception SignatureException if the signature object was not
* properly initialized, or if another exception occurs.
*
* @see sun.security.DSA#engineUpdate
* @see sun.security.DSA#engineVerify
*/
protected byte[] engineSign() throws SignatureException {
BigInteger k = generateK(presetQ);
BigInteger r = generateR(presetP, presetQ, presetG, k);
BigInteger s = generateS(presetX, presetQ, r, k);
try {
DerOutputStream outseq = new DerOutputStream(100);
outseq.putInteger(r);
outseq.putInteger(s);
DerValue result = new DerValue(DerValue.tag_Sequence,
outseq.toByteArray());
return result.toByteArray();
} catch (IOException e) {
throw new SignatureException("error encoding signature");
}
}
/**
* Verify all the data thus far updated.
*
* @param signature the alledged signature, encoded using the
* Canonical Encoding Rules, as a sequence of integers, r and s.
*
* @exception SignatureException if the signature object was not
* properly initialized, or if another exception occurs.
*
* @see sun.security.DSA#engineUpdate
* @see sun.security.DSA#engineSign
*/
protected boolean engineVerify(byte[] signature)
throws SignatureException {
return engineVerify(signature, 0, signature.length);
}
/**
* Verify all the data thus far updated.
*
* @param signature the alledged signature, encoded using the
* Canonical Encoding Rules, as a sequence of integers, r and s.
*
* @param offset the offset to start from in the array of bytes.
*
* @param length the number of bytes to use, starting at offset.
*
* @exception SignatureException if the signature object was not
* properly initialized, or if another exception occurs.
*
* @see sun.security.DSA#engineUpdate
* @see sun.security.DSA#engineSign
*/
protected boolean engineVerify(byte[] signature, int offset, int length)
throws SignatureException {
BigInteger r = null;
BigInteger s = null;
// first decode the signature.
try {
DerInputStream in = new DerInputStream(signature, offset, length);
DerValue[] values = in.getSequence(2);
r = values[0].getBigInteger();
s = values[1].getBigInteger();
} catch (IOException e) {
throw new SignatureException("invalid encoding for signature");
}
// some implementations do not correctly encode values in the ASN.1
// 2's complement format. force r and s to be positive in order to
// to validate those signatures
if (r.signum() < 0) {
r = new BigInteger(1, r.toByteArray());
}
if (s.signum() < 0) {
s = new BigInteger(1, s.toByteArray());
}
if ((r.compareTo(presetQ) == -1) && (s.compareTo(presetQ) == -1)) {
BigInteger w = generateW(presetP, presetQ, presetG, s);
BigInteger v = generateV(presetY, presetP, presetQ, presetG, w, r);
return v.equals(r);
} else {
throw new SignatureException("invalid signature: out of range values");
}
}
private BigInteger generateR(BigInteger p, BigInteger q, BigInteger g,
BigInteger k) {
BigInteger temp = g.modPow(k, p);
return temp.remainder(q);
}
private BigInteger generateS(BigInteger x, BigInteger q,
BigInteger r, BigInteger k) throws SignatureException {
byte[] s2 = getDigest();
BigInteger temp = new BigInteger(1, s2);
BigInteger k1 = k.modInverse(q);
BigInteger s = x.multiply(r);
s = temp.add(s);
s = k1.multiply(s);
return s.remainder(q);
}
private BigInteger generateW(BigInteger p, BigInteger q,
BigInteger g, BigInteger s) {
return s.modInverse(q);
}
private BigInteger generateV(BigInteger y, BigInteger p,
BigInteger q, BigInteger g, BigInteger w, BigInteger r)
throws SignatureException {
byte[] s2 = getDigest();
BigInteger temp = new BigInteger(1, s2);
temp = temp.multiply(w);
BigInteger u1 = temp.remainder(q);
BigInteger u2 = (r.multiply(w)).remainder(q);
BigInteger t1 = g.modPow(u1,p);
BigInteger t2 = y.modPow(u2,p);
BigInteger t3 = t1.multiply(t2);
BigInteger t5 = t3.remainder(p);
return t5.remainder(q);
}
/*
* Please read bug report 4044247 for an alternative, faster,
* NON-FIPS approved method to generate K
*/
private BigInteger generateK(BigInteger q) {
BigInteger k = null;
// The application specified a Kseed for us to use.
// Note that we do not allow usage of the same Kseed twice in a row
if (Kseed != null && !Arrays.equals(Kseed, previousKseed)) {
k = generateK(Kseed, q);
if (k.signum() > 0 && k.compareTo(q) < 0) {
previousKseed = new int [Kseed.length];
System.arraycopy(Kseed, 0, previousKseed, 0, Kseed.length);
return k;
}
}
// The application did not specify a Kseed for us to use.
// We'll generate a new Kseed by getting random bytes from
// a SecureRandom object.
SecureRandom random = getSigningRandom();
while (true) {
int[] seed = new int[5];
for (int i = 0; i < 5; i++)
seed[i] = random.nextInt();
k = generateK(seed, q);
if (k.signum() > 0 && k.compareTo(q) < 0) {
previousKseed = new int [seed.length];
System.arraycopy(seed, 0, previousKseed, 0, seed.length);
return k;
}
}
}
// Use the application-specified SecureRandom Object if provided.
// Otherwise, use our default SecureRandom Object.
private SecureRandom getSigningRandom() {
if (signingRandom == null) {
if (appRandom != null) {
signingRandom = appRandom;
} else {
signingRandom = JCAUtil.getSecureRandom();
}
}
return signingRandom;
}
/**
* Compute k for a DSA signature.
*
* @param seed the seed for generating k. This seed should be
* secure. This is what is refered to as the KSEED in the DSA
* specification.
*
* @param g the g parameter from the DSA key pair.
*/
private BigInteger generateK(int[] seed, BigInteger q) {
// check out t in the spec.
int[] t = { 0xEFCDAB89, 0x98BADCFE, 0x10325476,
0xC3D2E1F0, 0x67452301 };
//
int[] tmp = DSA.SHA_7(seed, t);
byte[] tmpBytes = new byte[tmp.length * 4];
for (int i = 0; i < tmp.length; i++) {
int k = tmp[i];
for (int j = 0; j < 4; j++) {
tmpBytes[(i * 4) + j] = (byte) (k >>> (24 - (j * 8)));
}
}
BigInteger k = new BigInteger(1, tmpBytes).mod(q);
return k;
}
// Constants for each round
private static final int round1_kt = 0x5a827999;
private static final int round2_kt = 0x6ed9eba1;
private static final int round3_kt = 0x8f1bbcdc;
private static final int round4_kt = 0xca62c1d6;
/**
* Computes set 1 thru 7 of SHA-1 on m1. */
static int[] SHA_7(int[] m1, int[] h) {
int[] W = new int[80];
System.arraycopy(m1,0,W,0,m1.length);
int temp = 0;
for (int t = 16; t <= 79; t++){
temp = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16];
W[t] = ((temp << 1) | (temp >>>(32 - 1)));
}
int a = h[0],b = h[1],c = h[2], d = h[3], e = h[4];
for (int i = 0; i < 20; i++) {
temp = ((a<<5) | (a>>>(32-5))) +
((b&c)|((~b)&d))+ e + W[i] + round1_kt;
e = d;
d = c;
c = ((b<<30) | (b>>>(32-30)));
b = a;
a = temp;
}
// Round 2
for (int i = 20; i < 40; i++) {
temp = ((a<<5) | (a>>>(32-5))) +
(b ^ c ^ d) + e + W[i] + round2_kt;
e = d;
d = c;
c = ((b<<30) | (b>>>(32-30)));
b = a;
a = temp;
}
// Round 3
for (int i = 40; i < 60; i++) {
temp = ((a<<5) | (a>>>(32-5))) +
((b&c)|(b&d)|(c&d)) + e + W[i] + round3_kt;
e = d;
d = c;
c = ((b<<30) | (b>>>(32-30)));
b = a;
a = temp;
}
// Round 4
for (int i = 60; i < 80; i++) {
temp = ((a<<5) | (a>>>(32-5))) +
(b ^ c ^ d) + e + W[i] + round4_kt;
e = d;
d = c;
c = ((b<<30) | (b>>>(32-30)));
b = a;
a = temp;
}
int[] md = new int[5];
md[0] = h[0] + a;
md[1] = h[1] + b;
md[2] = h[2] + c;
md[3] = h[3] + d;
md[4] = h[4] + e;
return md;
}
/**
* This implementation recognizes the following parameter:<dl>
*
* <dt><tt>Kseed</tt>
*
* <dd>a byte array.
*
* </dl>
*
* @deprecated
*/
@Deprecated
protected void engineSetParameter(String key, Object param) {
if (key.equals("KSEED")) {
if (param instanceof byte[]) {
Kseed = byteArray2IntArray((byte[])param);
KseedAsByteArray = (byte[])param;
} else {
debug("unrecognized param: " + key);
throw new InvalidParameterException("Kseed not a byte array");
}
} else {
throw new InvalidParameterException("invalid parameter");
}
}
/**
* Return the value of the requested parameter. Recognized
* parameters are:
*
* <dl>
*
* <dt><tt>Kseed</tt>
*
* <dd>a byte array.
*
* </dl>
*
* @return the value of the requested parameter.
*
* @see java.security.SignatureEngine
*
* @deprecated
*/
@Deprecated
protected Object engineGetParameter(String key) {
if (key.equals("KSEED")) {
return KseedAsByteArray;
} else {
return null;
}
}
/**
* Set the algorithm object.
*/
private void setParams(DSAParams params) throws InvalidKeyException {
if (params == null) {
throw new InvalidKeyException("DSA public key lacks parameters");
}
this.params = params;
this.presetP = params.getP();
this.presetQ = params.getQ();
this.presetG = params.getG();
}
/**
* Return a human readable rendition of the engine.
*/
public String toString() {
String printable = "DSA Signature";
if (presetP != null && presetQ != null && presetG != null) {
printable += "\n\tp: " + Debug.toHexString(presetP);
printable += "\n\tq: " + Debug.toHexString(presetQ);
printable += "\n\tg: " + Debug.toHexString(presetG);
} else {
printable += "\n\t P, Q or G not initialized.";
}
if (presetY != null) {
printable += "\n\ty: " + Debug.toHexString(presetY);
}
if (presetY == null && presetX == null) {
printable += "\n\tUNINIIALIZED";
}
return printable;
}
/*
* Utility routine for converting a byte array into an int array
*/
private int[] byteArray2IntArray(byte[] byteArray) {
int j = 0;
byte[] newBA;
int mod = byteArray.length % 4;
// guarantee that the incoming byteArray is a multiple of 4
// (pad with 0's)
switch (mod) {
case 3: newBA = new byte[byteArray.length + 1]; break;
case 2: newBA = new byte[byteArray.length + 2]; break;
case 1: newBA = new byte[byteArray.length + 3]; break;
default: newBA = new byte[byteArray.length + 0]; break;
}
System.arraycopy(byteArray, 0, newBA, 0, byteArray.length);
// copy each set of 4 bytes in the byte array into an integer
int[] newSeed = new int[newBA.length / 4];
for (int i = 0; i < newBA.length; i += 4) {
newSeed[j] = newBA[i + 3] & 0xFF;
newSeed[j] |= (newBA[i + 2] << 8) & 0xFF00;
newSeed[j] |= (newBA[i + 1] << 16) & 0xFF0000;
newSeed[j] |= (newBA[i + 0] << 24) & 0xFF000000;
j++;
}
return newSeed;
}
private static void debug(Exception e) {
if (debug) {
e.printStackTrace();
}
}
private static void debug(String s) {
if (debug) {
System.err.println(s);
}
}
}