package sun.security.pkcs11;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import sun.nio.ch.DirectBuffer;
import java.util.Hashtable;
import java.util.Arrays;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.security.interfaces.*;
import sun.security.pkcs11.wrapper.*;
import sun.security.util.KnownOIDs;
import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
final class P11PSSSignature extends SignatureSpi {
private final static boolean DEBUG = false;
private static final Hashtable<String, Integer> DIGEST_LENGTHS =
new Hashtable<String, Integer>();
static {
DIGEST_LENGTHS.put("SHA-1", 20);
DIGEST_LENGTHS.put("SHA-224", 28);
DIGEST_LENGTHS.put("SHA-256", 32);
DIGEST_LENGTHS.put("SHA-384", 48);
DIGEST_LENGTHS.put("SHA-512", 64);
DIGEST_LENGTHS.put("SHA-512/224", 28);
DIGEST_LENGTHS.put("SHA-512/256", 32);
DIGEST_LENGTHS.put("SHA3-224", 28);
DIGEST_LENGTHS.put("SHA3-256", 32);
DIGEST_LENGTHS.put("SHA3-384", 48);
DIGEST_LENGTHS.put("SHA3-512", 64);
}
private static String toStdName(String givenDigestAlg) {
if (givenDigestAlg == null) return null;
KnownOIDs given2 = KnownOIDs.findMatch(givenDigestAlg);
if (given2 == null) {
return givenDigestAlg;
} else {
return given2.stdName();
}
}
private static boolean isDigestEqual(String stdAlg, String givenAlg) {
if (stdAlg == null || givenAlg == null) return false;
givenAlg = toStdName(givenAlg);
return stdAlg.equalsIgnoreCase(givenAlg);
}
private final Token token;
private final String algorithm;
private static final String KEY_ALGO = "RSA";
private final CK_MECHANISM mechanism;
private final int type;
private P11Key p11Key = null;
private PSSParameterSpec sigParams = null;
private boolean isActive = false;
private final String mdAlg;
private MessageDigest md = null;
private Session session;
private int mode;
private boolean initialized = false;
private final byte[] buffer = new byte[1];
private int bytesProcessed = 0;
private final static int M_SIGN = 1;
private final static int M_VERIFY = 2;
private final static int T_DIGEST = 1;
private final static int T_UPDATE = 2;
P11PSSSignature(Token token, String algorithm, long mechId)
throws NoSuchAlgorithmException, PKCS11Exception {
super();
this.token = token;
this.algorithm = algorithm;
this.mechanism = new CK_MECHANISM(mechId);
int idx = algorithm.indexOf("with");
this.mdAlg = (idx == -1?
null : toStdName(algorithm.substring(0, idx)));
switch ((int)mechId) {
case (int)CKM_SHA1_RSA_PKCS_PSS:
case (int)CKM_SHA224_RSA_PKCS_PSS:
case (int)CKM_SHA256_RSA_PKCS_PSS:
case (int)CKM_SHA384_RSA_PKCS_PSS:
case (int)CKM_SHA512_RSA_PKCS_PSS:
case (int)CKM_SHA3_224_RSA_PKCS_PSS:
case (int)CKM_SHA3_256_RSA_PKCS_PSS:
case (int)CKM_SHA3_384_RSA_PKCS_PSS:
case (int)CKM_SHA3_512_RSA_PKCS_PSS:
type = T_UPDATE;
this.md = null;
break;
case (int)CKM_RSA_PKCS_PSS:
if (this.mdAlg != null && token.getMechanismInfo
(Functions.getHashMechId(this.mdAlg)) == null) {
throw new NoSuchAlgorithmException("Unsupported algorithm: " +
algorithm);
}
this.md = (this.mdAlg == null? null :
MessageDigest.getInstance(this.mdAlg));
type = T_DIGEST;
break;
default:
throw new ProviderException("Unsupported mechanism: " + mechId);
}
}
private static PSSParameterSpec genDefaultParams(String digestAlg,
P11Key key) throws SignatureException {
int mdLen;
try {
mdLen = DIGEST_LENGTHS.get(digestAlg);
} catch (NullPointerException npe) {
throw new SignatureException("Unsupported digest: " +
digestAlg);
}
int saltLen = Integer.min(mdLen, (key.length() >> 3) - mdLen -2);
return new PSSParameterSpec(digestAlg,
"MGF1", new MGF1ParameterSpec(digestAlg),
saltLen, PSSParameterSpec.TRAILER_FIELD_BC);
}
private void ensureInitialized() throws SignatureException {
token.ensureValid();
if (this.p11Key == null) {
throw new SignatureException("Missing key");
}
if (this.sigParams == null) {
if (this.mdAlg == null) {
throw new SignatureException
("Parameters required for RSASSA-PSS signature");
}
this.sigParams = genDefaultParams(this.mdAlg, this.p11Key);
this.mechanism.setParameter(new CK_RSA_PKCS_PSS_PARAMS(
this.mdAlg, "MGF1", this.mdAlg, sigParams.getSaltLength()));
}
if (initialized == false) {
try {
initialize();
} catch (ProviderException pe) {
throw new SignatureException(pe);
}
}
}
private void reset(boolean doCancel) {
if (!initialized) {
return;
}
initialized = false;
try {
if (session == null) {
return;
}
if (doCancel && token.explicitCancel) {
cancelOperation();
}
} finally {
p11Key.releaseKeyID();
mechanism.freeHandle();
session = token.releaseSession(session);
isActive = false;
}
}
private void cancelOperation() {
token.ensureValid();
if (DEBUG) System.out.print("Cancelling operation");
try {
if (mode == M_SIGN) {
if (type == T_UPDATE) {
if (DEBUG) System.out.println(" by C_SignFinal");
token.p11.C_SignFinal(session.id(), 0);
} else {
byte[] digest =
(md == null? new byte[0] : md.digest());
if (DEBUG) System.out.println(" by C_Sign");
token.p11.C_Sign(session.id(), digest);
}
} else {
byte[] signature =
new byte[(p11Key.length() + 7) >> 3];
if (type == T_UPDATE) {
if (DEBUG) System.out.println(" by C_VerifyFinal");
token.p11.C_VerifyFinal(session.id(), signature);
} else {
byte[] digest =
(md == null? new byte[0] : md.digest());
if (DEBUG) System.out.println(" by C_Verify");
token.p11.C_Verify(session.id(), digest, signature);
}
}
} catch (PKCS11Exception e) {
if (e.getErrorCode() == CKR_OPERATION_NOT_INITIALIZED) {
return;
}
if (mode == M_SIGN) {
throw new ProviderException("cancel failed", e);
}
}
}
private void initialize() throws ProviderException {
if (DEBUG) System.out.println("Initializing");
if (p11Key == null) {
throw new ProviderException(
"No Key found, call initSign/initVerify first");
}
long keyID = p11Key.getKeyID();
try {
if (session == null) {
session = token.getOpSession();
}
if (mode == M_SIGN) {
token.p11.C_SignInit(session.id(), mechanism, keyID);
} else {
token.p11.C_VerifyInit(session.id(), mechanism, keyID);
}
} catch (PKCS11Exception e) {
p11Key.releaseKeyID();
session = token.releaseSession(session);
throw new ProviderException("Initialization failed", e);
}
if (bytesProcessed != 0) {
bytesProcessed = 0;
if (md != null) {
md.reset();
}
}
initialized = true;
isActive = false;
if (DEBUG) System.out.println("Initialized");
}
private void checkKeySize(Key key) throws InvalidKeyException {
if (DEBUG) System.out.print("Checking Key");
if (!key.getAlgorithm().equals(KEY_ALGO)) {
throw new InvalidKeyException("Only " + KEY_ALGO +
" keys are supported");
}
CK_MECHANISM_INFO mechInfo = null;
try {
mechInfo = token.getMechanismInfo(mechanism.mechanism);
} catch (PKCS11Exception e) {
if (DEBUG) {
System.out.println("Unexpected exception");
e.printStackTrace();
}
}
int keySize = 0;
if (mechInfo != null) {
if (key instanceof P11Key) {
keySize = (((P11Key) key).length() + 7) >> 3;
} else if (key instanceof RSAKey) {
keySize = ((RSAKey) key).getModulus().bitLength() >> 3;
} else {
throw new InvalidKeyException("Unrecognized key type " + key);
}
if ((mechInfo.iMinKeySize != 0) &&
(keySize < (mechInfo.iMinKeySize >> 3))) {
throw new InvalidKeyException(KEY_ALGO +
" key must be at least " + mechInfo.iMinKeySize + " bits");
}
if ((mechInfo.iMaxKeySize != Integer.MAX_VALUE) &&
(keySize > (mechInfo.iMaxKeySize >> 3))) {
throw new InvalidKeyException(KEY_ALGO +
" key must be at most " + mechInfo.iMaxKeySize + " bits");
}
}
if (this.sigParams != null) {
String digestAlg = this.sigParams.getDigestAlgorithm();
int sLen = this.sigParams.getSaltLength();
int hLen = DIGEST_LENGTHS.get(toStdName(digestAlg)).intValue();
int minKeyLen = Math.addExact(Math.addExact(sLen, hLen), 2);
if (keySize < minKeyLen) {
throw new InvalidKeyException
("Key is too short for current params, need min " + minKeyLen);
}
}
}
private void setSigParams(AlgorithmParameterSpec p)
throws InvalidAlgorithmParameterException {
if (p == null) {
throw new InvalidAlgorithmParameterException("PSS Parameter required");
}
if (!(p instanceof PSSParameterSpec)) {
throw new InvalidAlgorithmParameterException
("Only PSSParameterSpec is supported");
}
PSSParameterSpec params = (PSSParameterSpec) p;
if (params == this.sigParams) return;
String digestAlgorithm = params.getDigestAlgorithm();
if (this.mdAlg != null && !isDigestEqual(this.mdAlg, digestAlgorithm)) {
throw new InvalidAlgorithmParameterException
("Digest algorithm in Signature parameters must be " +
this.mdAlg);
}
try {
if (token.getMechanismInfo(Functions.getHashMechId
(digestAlgorithm)) == null) {
throw new InvalidAlgorithmParameterException
("Unsupported digest algorithm: " + digestAlgorithm);
}
} catch (PKCS11Exception pe) {
throw new InvalidAlgorithmParameterException(pe);
}
Integer digestLen = DIGEST_LENGTHS.get(toStdName(digestAlgorithm));
if (digestLen == null) {
throw new InvalidAlgorithmParameterException
("Unsupported digest algorithm in Signature parameters: " +
digestAlgorithm);
}
if (!(params.getMGFAlgorithm().equalsIgnoreCase("MGF1"))) {
throw new InvalidAlgorithmParameterException("Only supports MGF1");
}
String mgfDigestAlgo = digestAlgorithm;
AlgorithmParameterSpec mgfParams = params.getMGFParameters();
if (mgfParams != null) {
if (!(mgfParams instanceof MGF1ParameterSpec)) {
throw new InvalidAlgorithmParameterException
("Only MGF1ParameterSpec is supported");
}
mgfDigestAlgo = ((MGF1ParameterSpec)mgfParams).getDigestAlgorithm();
}
if (params.getTrailerField() != PSSParameterSpec.TRAILER_FIELD_BC) {
throw new InvalidAlgorithmParameterException
("Only supports TrailerFieldBC(1)");
}
int saltLen = params.getSaltLength();
if (this.p11Key != null) {
int maxSaltLen = ((this.p11Key.length() + 7) >> 3) -
digestLen.intValue() - 2;
if (DEBUG) {
System.out.println("Max saltLen = " + maxSaltLen);
System.out.println("Curr saltLen = " + saltLen);
}
if (maxSaltLen < 0 || saltLen > maxSaltLen) {
throw new InvalidAlgorithmParameterException
("Invalid with current key size");
}
} else if (DEBUG) {
System.out.println("No key available for validating saltLen");
}
try {
this.mechanism.setParameter(
new CK_RSA_PKCS_PSS_PARAMS(digestAlgorithm, "MGF1",
mgfDigestAlgo, saltLen));
this.sigParams = params;
} catch (IllegalArgumentException iae) {
throw new InvalidAlgorithmParameterException(iae);
}
}
@Override
protected void engineInitVerify(PublicKey publicKey)
throws InvalidKeyException {
if (publicKey == null) {
throw new InvalidKeyException("Key must not be null");
}
if (publicKey != p11Key) {
checkKeySize(publicKey);
}
reset(true);
mode = M_VERIFY;
p11Key = P11KeyFactory.convertKey(token, publicKey, KEY_ALGO);
if (this.p11Key != null && this.sigParams != null) {
try {
initialize();
} catch (ProviderException pe) {
throw new InvalidKeyException(pe);
}
}
}
@Override
protected void engineInitSign(PrivateKey privateKey)
throws InvalidKeyException {
if (privateKey == null) {
throw new InvalidKeyException("Key must not be null");
}
if (privateKey != p11Key) {
checkKeySize(privateKey);
}
reset(true);
mode = M_SIGN;
p11Key = P11KeyFactory.convertKey(token, privateKey, KEY_ALGO);
if (this.p11Key != null && this.sigParams != null) {
try {
initialize();
} catch (ProviderException pe) {
throw new InvalidKeyException(pe);
}
}
}
@Override
protected void engineUpdate(byte b) throws SignatureException {
ensureInitialized();
isActive = true;
buffer[0] = b;
engineUpdate(buffer, 0, 1);
}
@Override
protected void engineUpdate(byte[] b, int ofs, int len)
throws SignatureException {
ensureInitialized();
if (len == 0) {
return;
}
if (len + bytesProcessed < 0) {
throw new ProviderException("Processed bytes limits exceeded.");
}
isActive = true;
switch (type) {
case T_UPDATE:
try {
if (mode == M_SIGN) {
System.out.println(this + ": Calling C_SignUpdate");
token.p11.C_SignUpdate(session.id(), 0, b, ofs, len);
} else {
System.out.println(this + ": Calling C_VerfifyUpdate");
token.p11.C_VerifyUpdate(session.id(), 0, b, ofs, len);
}
bytesProcessed += len;
} catch (PKCS11Exception e) {
reset(false);
throw new ProviderException(e);
}
break;
case T_DIGEST:
if (md == null) {
throw new ProviderException("PSS Parameters required");
}
md.update(b, ofs, len);
bytesProcessed += len;
break;
default:
throw new ProviderException("Internal error");
}
}
@Override
protected void engineUpdate(ByteBuffer byteBuffer) {
try {
ensureInitialized();
} catch (SignatureException se) {
throw new ProviderException(se);
}
int len = byteBuffer.remaining();
if (len <= 0) {
return;
}
isActive = true;
switch (type) {
case T_UPDATE:
if (byteBuffer instanceof DirectBuffer == false) {
super.engineUpdate(byteBuffer);
return;
}
long addr = ((DirectBuffer)byteBuffer).address();
int ofs = byteBuffer.position();
try {
if (mode == M_SIGN) {
System.out.println(this + ": Calling C_SignUpdate");
token.p11.C_SignUpdate
(session.id(), addr + ofs, null, 0, len);
} else {
System.out.println(this + ": Calling C_VerifyUpdate");
token.p11.C_VerifyUpdate
(session.id(), addr + ofs, null, 0, len);
}
bytesProcessed += len;
byteBuffer.position(ofs + len);
} catch (PKCS11Exception e) {
reset(false);
throw new ProviderException("Update failed", e);
}
break;
case T_DIGEST:
if (md == null) {
throw new ProviderException("PSS Parameters required");
}
md.update(byteBuffer);
bytesProcessed += len;
break;
default:
reset(false);
throw new ProviderException("Internal error");
}
}
@Override
protected byte[] engineSign() throws SignatureException {
ensureInitialized();
boolean doCancel = true;
if (DEBUG) System.out.print("Generating signature");
try {
byte[] signature;
if (type == T_UPDATE) {
if (DEBUG) System.out.println(" by C_SignFinal");
signature = token.p11.C_SignFinal(session.id(), 0);
} else {
if (md == null) {
throw new ProviderException("PSS Parameters required");
}
byte[] digest = md.digest();
if (DEBUG) System.out.println(" by C_Sign");
signature = token.p11.C_Sign(session.id(), digest);
}
doCancel = false;
return signature;
} catch (PKCS11Exception pe) {
doCancel = false;
throw new ProviderException(pe);
} catch (ProviderException e) {
throw e;
} finally {
reset(doCancel);
}
}
@Override
protected boolean engineVerify(byte[] signature) throws SignatureException {
ensureInitialized();
boolean doCancel = true;
if (DEBUG) System.out.print("Verifying signature");
try {
if (type == T_UPDATE) {
if (DEBUG) System.out.println(" by C_VerifyFinal");
token.p11.C_VerifyFinal(session.id(), signature);
} else {
if (md == null) {
throw new ProviderException("PSS Parameters required");
}
byte[] digest = md.digest();
if (DEBUG) System.out.println(" by C_Verify");
token.p11.C_Verify(session.id(), digest, signature);
}
doCancel = false;
return true;
} catch (PKCS11Exception pe) {
doCancel = false;
long errorCode = pe.getErrorCode();
if (errorCode == CKR_SIGNATURE_INVALID) {
return false;
}
if (errorCode == CKR_SIGNATURE_LEN_RANGE) {
return false;
}
if (errorCode == CKR_DATA_LEN_RANGE) {
return false;
}
throw new ProviderException(pe);
} catch (ProviderException e) {
throw e;
} finally {
reset(doCancel);
}
}
@SuppressWarnings("deprecation")
@Override
protected void engineSetParameter(String param, Object value)
throws InvalidParameterException {
throw new UnsupportedOperationException("setParameter() not supported");
}
@Override
protected void engineSetParameter(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (isActive) {
throw new ProviderException
("Cannot set parameters during operations");
}
setSigParams(params);
if (type == T_DIGEST) {
try {
this.md = MessageDigest.getInstance(sigParams.getDigestAlgorithm());
} catch (NoSuchAlgorithmException nsae) {
throw new InvalidAlgorithmParameterException(nsae);
}
}
if (this.p11Key != null && this.sigParams != null) {
try {
initialize();
} catch (ProviderException pe) {
throw new InvalidAlgorithmParameterException(pe);
}
}
}
@SuppressWarnings("deprecation")
@Override
protected Object engineGetParameter(String param)
throws InvalidParameterException {
throw new UnsupportedOperationException("getParameter() not supported");
}
@Override
protected AlgorithmParameters engineGetParameters() {
if (this.sigParams != null) {
try {
AlgorithmParameters ap = AlgorithmParameters.getInstance("RSASSA-PSS");
ap.init(this.sigParams);
return ap;
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
return null;
}
}