Skip to content

Commit

Permalink
#153 Delegated management token support (#155)
Browse files Browse the repository at this point in the history
* #153: Add initial functionality of including DM token calculation for INSTALL [for load]. Minor refactoring for more clarity.
* #153: Extract DM token addition to separate universal handler, use in all commands that require DM token. Replace private key loading implementation.
* #153: Simple tests for DelegatedManagementHandler
* #153: Refactor DAP properties assignment to separate class. Handle DAP in --install. Extract duplicated CAP loading to separate method. Declare DM token in GlobalPlatform and load it in transitDM. Refactoring.
* #153: Sign APDU data for token with crypto.Cipher (RSA/ECB/PKCS1Padding) instead of security.Signature
* #153: Fix introduced LGTM alert. Remove DAPProperties setters. Cipher back to Signature. Swap -token to -token-key.
* #153: Fix another LGTM error
* #153: Fix false assumption that INS_DELETE does not require zero-length of DM token to be transmitted
*  #153: Include required LFDB hash calculation for load if token-key exists (as per specification). Respect SHA256 option for load if DAP not required.
*  #153: Add tag '9E' before Delete Token
  • Loading branch information
gregorjohannson authored and martinpaljak committed May 3, 2019
1 parent 5fd2424 commit c8dad37
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 164 deletions.
54 changes: 54 additions & 0 deletions src/main/java/pro/javacard/gp/DAPProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package pro.javacard.gp;

import joptsimple.OptionSet;
import pro.javacard.AID;

import javax.smartcardio.CardException;

import static pro.javacard.gp.GPCommandLineInterface.OPT_DAP_DOMAIN;
import static pro.javacard.gp.GPCommandLineInterface.OPT_TO;

public class DAPProperties {
private AID targetDomain = null;
private AID dapDomain = null;
private boolean required = false;

public DAPProperties(OptionSet args, GlobalPlatform gp) throws CardException, GPException {
// Override target and check for DAP
if (args.has(OPT_TO)) {
targetDomain = AID.fromString(args.valueOf(OPT_TO));
if (gp.getRegistry().getDomain(targetDomain) == null) {
throw new GPException("Specified target domain is invalid: " + targetDomain);
}
if (gp.getRegistry().getDomain(targetDomain).getPrivileges().has(GPRegistryEntry.Privilege.DAPVerification))
required = true;
}

// Check if DAP block is required
for (GPRegistryEntryApp e : gp.getRegistry().allDomains()) {
if (e.getPrivileges().has(GPRegistryEntry.Privilege.MandatedDAPVerification))
required = true;
}

// Check if DAP is overriden
if (args.has(OPT_DAP_DOMAIN)) {
dapDomain = AID.fromString(args.valueOf(OPT_DAP_DOMAIN));
GPRegistryEntry.Privileges p = gp.getRegistry().getDomain(dapDomain).getPrivileges();
if (!(p.has(GPRegistryEntry.Privilege.DAPVerification) || p.has(GPRegistryEntry.Privilege.MandatedDAPVerification))) {
throw new GPException("Specified DAP domain does not have (Mandated)DAPVerification privilege: " + p.toString());
}
}
}

public AID getTargetDomain() {
return targetDomain;
}

public AID getDapDomain() {
return dapDomain;
}

public boolean isRequired() {
return required;
}
}
80 changes: 80 additions & 0 deletions src/main/java/pro/javacard/gp/DMTokenGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package pro.javacard.gp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.smartcardio.CommandAPDU;
import java.io.ByteArrayOutputStream;
import java.security.PrivateKey;
import java.security.Signature;

import static pro.javacard.gp.GlobalPlatform.INS_DELETE;

