-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #337 from solver-it-sro/native-pkcs
Native pkcs
- Loading branch information
Showing
15 changed files
with
271 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
...ain/java/digital/slovensko/autogram/core/errors/KeyPinDifferentFromTokenPinException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package digital.slovensko.autogram.core.errors; | ||
|
||
public class KeyPinDifferentFromTokenPinException extends AutogramException { | ||
public KeyPinDifferentFromTokenPinException(Throwable e) { | ||
super("Nepodporovaný PIN certifikátu", "PIN podpisového certifikátu je iný než PIN úložiska certifikátov. Ďalšie podpisovanie môže viesť k zablokovaniu karty!", "Použitý certifikát má nastavený atribút CKA_ALWAYS_AUTHENTICATE a jeho PIN je iný než PIN úložiska certifikátov. Toto zatiaľ nepodporujeme. Kontaktujte nás, prosím, na [email protected]", e); | ||
} | ||
|
||
@Override | ||
public boolean batchCanContinue() { | ||
return false; | ||
} | ||
} |
173 changes: 173 additions & 0 deletions
173
src/main/java/digital/slovensko/autogram/drivers/NativePkcs11SignatureToken.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package digital.slovensko.autogram.drivers; | ||
|
||
import digital.slovensko.autogram.core.errors.AutogramException; | ||
import digital.slovensko.autogram.core.errors.KeyPinDifferentFromTokenPinException; | ||
import eu.europa.esig.dss.enumerations.SignatureAlgorithm; | ||
import eu.europa.esig.dss.model.DSSException; | ||
import eu.europa.esig.dss.model.SignatureValue; | ||
import eu.europa.esig.dss.model.ToBeSigned; | ||
import eu.europa.esig.dss.token.DSSPrivateKeyEntry; | ||
import eu.europa.esig.dss.token.KSPrivateKeyEntry; | ||
import eu.europa.esig.dss.token.Pkcs11SignatureToken; | ||
import eu.europa.esig.dss.token.PrefilledPasswordCallback; | ||
import sun.security.pkcs11.wrapper.CK_ATTRIBUTE; | ||
import sun.security.pkcs11.wrapper.PKCS11; | ||
import sun.security.pkcs11.wrapper.PKCS11Constants; | ||
import sun.security.pkcs11.wrapper.PKCS11Exception; | ||
|
||
import java.lang.reflect.Field; | ||
import java.security.GeneralSecurityException; | ||
import java.security.PrivateKey; | ||
import java.security.Signature; | ||
import java.security.spec.AlgorithmParameterSpec; | ||
import java.util.Objects; | ||
|
||
public class NativePkcs11SignatureToken extends Pkcs11SignatureToken { | ||
private static final long CKU_CONTEXT_SPECIFIC = 2L; | ||
|
||
private final PrefilledPasswordCallback prefilledPasswordCallback; | ||
private final boolean shouldProvidePasswordForCkaAA; | ||
|
||
public NativePkcs11SignatureToken(String pkcsPath, PrefilledPasswordCallback prefilledPasswordCallback, int slotIndex, boolean shouldProvidePasswordForCkaAA) { | ||
super(pkcsPath, prefilledPasswordCallback, -1, slotIndex, null); | ||
|
||
this.prefilledPasswordCallback = prefilledPasswordCallback; | ||
this.shouldProvidePasswordForCkaAA = shouldProvidePasswordForCkaAA; | ||
} | ||
|
||
private byte[] sign(final byte[] bytes, final String javaSignatureAlgorithm, final AlgorithmParameterSpec param, final DSSPrivateKeyEntry keyEntry) throws GeneralSecurityException { | ||
if (!(keyEntry instanceof KSPrivateKeyEntry)) { | ||
throw new IllegalArgumentException("Only KSPrivateKeyEntry are supported"); | ||
} | ||
|
||
final Signature signature = getSignatureInstance(javaSignatureAlgorithm); | ||
if (param != null) { | ||
signature.setParameter(param); | ||
} | ||
var pk = ((KSPrivateKeyEntry) keyEntry).getPrivateKey(); | ||
|
||
signature.initSign(pk); | ||
runContextSpecificLoginIfNeeded(signature, pk); | ||
signature.update(bytes); | ||
return signature.sign(); | ||
} | ||
|
||
private void runContextSpecificLoginIfNeeded(Signature signature, PrivateKey pk) throws GeneralSecurityException { | ||
try { | ||
// TODO cache & short-circuit | ||
var p11 = getP11(signature); | ||
var sessionId = getSessionId(signature); | ||
var keyID = getKeyID(pk); | ||
var attrs = new CK_ATTRIBUTE[]{new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_AUTHENTICATE)}; | ||
|
||
p11.C_GetAttributeValue(sessionId, keyID, attrs); | ||
if (shouldProvidePasswordForCkaAA && isAlwaysAuthenticate(attrs)) | ||
p11.C_Login(sessionId, CKU_CONTEXT_SPECIFIC, prefilledPasswordCallback.getPassword()); | ||
|
||
} catch (PKCS11Exception e) { | ||
if (e.getMessage().equals("CKR_PIN_INCORRECT")) | ||
throw new KeyPinDifferentFromTokenPinException(e); | ||
|
||
throw new GeneralSecurityException(e); | ||
} | ||
} | ||
|
||
private static boolean isAlwaysAuthenticate(CK_ATTRIBUTE[] attrs) { | ||
var result = attrs[0].pValue; | ||
if (result instanceof byte[]) { | ||
return ((byte[]) result)[0] == 1; | ||
} else { | ||
return false; // CKA_ALWAYS_AUTHENTICATE not found | ||
} | ||
} | ||
|
||
private static long getKeyID(PrivateKey pk) { | ||
try { | ||
var keyIDHolderField = pk.getClass().getSuperclass().getDeclaredField("keyIDHolder"); | ||
keyIDHolderField.setAccessible(true); | ||
var keyIDHolder = keyIDHolderField.get(pk); | ||
|
||
var keyIDField = keyIDHolder.getClass().getDeclaredField("keyID"); | ||
keyIDField.setAccessible(true); | ||
|
||
return (long) keyIDField.get(keyIDHolder); | ||
} catch (IllegalAccessException | NoSuchFieldException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static long getSessionId(Signature signature) { | ||
try { | ||
Field sigSpiField = signature.getClass().getDeclaredField("sigSpi"); | ||
sigSpiField.setAccessible(true); | ||
var sigSpi = sigSpiField.get(signature); | ||
|
||
var sessionField = sigSpi.getClass().getDeclaredField("session"); | ||
sessionField.setAccessible(true); | ||
var session = sessionField.get(sigSpi); | ||
|
||
var idField = session.getClass().getDeclaredField("id"); | ||
idField.setAccessible(true); | ||
|
||
return (long) idField.get(session); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static PKCS11 getP11(Signature signature) { | ||
try { | ||
Field sigSpiField = signature.getClass().getDeclaredField("sigSpi"); | ||
sigSpiField.setAccessible(true); | ||
var sigSpi = sigSpiField.get(signature); | ||
|
||
var tokenField = sigSpi.getClass().getDeclaredField("token"); | ||
tokenField.setAccessible(true); | ||
var token = tokenField.get(sigSpi); | ||
|
||
var p11Field = token.getClass().getDeclaredField("p11"); | ||
p11Field.setAccessible(true); | ||
var p11 = p11Field.get(token); | ||
|
||
return (PKCS11) p11; | ||
} catch (NoSuchFieldException | IllegalAccessException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
// copy & paste just to call overridden private sign method | ||
@Override | ||
public SignatureValue sign(ToBeSigned toBeSigned, SignatureAlgorithm signatureAlgorithm, DSSPrivateKeyEntry keyEntry) throws DSSException { | ||
assertEncryptionAlgorithmValid(signatureAlgorithm, keyEntry); | ||
|
||
final String javaSignatureAlgorithm = signatureAlgorithm.getJCEId(); | ||
final byte[] bytes = toBeSigned.getBytes(); | ||
AlgorithmParameterSpec param = null; | ||
if (signatureAlgorithm.getMaskGenerationFunction() != null) { | ||
param = createPSSParam(signatureAlgorithm.getDigestAlgorithm()); | ||
} | ||
|
||
try { | ||
final byte[] signatureValue = sign(bytes, javaSignatureAlgorithm, param, keyEntry); | ||
SignatureValue value = new SignatureValue(); | ||
value.setAlgorithm(signatureAlgorithm); | ||
value.setValue(signatureValue); | ||
return value; | ||
} catch (AutogramException e) { | ||
throw e; | ||
} catch (Exception e) { | ||
throw new DSSException(String.format("Unable to sign : %s", e.getMessage()), e); | ||
} | ||
} | ||
|
||
// copy & paste | ||
private void assertEncryptionAlgorithmValid(SignatureAlgorithm signatureAlgorithm, DSSPrivateKeyEntry keyEntry) { | ||
Objects.requireNonNull(signatureAlgorithm, "SignatureAlgorithm shall be provided."); | ||
Objects.requireNonNull(signatureAlgorithm.getEncryptionAlgorithm(), "EncryptionAlgorithm shall be provided within the SignatureAlgorithm."); | ||
Objects.requireNonNull(keyEntry, "keyEntry shall be provided."); | ||
if (!signatureAlgorithm.getEncryptionAlgorithm().isEquivalent(keyEntry.getEncryptionAlgorithm())) { | ||
throw new IllegalArgumentException(String.format("The provided SignatureAlgorithm '%s' cannot be used to sign with " + | ||
"the token's implied EncryptionAlgorithm '%s'", signatureAlgorithm.getName(), keyEntry.getEncryptionAlgorithm().getName())); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.