public class DMTokenGenerator {
private static final Logger logger = LoggerFactory.getLogger(DMTokenGenerator.class);
private static final String acceptedSignatureAlgorithm = "SHA1withRSA";

private PrivateKey key;

public DMTokenGenerator(PrivateKey key) {
this.key = key;
}

public CommandAPDU applyToken(CommandAPDU apdu) {
ByteArrayOutputStream newData = new ByteArrayOutputStream();

try {
newData.write(apdu.getData());
if (apdu.getINS() == INS_DELETE || apdu.getINS() == (INS_DELETE & 255)) {
// See GP 2.3.1 Table 11-23
logger.debug("Adding tag 0x9E before Delete Token");
newData.write(0x9E);
}
if (key == null) {
logger.debug("No private key for token generation provided");
newData.write(0); //Token length
} else {
logger.debug("Using private key for token generation (" + acceptedSignatureAlgorithm + ")");
byte[] token = calculateToken(apdu, key);
newData.write(token.length);
newData.write(token);
}
return new CommandAPDU(apdu.getCLA(), apdu.getINS(), apdu.getP1(), apdu.getP2(), newData.toByteArray());
} catch (Exception e) {
throw new RuntimeException("Could not add DM token to constructed APDU", e);
}
}

private static byte[] calculateToken(CommandAPDU apdu, PrivateKey key) {
return signData(key, getTokenData(apdu));
}

private static byte[] getTokenData(CommandAPDU apdu) {
try {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
bo.write(apdu.getP1());
bo.write(apdu.getP2());
bo.write(apdu.getData().length);
bo.write(apdu.getData());
return bo.toByteArray();
} catch (Exception e) {
throw new RuntimeException("Could not get P1/P2 or data for token calculation", e);
}
}

private static byte[] signData(PrivateKey privateKey, byte[] apduData) {
try {
Signature signature = Signature.getInstance(acceptedSignatureAlgorithm);
signature.initSign(privateKey);
signature.update(apduData);
return signature.sign();
} catch (Exception e) {
throw new RuntimeException("Could not create signature with instance " + acceptedSignatureAlgorithm, e);
}
}

public boolean hasKey() {
return key != null;
}

}
5 changes: 4 additions & 1 deletion src/main/java/pro/javacard/gp/GPCommandLineInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ abstract class GPCommandLineInterface {
protected final static String OPT_ACR_DELETE = "acr-delete";
protected final static String OPT_ACR_RULE = "acr-rule";
protected final static String OPT_ACR_CERT_HASH = "acr-hash";
protected final static String OPT_TOKEN_KEY = "token-key";
// TODO - include token "as is" with -token

protected static OptionSet parseArguments(String[] argv) throws IOException {
OptionSet args = null;
Expand Down Expand Up @@ -158,8 +160,9 @@ protected static OptionSet parseArguments(String[] argv) throws IOException {
parser.accepts(OPT_TODAY, "Set date to today when updating CPLC");

parser.accepts(OPT_STORE_DATA_BLOB, "STORE DATA blob").withRequiredArg().describedAs("data");
parser.accepts(OPT_STORE_DATA, "send STORE DATA commands").withRequiredArg().describedAs("data");
parser.accepts(OPT_STORE_DATA, "Send STORE DATA commands").withRequiredArg().describedAs("data");

parser.accepts(OPT_TOKEN_KEY, "Path to private key used in Delegated Management token generation").withRequiredArg().describedAs("path");

parser.accepts(OPT_MAKE_DEFAULT, "Make AID the default").withRequiredArg().describedAs("AID");
parser.accepts(OPT_RENAME_ISD, "Rename ISD").withRequiredArg().describedAs("new AID");
Expand Down
23 changes: 18 additions & 5 deletions src/main/java/pro/javacard/gp/GPCrypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package pro.javacard.gp;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
Expand All @@ -31,17 +32,15 @@
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import pro.javacard.AID;
import pro.javacard.gp.GPKey.Type;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
Expand Down Expand Up @@ -257,7 +256,7 @@ public static byte[] kcv_3des(GPKey key) {
}

// Get a public key from a PEM file, either public key or keypair
public static PublicKey pem2pubkey(InputStream in) throws IOException {
public static PublicKey pem2PublicKey(InputStream in) throws IOException {
try (PEMParser pem = new PEMParser(new InputStreamReader(in, StandardCharsets.US_ASCII))) {
Object ohh = pem.readObject();
if (ohh instanceof PEMKeyPair) {
Expand All @@ -275,4 +274,18 @@ public static PublicKey pem2pubkey(InputStream in) throws IOException {
} else throw new IllegalArgumentException("Can not read PEM");
}
}

// Get a private key from a PEM file, either private key or keypair
public static PrivateKey pem2PrivateKey(InputStream in) throws IOException {
try (PEMParser pem = new PEMParser(new InputStreamReader(in, StandardCharsets.US_ASCII))) {
Object ohh = pem.readObject();
if (ohh instanceof PEMKeyPair) {
PEMKeyPair kp = (PEMKeyPair) ohh;
return new JcaPEMKeyConverter().getKeyPair(kp).getPrivate();
} else if (ohh instanceof PrivateKeyInfo) {
return new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) ohh);
} else throw new IllegalArgumentException("Can not read PEM");
}
}

}
Loading

0 comments on commit c8dad37

Please sign in to comment.