Our Online Store have the new products: RFID antenna board. Currently it can work with JC10M24R and JCOP4 card chips.
Compared with normal cards, the antenna board module has a smaller size and fixed holes, which is easy to integrate in the IOT(Internet Of Things) project.

Step-by-step implementation of SCP11

JavaCard Applet Development Related Questions and Answers.
scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 6:19 am

In this artical, I'll show you how to implemented the Secure Channel Protocol 11 with the crypto library bouncycastle.

SCP11, the Secure Channel Protocol 11, is a new protocol proposed by the GlobalPlatform organization based on elliptic curve cryptography for mutual authentication and secure channel establishment. According to the document of the SCP11 specification, it was first released in May 2015. The latest version is v1.2 released in July 2018.

SCP11 realizes the inside and outside of the card, and the card part needs to cooperate with the GP in the card to modify some card configuration information to realize personalized instructions, such as STORE DATA, PUT KEY, etc. Consistent, so this article will gradually describe the key parts of the card's outer part of SCP11, as well as the problems and solutions that may be encountered during the development process.

The source code algorithm of this paper is based on the open source algorithm bouncycastle. For more details about the algorithm library, please visit the official website http://www.bouncycastle.org/
You do not have the required permissions to view the files attached to this post. Please login first.
Last edited by scplatform on Wed Sep 19, 2018 7:22 am, edited 3 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 6:43 am

Ⅰ.Firstly, TLVUtil
According Table 6-12: Certificate Format of GPCS 2.3 Amendment F v1.2, we need a utility class to manage TLV, and then inherit the certificate class from it.
0x01. First, we define two variables to store the data and length of TLV:

Code: Select all

byte[] data;
short dataSize;
0x02.And several constructors for various USES:

Code: Select all

/**
 * TLVUtil
 * @param buffer
 * @param offset
 * @param length
 * @throws Exception
 */
public TLVUtil(byte[] buffer, short offset, short length) throws Exception {
	short size = verifyFormat(buffer, offset, length);
	if (size < 0) {
		throw new Exception("tlv size error.");
	}
	try {
		byte[] tempData = new byte[size];
		System.arraycopy(buffer, offset, tempData, (short) 0, size);
		this.data = tempData;
		dataSize = size;
	} catch (Exception e) {

	}
}
/**
 * TLVUtil
 * @param tag
 * @param data
 */
public TLVUtil(byte[] tag, byte[] data) {
	
	short tagLen = parseTag(tag, (short)0, (short) tag.length);
	short lengthSize = TLVUtil.calcLengthSize(data.length);
	
	byte[] ret = new byte[tagLen + lengthSize + data.length];
	
	System.arraycopy(tag, 0, ret, 0, tagLen);
	short offset = TLVUtil.calcLengthValue(ret, tagLen, data.length);
	System.arraycopy(data, 0, ret, offset, data.length);
	
	//short size = verifyFormat(data, (short) 0, (short) data.length);
	this.data = ret;
	this.dataSize = (short) ret.length;
}

/**
 * TLVUtil
 * @param data
 */
public TLVUtil(byte[] data) {
	short size = verifyFormat(data, (short) 0, (short) data.length);
	this.data = data;
	this.dataSize = size;
}

/**
 * TLVUtil
 * @param data
 * @param size
 */
public TLVUtil(byte[] data, short size) {
	this.data = data;
	this.dataSize = size;
}
0x03. We hope to get value from specific offset:

Code: Select all

/**
 * TLVUtil
 * @param buffer
 * @param offset
 * @param length
 * @throws Exception
 */
public TLVUtil(byte[] buffer, short offset, short length) throws Exception {
	short size = verifyFormat(buffer, offset, length);
	if (size < 0) {
		throw new Exception("tlv size error.");
	}
	try {
		byte[] tempData = new byte[size];
		System.arraycopy(buffer, offset, tempData, (short) 0, size);
		this.data = tempData;
		dataSize = size;
	} catch (Exception e) {

	}
}
0x04.Some other interface to parse the LENGTH, and the TAG of TLV, reference the following source code for more details.

0x05.source code:

Code: Select all

/**
 * @file TLVUtil.java
 * @brief TODO
 * @author SCPlatform@outlook.com
 * @version 1.0
 * @date 2018-06-26
 *
 * @copyright SCPlatform@outlook.com All rights reserved.
 */
package com.scplatform.scp11;

import org.bouncycastle.util.Arrays;

/**
 * @author SCPlatform@outlook.com
 *
 */
public class TLVUtil {


	byte[] data;
	short dataSize;
	/**
	 * calcLengthSize
	 * @param valueSize
	 * @return
	 */
	public static short calcLengthSize(int vLen) {
		short lLen = 0;
		if (vLen < 128) {
			lLen = 1;
		} else if (vLen < 256) {
			lLen = 2;
		} else if (vLen < 65536) {
			lLen = 3;
		} else {
			lLen = 4;
		}
		return lLen;
	}

	/**
	 * calcLengthValue
	 * @param dist
	 * @param distOffset
	 * @param valueSize
	 * @return
	 */
	public static short calcLengthValue(byte[] dist, short distOffset, int valueSize) {
		if (valueSize < 128) {
			dist[distOffset++] = (byte) valueSize;
		} else if (valueSize < 256) {
			dist[distOffset++] = (byte) 0x81;
			dist[distOffset++] = (byte) valueSize;
		} else if (valueSize < 65536) {
			dist[distOffset++] = (byte) 0x82;
			dist[distOffset++] = (byte) ((valueSize >> 8) &0xFF);
			dist[distOffset++] = (byte) (valueSize&0xFF);
		} else {
			dist[distOffset++] = (byte) 0x83;
			dist[distOffset++] = (byte) ((valueSize >> 16) &0xFF);
			dist[distOffset++] = (byte) ((valueSize >> 8) &0xFF);
			dist[distOffset++] = (byte) (valueSize&0xFF);
		}
		return distOffset;
	}
	/**
	 * verifyFormat
	 * @param buffer
	 * @param offset
	 * @param length
	 * @return
	 */
	protected static short verifyFormat(byte[] buffer, short offset, short length) {
		short ret = 0;
		short tagLen = parseTag(buffer, offset, length);
		if (tagLen < 0) {
			return tagLen;
		}
		ret += tagLen;
		offset += tagLen;
		length -= tagLen;
		int len = parseLength(buffer, offset, length);

		ret += (len >> 16);
		offset += (len >> 16);
		length -= (len >> 16);
		if (length < (len & 0xffff)) {
			return (short) -1;
		}
		ret += (short) (len & 0xffff);
		return ret;
	}

	/**
	 * parseTag
	 * @param buffer
	 * @param offset
	 * @param length
	 * @return
	 */
	protected static short parseTag(byte[] buffer, short offset, short length) {
		short tagOff = offset;
		if (length < 1) {
			return (short) -1;
		}
		byte tag = buffer[tagOff++];
		if ((tag & 0x1f) == 0x1f) {
			while ((buffer[tagOff] & 0x80) != 0) {
				tagOff++;
				if (tagOff - offset > length) {
					return (short) -1;
				}
			}
			tagOff++;
			if (tagOff - offset > length) {
				return (short) -1;
			}
		}
		return (short) (tagOff - offset);
	}

	/**
	 * parseLength
	 * @param buffer
	 * @param offset
	 * @param length
	 * @return
	 */
	protected static int parseLength(byte[] buffer, short offset, short length) {
		if (length < 1) {
			return (short) -1;
		}
		byte len = buffer[offset++];
		if (len >= 0) // 0~7F
		{
			return 0x010000 | (len & 0xff);
		} else if (len == (byte) 0x81) {
			if (length < 2) {
				return (short) -1;
			}
			return 0x020000 | (buffer[offset] & 0xff);
		} else if (len == (byte) 0x82) {
			if (length < 3) {
				return (short) -1;
			}
			short val = getShort(buffer, offset);
			if (val < 0) {
				return (short) -1;
			}
			return 0x030000 | val;
		} else {
			return (short) (-1);
		}
	}

	/**
	 * TLVUtil
	 * @param buffer
	 * @param offset
	 * @param length
	 * @throws Exception
	 */
	public TLVUtil(byte[] buffer, short offset, short length) throws Exception {
		short size = verifyFormat(buffer, offset, length);
		if (size < 0) {
			throw new Exception("tlv size error.");
		}
		try {
			byte[] tempData = new byte[size];
			System.arraycopy(buffer, offset, tempData, (short) 0, size);
			this.data = tempData;
			dataSize = size;
		} catch (Exception e) {

		}
	}
	/**
	 * TLVUtil
	 * @param tag
	 * @param data
	 */
	public TLVUtil(byte[] tag, byte[] data) {
		
		short tagLen = parseTag(tag, (short)0, (short) tag.length);
		short lengthSize = TLVUtil.calcLengthSize(data.length);
		
		byte[] ret = new byte[tagLen + lengthSize + data.length];
		
		System.arraycopy(tag, 0, ret, 0, tagLen);
		short offset = TLVUtil.calcLengthValue(ret, tagLen, data.length);
		System.arraycopy(data, 0, ret, offset, data.length);
		
		//short size = verifyFormat(data, (short) 0, (short) data.length);
		this.data = ret;
		this.dataSize = (short) ret.length;
	}
	
	/**
	 * TLVUtil
	 * @param data
	 */
	public TLVUtil(byte[] data) {
		short size = verifyFormat(data, (short) 0, (short) data.length);
		this.data = data;
		this.dataSize = size;
	}

	/**
	 * TLVUtil
	 * @param data
	 * @param size
	 */
	public TLVUtil(byte[] data, short size) {
		this.data = data;
		this.dataSize = size;
	}

	/**
	 * getShort
	 * @param bytes
	 * @param offset
	 * @return
	 */
	public static short getShort(byte[] bytes, short offset) {
		return (short) ((0xff00 & (bytes[offset] << 8))|(0xff & bytes[offset + 1]));
	}

	/**
	 * tagEquals
	 * @param tag
	 * @return
	 */
	public boolean tagEquals(short tag) {
		short tagLen = parseTag(data, (short) 0, (short) this.dataSize);
		if (tagLen == 1) {
			return tag == (short) (data[(short) 0] & 0xff);
		} else if (tagLen == 2) {
			return tag == getShort(data, (short) 0);
		} else {
			return false;
		}
	}

	/**
	 * tagEquals
	 * @param tlv
	 * @return
	 */
	public boolean tagEquals(TLVUtil tlv) {
		short tagLen = parseTag(data, (short) 0, (short) this.dataSize);
		return (tagLen == parseTag(tlv.data, (short) 0, (short) tlv.dataSize) && Arrays.areEqual(tlv.data, data));
	}

	/**
	 * size
	 * @return
	 */
	public short size() {
		return (short) dataSize;
	}

	/**
	 * toBytes
	 * @param buffer
	 * @param offset
	 * @return
	 */
	public short toBytes(byte[] buffer, short offset) {
		short size = (short) dataSize;
		System.arraycopy(data, (short) 0, buffer, offset, size);
		return (short) (offset + size);
	}
	
	/**
	 * toBytes
	 * @return
	 */
	public byte[] toBytes() {
		
		return Arrays.clone(data);
	}

	/**
	 * getValue
	 * @param buffer
	 * @param offset
	 * @return
	 */
	public short getValue(byte[] buffer, short offset) {
		short tagLen = parseTag(data, (short) 0, (short) dataSize);
		int len = parseLength(data, tagLen, (short) (dataSize - tagLen));
		short valueLen = (short) (len & 0xffff);
		short lenLen = (short) (len >> 16);

		System.arraycopy(data, (short) (tagLen + lenLen), buffer, offset, valueLen);
		return valueLen;
	}

}
Last edited by scplatform on Wed Sep 19, 2018 7:24 am, edited 3 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 7:21 am

Ⅱ. Define a class to manage the certificate of SCP11, which inherited from TLVUtil..

0x01.According the certificate format table, we define the following field to manage the items of certificate.

Code: Select all

public byte[] tlv93;
public byte[] tlv42;
public byte[] tlv5F20;
public byte[] tlv95;
public byte[] tlv5F25;
public byte[] tlv5F24;
public byte[] tlv53;
public byte[] tlv73;
public byte[] tlvBF20;
public byte[] tlv7F49;
public byte[] tlv5F37;
0x02. Get sub tlv infomation from the certificate, e.g. the LENGTH offset, the VALUE offset, the VALUE length etc.

Code: Select all

/**
 * Get the Sub TLV's option offset or value length, option can be @see
 * OPTION_TAG_OFF.
 * 
 * @param tag
 * @param option
 * @return
 * @throws Exception
 */
public short getSubTLVInfo(short tag, byte option) throws Exception {
	short offset = 2;
	short result = -1;
	byte[] buffer = this.data;
	short length = this.size();

	int len = TLVUtil.parseLength(buffer, offset, (short) 5);// Len(L)
	short valueLength7F21 = (short) (len & 0xffff);// Len(V)
	short lenLength7F21 = (short) (len >> 16);// L LEN
	offset += lenLength7F21;
	short endOffset = (short) (offset + valueLength7F21);

	while (offset < endOffset) {
		short tagLength = TLVUtil.parseTag(buffer, offset, length);
		short tagX = -1;
		if (tagLength == 1) {
			tagX = (short) ((byte) buffer[offset] & 0xFF);
		} else if (tagLength == 2) {
			tagX = TLVUtil.getShort(buffer, offset);
		} else {
			throw new Exception("certificate data error");
		}

		len = TLVUtil.parseLength(buffer, (short) (offset + tagLength), length);// Len(L)
		short valueLength = (short) (len & 0xffff);// Len(V)
		short lenLength = (short) (len >> 16);// L LEN

		if (tag == tagX) {
			switch (option) {
			case OPTION_TAG_OFF:
				result = offset;
				break;
			case OPTION_LENGTH_OFF:
				result = (short) (offset + tagLength);
				break;
			case OPTION_VALUE_OFF:
				result = (short) (offset + tagLength + lenLength);
				break;
			case OPTION_VALUE_LENGTH:
				result = valueLength;
				break;
			default:
				// TODO:..
				break;
			}
			break;// jump out while.
		}
		offset += tagLength;
		offset += lenLength;
		offset += valueLength;
	}

	return result;
}
0x03. We can use getSubTLVInfo like this:

Code: Select all

/**
 * getSignatureMessage
 * @return
 */
public byte[] getSignatureMessage() {
	short offset = 0;
	short endOff = 0;
	try {
		short tagLen = TLVUtil.parseTag(this.data, offset, (short) 5);
		offset += tagLen;
		int dataLen = TLVUtil.parseLength(this.data, offset, (short) 5);
		
		short lenLen7F21 = (short) (dataLen >> 16);
		offset += lenLen7F21;

		endOff = getSubTLVInfo((short) 0x5F37, OPTION_TAG_OFF);
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return Arrays.copyOfRange(this.data, offset, endOff);
}
0x04.Get the sub TLV's value data with specific TAG.

Code: Select all

/**
 * Get the sub tag's value data.
 * 
 * @param tag
 *            the sub tag in 7F21, See Table 6-12. GPCS 2.3 AmdF v1.1.0.23.
 * @param buffer
 * @param offset
 * @return
 * @throws Exception
 */
public byte[] getSubTLVValue(short tag) throws Exception {
	byte[] result = null;
	byte[] buffer = this.data;
	short offset = 2;
	short length = this.size();

	int len = TLVUtil.parseLength(buffer, offset, length);// Len(L)
	short valueLength = (short) (len & 0xffff);// Len(V)
	short lenLength = (short) (len >> 16);// L LEN
	offset += lenLength;
	short endOffset = (short) (offset + valueLength);

	while (offset < endOffset) {
		short tagLength = TLVUtil.parseTag(buffer, offset, length);
		short tagX = -1;
		if (tagLength == 1) {
			tagX = (short) ((byte) buffer[offset]);
		} else if (tagLength == 2) {
			tagX = TLVUtil.getShort(buffer, offset);
		} else {
			throw new Exception("certificate data error");
		}

		len = TLVUtil.parseLength(buffer, (short) (offset + tagLength), length);// Len(L)
		valueLength = (short) (len & 0xffff);// Len(V)
		lenLength = (short) (len >> 16);// L LEN

		if (tag == tagX) {
			result = Arrays.copyOfRange(buffer, (offset + tagLength + lenLength),
					(offset + tagLength + lenLength + valueLength));
			break;
		}
		offset += tagLength;
		offset += lenLength;
		offset += valueLength;
	}

	return result;
}
0x05. For more details, see the source code:

Code: Select all

/**
 * @file CertificateSCP11.java
 * @brief TODO
 * @author SCPlatform@outlook.com
 * @version 1.0
 * @date 2018-06-25
 *
 * @copyright SCPlatform@outlook.com All rights reserved.
 */
package com.scplatform.scp11;

import java.security.interfaces.ECPublicKey;

import org.bouncycastle.util.Arrays;

/**
 * @author SCPlatform@outlook.com
 *
 */
public class CertificateSCP11 extends TLVUtil {

	public static final byte OPTION_TAG_OFF = (byte) 0;
	public static final byte OPTION_LENGTH_OFF = (byte) 1;
	public static final byte OPTION_VALUE_OFF = (byte) 2;
	public static final byte OPTION_VALUE_LENGTH = (byte) 3;

	public byte[] tlv93;
	public byte[] tlv42;
	public byte[] tlv5F20;
	public byte[] tlv95;
	public byte[] tlv5F25;
	public byte[] tlv5F24;
	public byte[] tlv53;
	public byte[] tlv73;
	public byte[] tlvBF20;
	public byte[] tlv7F49;
	public byte[] tlv5F37;
	
	public CertificateSCP11(byte[] data) {
		super(data);
	}

	public CertificateSCP11(byte[] buffer, short offset, short length) throws Exception {
		super(buffer, offset, length);
	}

	public byte[] get_93_csn() {
		return tlv93;
	}

	public void set_93_csn(byte[] _93_csn) {
		this.tlv93 = _93_csn;
	}

	public byte[] get_42_identifier() {
		return tlv42;
	}

	public void set_42_identifier(byte[] _42_identifier) {
		this.tlv42 = _42_identifier;
	}

	public byte[] get_5F20_subjectID() {
		return tlv5F20;
	}

	public void set_5F20_subjectID(byte[] _5f20_subjectID) {
		tlv5F20 = _5f20_subjectID;
	}

	public byte[] get_95_keyUsage() {
		return tlv95;
	}

	public void set_95_keyUsage(byte[] _95_keyUsage) {
		this.tlv95 = _95_keyUsage;
	}

	public byte[] get_5F25_EffectiveDate() {
		return tlv5F25;
	}

	public void set_5F25_EffectiveDate(byte[] _5f25_EffectiveDate) {
		tlv5F25 = _5f25_EffectiveDate;
	}

	public byte[] get_5F24_ExpirationDate() {
		return tlv5F24;
	}

	public void set_5F24_ExpirationDate(byte[] _5f24_ExpirationDate) {
		tlv5F24 = _5f24_ExpirationDate;
	}

	public byte[] get_53_DiscretionaryData() {
		return tlv53;
	}

	public void set_53_DiscretionaryData(byte[] _53_DiscretionaryData) {
		this.tlv53 = _53_DiscretionaryData;
	}

	public byte[] get_73_DiscretionaryData() {
		return tlv73;
	}

	public void set_73_DiscretionaryData(byte[] _73_DiscretionaryData) {
		this.tlv73 = _73_DiscretionaryData;
	}

	public byte[] get_BF20() {
		return tlvBF20;
	}

	public void set_BF20(byte[] _BF20) {
		this.tlvBF20 = _BF20;
	}

	public byte[] get_7F49_PubKey() {
		return tlv7F49;
	}

	public void set_7F49_PubKey(byte[] _7f49_PubKey) {
		tlv7F49 = _7f49_PubKey;
	}

	public byte[] get_5F37_Signature() {
		return tlv5F37;
	}

	public void set_5F37_Signature(byte[] _5f37_Signature) {
		tlv5F37 = _5f37_Signature;
	}

	/**
	 * Get the Sub TLV's option offset or value length, option can be @see
	 * OPTION_TAG_OFF.
	 * 
	 * @param tag
	 * @param option
	 * @return
	 * @throws Exception
	 */
	public short getSubTLVInfo(short tag, byte option) throws Exception {
		short offset = 2;
		short result = -1;
		byte[] buffer = this.data;
		short length = this.size();

		int len = TLVUtil.parseLength(buffer, offset, (short) 5);// Len(L)
		short valueLength7F21 = (short) (len & 0xffff);// Len(V)
		short lenLength7F21 = (short) (len >> 16);// L LEN
		offset += lenLength7F21;
		short endOffset = (short) (offset + valueLength7F21);

		while (offset < endOffset) {
			short tagLength = TLVUtil.parseTag(buffer, offset, length);
			short tagX = -1;
			if (tagLength == 1) {
				tagX = (short) ((byte) buffer[offset] & 0xFF);
			} else if (tagLength == 2) {
				tagX = TLVUtil.getShort(buffer, offset);
			} else {
				throw new Exception("certificate data error");
			}

			len = TLVUtil.parseLength(buffer, (short) (offset + tagLength), length);// Len(L)
			short valueLength = (short) (len & 0xffff);// Len(V)
			short lenLength = (short) (len >> 16);// L LEN

			if (tag == tagX) {
				switch (option) {
				case OPTION_TAG_OFF:
					result = offset;
					break;
				case OPTION_LENGTH_OFF:
					result = (short) (offset + tagLength);
					break;
				case OPTION_VALUE_OFF:
					result = (short) (offset + tagLength + lenLength);
					break;
				case OPTION_VALUE_LENGTH:
					result = valueLength;
					break;
				default:
					// TODO:..
					break;
				}
				break;// jump out while.
			}
			offset += tagLength;
			offset += lenLength;
			offset += valueLength;
		}

		return result;
	}

	/**
	 * Get the sub tag's value data.
	 * 
	 * @param tag
	 *            the sub tag in 7F21, See Table 6-12. GPCS 2.3 AmdF v1.1.0.23.
	 * @param buffer
	 * @param offset
	 * @return
	 * @throws Exception
	 */
	public byte[] getSubTLVValue(short tag) throws Exception {
		byte[] result = null;
		byte[] buffer = this.data;
		short offset = 2;
		short length = this.size();

		int len = TLVUtil.parseLength(buffer, offset, length);// Len(L)
		short valueLength = (short) (len & 0xffff);// Len(V)
		short lenLength = (short) (len >> 16);// L LEN
		offset += lenLength;
		short endOffset = (short) (offset + valueLength);

		while (offset < endOffset) {
			short tagLength = TLVUtil.parseTag(buffer, offset, length);
			short tagX = -1;
			if (tagLength == 1) {
				tagX = (short) ((byte) buffer[offset]);
			} else if (tagLength == 2) {
				tagX = TLVUtil.getShort(buffer, offset);
			} else {
				throw new Exception("certificate data error");
			}

			len = TLVUtil.parseLength(buffer, (short) (offset + tagLength), length);// Len(L)
			valueLength = (short) (len & 0xffff);// Len(V)
			lenLength = (short) (len >> 16);// L LEN

			if (tag == tagX) {
				result = Arrays.copyOfRange(buffer, (offset + tagLength + lenLength),
						(offset + tagLength + lenLength + valueLength));
				break;
			}
			offset += tagLength;
			offset += lenLength;
			offset += valueLength;
		}

		return result;
	}

	/**
	 * getSignatureMessage
	 * @return
	 */
	public byte[] getSignatureMessage() {
		short offset = 0;
		short endOff = 0;
		try {
			short tagLen = TLVUtil.parseTag(this.data, offset, (short) 5);
			offset += tagLen;
			int dataLen = TLVUtil.parseLength(this.data, offset, (short) 5);
			
			short lenLen7F21 = (short) (dataLen >> 16);
			offset += lenLen7F21;

			endOff = getSubTLVInfo((short) 0x5F37, OPTION_TAG_OFF);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return Arrays.copyOfRange(this.data, offset, endOff);
	}

	/**
	 * getSignatureBytes
	 * 
	 * @return
	 */
	public byte[] getSignatureBytes() {
		byte[] sig = null;
		try {
			sig = getSubTLVValue((short) 0x5F37);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sig;
	}

	public ECPublicKey getPublicKey() {
		byte[] pubk = null;
		try {
			pubk = getSubTLVValue((short) 0x7F49);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}
Last edited by scplatform on Wed Sep 19, 2018 7:25 am, edited 2 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 7:52 am

Ⅲ.Key parameter reference

0x01. Key parameter reference.
According the Table B-2: Key Parameter Reference Values of GPCS 2.3.1, we defined:

Code: Select all

public enum KeyParameterReference {
	KPR_CURVE_P256((short)0x00),
	KPR_CURVE_P384((short)0x01),
	KPR_CURVE_P512((short)0x02),
	KPR_CURVE_brainpoolP256r1((short)0x03),
	KPR_CURVE_brainpoolP256t1((short)0x04),
	KPR_CURVE_brainpoolP384r1((short)0x05),
	KPR_CURVE_brainpoolP384t1((short)0x06),
	KPR_CURVE_brainpoolP512r1((short)0x07),
	KPR_CURVE_brainpoolP512t1((short)0x08),
	KPR_CURVE_secp256k1((short) 0xDFFE);
	
	private short kpr = 0;
	private KeyParameterReference(short _kpr)
	{
		this.kpr = _kpr;
	}
	public short getKPRValue()
	{
		return this.kpr;
	}
}
Note:KPR_CURVE_secp256k1 is defined by Jubiter Wallet, site:http://www.jubiterwallet.com/
Last edited by scplatform on Wed Sep 19, 2018 7:25 am, edited 3 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 8:00 am

Ⅳ.Curve, in order to configuration the curve parameters, we defined an ENUM to manage EC curve.

0x01.Curve parameters, we used two curves:

Code: Select all

SECP_256k1("0000000000000000000000000000000000000000000000000000000000000000",
		"0000000000000000000000000000000000000000000000000000000000000007",
		"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
		"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
		"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
		"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
		1,
		KeyParameterReference.KPR_CURVE_secp256k1),

NIST_256("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
		"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
		"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
		"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
		"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
		"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0",
		1,
		KeyParameterReference.KPR_CURVE_P256);
0x02. Use ENUM to manage the parameters:

Code: Select all

public enum EC_Curve {	
	String _p = null;
	String _a = null;
	String _b = null;
	String _n = null;
	String _gx = null;
	String _gy = null;
	int _k = 1;
	KeyParameterReference _kpr;
	EllipticCurve curve;
	ECParameterSpec ecSpec;
	
	private EC_Curve(String a, String b, String p, String n, String gx, String gy, int k, KeyParameterReference kpr) {
		this._a = a;
		this._b = b;
		this._p = p;
		this._n = n;
		this._gx = gx;
		this._gy = gy;
		this._k = k;
		curve = new EllipticCurve(
			new ECFieldFp(new BigInteger(this._p, 16)), // p
			new BigInteger(this._a, 16), // a
			new BigInteger(this._b, 16)); // b
	         ecSpec = new ECParameterSpec(curve,
			new ECPoint(new BigInteger(this._gx, 16), new BigInteger(this._gy, 16)), // G
			new BigInteger(this._n, 16), // order
			this._k); // h
       }
}
0x03.We hope to generate ECPublicKey or ECPrivateKey with specific curve parameters, so we can use the ECParameterSpec field ecSpec, build private key from string value:

Code: Select all

	/**
	 * buildPrivKeyFromValue
	 * @param kvalue
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws InvalidKeySpecException
	 */
	public ECPrivateKey buildPrivKeyFromValue(byte[] kvalue) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
	{	
		if((kvalue[0] & 0x80) == 0x80)
		{
			byte[] tmp = new byte[kvalue.length + 1];
			tmp[0] = 0;
			System.arraycopy(kvalue, 0, tmp, 1, kvalue.length);	
			kvalue = tmp;
		}
		BigInteger privKey = new BigInteger(kvalue);
		//privKey = privKey.abs();
		ECPrivateKeySpec keySpec = new ECPrivateKeySpec(privKey, ecSpec);
		KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
		ECPrivateKey ecprivKey = (ECPrivateKey) fact.generatePrivate(keySpec);
		
		return ecprivKey;
	}
0x04. Build public key from string value:

Code: Select all

/**
 * 
 * @param kvalue
 * @return
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 * @throws InvalidKeySpecException
 */
public ECPublicKey buildPubKeyFromValue(byte[] kvalue) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
{	
	ECPoint point = ECPointUtil.decodePoint(curve, kvalue);
	ECPublicKeySpec keySpec = new ECPublicKeySpec(point, ecSpec);
	KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
	ECPublicKey pubKey = (ECPublicKey) fact.generatePublic(keySpec);
	
	return pubKey;
}
0x05. or generate a keypair:

Code: Select all

/**
 * Generate a key pair with specific curve.
 * @return a key pair
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 * @throws InvalidAlgorithmParameterException
 */
public KeyPair genKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {

	
	KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
	keyGen.initialize(ecSpec);

	return keyGen.generateKeyPair();
}
0x06. For more details, see the source code.

Code: Select all

/**
 * @file CurveParameters.java
 * @brief TODO
 * @author SCPlatform@outlook.com
 * @version 1.0
 * @date 2018-06-25
 *
 * @copyright SCPlatform@outlook.com All rights reserved.
 */
package com.scplatform.scp11;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;


import org.bouncycastle.jce.ECPointUtil;

/**
 * @author SCPlatform@outlook.com
 *
 */
public enum EC_Curve {
	SECP_256k1("0000000000000000000000000000000000000000000000000000000000000000",
			"0000000000000000000000000000000000000000000000000000000000000007",
			"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
			"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
			"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
			"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
			1,
			KeyParameterReference.KPR_CURVE_secp256k1),

	NIST_256("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
			"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
			"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
			"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
			"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
			"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0",
			1,
			KeyParameterReference.KPR_CURVE_P256);
	
	String _p = null;
	String _a = null;
	String _b = null;
	String _n = null;
	String _gx = null;
	String _gy = null;
	int _k = 1;
	KeyParameterReference _kpr;
	EllipticCurve curve;
	ECParameterSpec ecSpec;
	
	private EC_Curve(String a, String b, String p, String n, String gx, String gy, int k, KeyParameterReference kpr) {
		this._a = a;
		this._b = b;
		this._p = p;
		this._n = n;
		this._gx = gx;
		this._gy = gy;
		this._k = k;
		curve = new EllipticCurve(
				new ECFieldFp(new BigInteger(this._p, 16)), // p
				new BigInteger(this._a, 16), // a
				new BigInteger(this._b, 16)); // b
		ecSpec = new ECParameterSpec(curve,
				new ECPoint(new BigInteger(this._gx, 16), new BigInteger(this._gy, 16)), // G
				new BigInteger(this._n, 16), // order
				this._k); // h
	}

	/**
	 * Generate a key pair with specific curve.
	 * @return a key pair
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws InvalidAlgorithmParameterException
	 */
	public KeyPair genKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {

		
		KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
		keyGen.initialize(ecSpec);

		return keyGen.generateKeyPair();
	}
/**
 * 
 * @param kvalue
 * @return
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 * @throws InvalidKeySpecException
 */
public ECPublicKey buildPubKeyFromValue(byte[] kvalue) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
{	
	ECPoint point = ECPointUtil.decodePoint(curve, kvalue);
	ECPublicKeySpec keySpec = new ECPublicKeySpec(point, ecSpec);
	KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
	ECPublicKey pubKey = (ECPublicKey) fact.generatePublic(keySpec);
	
	return pubKey;
}
	/**
	 * buildPrivKeyFromValue
	 * @param kvalue
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws InvalidKeySpecException
	 */
	public ECPrivateKey buildPrivKeyFromValue(byte[] kvalue) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
	{	
		if((kvalue[0] & 0x80) == 0x80)
		{
			byte[] tmp = new byte[kvalue.length + 1];
			tmp[0] = 0;
			System.arraycopy(kvalue, 0, tmp, 1, kvalue.length);	
			kvalue = tmp;
		}
		BigInteger privKey = new BigInteger(kvalue);
		//privKey = privKey.abs();
		ECPrivateKeySpec keySpec = new ECPrivateKeySpec(privKey, ecSpec);
		KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
		ECPrivateKey ecprivKey = (ECPrivateKey) fact.generatePrivate(keySpec);
		
		return ecprivKey;
	}
}

Last edited by scplatform on Wed Sep 19, 2018 7:27 am, edited 1 time in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 8:15 am

Ⅴ. Static information of offcard
0x01.static key and parameters
There are a lot of items you need to manage, the Key Parameter Reference, the chain of OCE certifcate, and device certificate if you want to support personalize you device.

HostID can be present while SCP11a/b/c, but CardGroupID only be present when SCP11c, and SIN/SDIN for SCP11a/b.
The certificate chain can take on a variety of variations, as well as multiple authorization levels.

0x02.Session info
For the session of SCP11, we should choice the option 'i', SCP11a, b, or c. and the Security Level. There are two levels, they are C_MAC|R_MAC or C_MAC|C_DECRYPTION|R_MAC|R_ENCRYPTION.

The static key version which value defined in section 0x01.

Key length of the AES:AES_128 bit, AES_192 bit or AES_256 bit.

Message digest algorithms generally depend on the length of the ecc algorithm key.

Finaly ,HostID which is optional.
0x03. Source code
We use List<String> to store the certificate info,

Code: Select all

/**
 * @file SCP11StaticInfo.java
 * @brief TODO
 * @author SCPlatform@outlook.com
 * @version 1.0
 * @date 2018-06-26
 *
 * @copyright SCPlatform@outlook.com All rights reserved.
 */
package com.scplatform.scp11;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author SCPlatform@outlook.com
 *
 */
public class SCP11StaticInfo extends StaticInfo {
	public static final byte INDEX_CERT = 0;
	public static final byte INDEX_PK = 1;
	public static final byte INDEX_SK = 2;
	
	public static final byte INDEX_CERT_KLCC = 0;
	public static final byte INDEX_CERT_SD = -1;//list.size() -1
	public static final byte INDEX_CERT_KLOC = 0;
	public static final byte INDEX_CERT_OCE = -1;//list.size() -1
	

	KeyParameterReference kpr;
	boolean includeHostID = true;	
	EC_Curve curve;
	
	byte keyLength = 0x10;
	byte kid_ca_kloc = 0x10;
	short apdu_block_size = 240;
	String hostID;
	String sdin;
	String sin;	
	String key_dek;//static
	//MessageDigest digest;
	
	List<List<String>> oce_cert_chain;
	List<List<String>> sd_cert_chain;
	String epk_oce_ecka_value = null;
	String esk_oce_ecka_value = null;

	public SCP11StaticInfo()
	{
		super();
		
		this.kpr = KeyParameterReference.KPR_CURVE_secp256k1;
		this.includeHostID = true;
		this.curve = EC_Curve.SECP_256k1;
		this.keyLength = 0x10;
		this.kid_ca_kloc = 0x10;
		this.hostID = "8080808080808080";
		this.sdin = null;
		this.sin = null;
		this.key_dek = null;
		this.epk_oce_ecka_value = null;
		this.esk_oce_ecka_value = null;
		
		String cert_kloc_ecdsa_value = "";
		String pk_kloc_ecdsa_value = "";
		String sk_kloc_ecdsa_value = null;
		
		List<String> cert_kloc_ecdsa = new ArrayList<>();
		cert_kloc_ecdsa.add(INDEX_CERT, cert_kloc_ecdsa_value);
		cert_kloc_ecdsa.add(INDEX_PK, pk_kloc_ecdsa_value);
		cert_kloc_ecdsa.add(INDEX_SK, sk_kloc_ecdsa_value);
		
		String cert_oce_ecka_value = "";
		String pk_oce_ecka_value = "";
		String sk_oce_ecka_value = "";
		
		List<String> cert_oce_ecka = new ArrayList<>();
		cert_oce_ecka.add(INDEX_CERT, cert_oce_ecka_value);
		cert_oce_ecka.add(INDEX_PK, pk_oce_ecka_value);
		cert_oce_ecka.add(INDEX_SK, sk_oce_ecka_value);
		
		oce_cert_chain = new ArrayList<List<String>>();
		oce_cert_chain.add(cert_kloc_ecdsa);
		oce_cert_chain.add(cert_oce_ecka);
		
		//*****************************
		String cert_klcc_ecdsa_value = "";
		String pk_klcc_ecdsa_value = "";
		String sk_klcc_ecdsa_value = "";
		
		List<String> cert_klcc_ecdsa = new ArrayList<>();
		cert_klcc_ecdsa.add(INDEX_CERT, cert_klcc_ecdsa_value);
		cert_klcc_ecdsa.add(INDEX_PK, pk_klcc_ecdsa_value);
		cert_klcc_ecdsa.add(INDEX_SK, sk_klcc_ecdsa_value);
		
		String cert_sd_ecka_value = "";
		String pk_sd_ecka_value = "";
		String sk_sd_ecka_value = "";
		
		List<String> cert_sd_ecka = new ArrayList<>();
		cert_sd_ecka.add(INDEX_CERT, cert_sd_ecka_value);
		cert_sd_ecka.add(INDEX_PK, pk_sd_ecka_value);
		cert_sd_ecka.add(INDEX_SK, sk_sd_ecka_value);
		
		sd_cert_chain = new ArrayList<List<String>>();
		sd_cert_chain.add(cert_klcc_ecdsa);
		sd_cert_chain.add(cert_sd_ecka);
		
	}
	public void loadSettings(String setFilePath) throws IOException {
		BufferedReader br=null;
		//ArrayList<String> list = new ArrayList<String>();
		try
		{
			br = new BufferedReader(new FileReader(setFilePath));
			String line = null;
			while((line = br.readLine()) != null)
			{
//				JSONObject jsonObj = new JSONObject(line);
//				JSONArray scp11c = jsonObj.getJSONArray("SCP11c");//
//				
//				for(int i=0; i<scp11c.length(); i++)
//				{
//					jsonObj = scp11c.getJSONObject(i);
//					JSONObject kpr = jsonObj.getJSONObject("KPR");
//					//TODO:....
//					
//				}
			}
			br.close();
			
		}catch(Exception exp)
		{
			exp.printStackTrace();
		}
		finally
		{
			if(br != null)
				br.close();
		}		
	}

	public void saveSettings(String setFilePath) {
		// TODO Auto-generated method stub
		
	}
	public KeyParameterReference getKpr() {
		return kpr;
	}
	public void setKpr(KeyParameterReference kpr) {
		this.kpr = kpr;
	}
	public boolean isIncludeHostID() {
		return includeHostID;
	}
	public void setIncludeHostID(boolean includeHostID) {
		this.includeHostID = includeHostID;
	}
	public EC_Curve getCurve() {
		return curve;
	}
	public void setCurve(EC_Curve curve) {
		this.curve = curve;
	}
	public byte getKeyLength() {
		return keyLength;
	}
	public void setKeyLength(byte keyLength) {
		this.keyLength = keyLength;
	}
	public byte getKid_ca_kloc() {
		return kid_ca_kloc;
	}
	public void setKid_ca_kloc(byte kid_ca_kloc) {
		this.kid_ca_kloc = kid_ca_kloc;
	}
	public short getApdu_block_size() {
		return apdu_block_size;
	}
	public void setApdu_block_size(short apdu_block_size) {
		this.apdu_block_size = apdu_block_size;
	}
	public String getHostID() {
		return hostID;
	}
	public void setHostID(String hostID) {
		this.hostID = hostID;
	}
	public String getSdin() {
		return sdin;
	}
	public void setSdin(String sdin) {
		this.sdin = sdin;
	}
	public String getSin() {
		return sin;
	}
	public void setSin(String sin) {
		this.sin = sin;
	}
	public String getKey_dek() {
		return key_dek;
	}
	public void setKey_dek(String key_dek) {
		this.key_dek = key_dek;
	}
	public List<List<String>> getOce_cert_chain() {
		return oce_cert_chain;
	}
	public void setOce_cert_chain(List<List<String>> oce_cert_chain) {
		this.oce_cert_chain = oce_cert_chain;
	}
	public List<List<String>> getSd_cert_chain() {
		return sd_cert_chain;
	}
	public void setSd_cert_chain(List<List<String>> sd_cert_chain) {
		this.sd_cert_chain = sd_cert_chain;
	}
	public String getEpk_oce_ecka_value() {
		return epk_oce_ecka_value;
	}
	public void setEpk_oce_ecka_value(String epk_oce_ecka_value) {
		this.epk_oce_ecka_value = epk_oce_ecka_value;
	}
	public String getEsk_oce_ecka_value() {
		return esk_oce_ecka_value;
	}
	public void setEsk_oce_ecka_value(String esk_oce_ecka_value) {
		this.esk_oce_ecka_value = esk_oce_ecka_value;
	}

}
0x04. the static key and session parameter may be store in a json file.
e.g.

Code: Select all

"SCP11c": {
    "KPR": "\u0000", 
    "HostID": "8080808080808080", 
    "OCE": [
        [
            "", 
            "", 
            ""
        ], 
        [
            "", 
            "", 
            ""
        ]
    ], 
    "KID_CA_KLOC": 16, 
    "CardGroupID": "01020304", 
    "Curve": {
        "A": "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 
        "B": "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 
        "G": "0432C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 
        "k": "01", 
        "N": "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 
        "P": "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"
    }, 
    "IncludeHostID": true, 
    "Key_DEK": "", 
    "eKeyPair": [
        "", 
        ""
    ], 
    "KeyLength": 16, 
    "Digest": 33, 
    "SD": [
        [
            "", 
            "", 
            ""
        ], 
        [
            "", 
            "", 
            ""
        ]
    ]
}

You do not have the required permissions to view the files attached to this post. Please login first.
Last edited by scplatform on Wed Sep 19, 2018 7:31 am, edited 5 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 8:16 am

Ⅵ.Control Reference Template A6

0x01.CRTA6 used in INTERNAL and MUTUAL AUTH command
0x02. Class CRTA6 to manage the information

Code: Select all

public class CRTA6 {
	public static final byte OFFSET_90 = 0;
	public static final byte SIZE_90 = 2;
	public static final byte OFFSET_95 = 2;
	public static final byte OFFSET_80 = 3;
	public static final byte OFFSET_81 = 4;
	public static final byte SIZE_ONE = 1;
	
	byte[] _a6Content = null;
	byte[] _keyUsage;
	byte[] _keyType;
	byte[] _keyLength;
	byte[] _hostID;
	byte[] _scpIDParam;
	
	public CRTA6(byte[] a6Content, byte[] hostID)
	{
		_a6Content = a6Content;
 		this._keyUsage = Arrays.copyOfRange(_a6Content, OFFSET_95, (OFFSET_95 + SIZE_ONE));
		this._keyType =  Arrays.copyOfRange(_a6Content, OFFSET_80, (OFFSET_80 + SIZE_ONE));
		this._keyLength =Arrays.copyOfRange(_a6Content, OFFSET_81, (OFFSET_81 + SIZE_ONE));
		this._hostID =  hostID;
		this._scpIDParam = Arrays.copyOfRange(_a6Content, OFFSET_90, (OFFSET_90 + SIZE_90));
	}
}
0x03. Get SharedInfo from CRTA6
Get the Shared Info form the CRTA6 while deriveSessionKey

Code: Select all

/**
 * Get SharedInfo from CRTA6.
 * @return
 */
public byte[] getSharedInfo()
{
	short len = (short) (this._keyUsage.length + this._keyType.length + this._keyLength.length + this._hostID.length + 1);
	byte[] ret = new byte[len];
	short offset = 0;
	System.arraycopy(this._keyUsage, offset, ret, offset, this._keyUsage.length);
	offset += this._keyUsage.length;
	
	System.arraycopy(this._keyType, 0, ret, offset, this._keyType.length);
	offset += this._keyType.length;
	
	System.arraycopy(this._keyLength, 0, ret, offset, this._keyLength.length);
	offset += this._keyLength.length;
	
	ret[offset++] = (byte) this._hostID.length;
	System.arraycopy(this._hostID, 0, ret, offset, this._hostID.length);
	return ret;
}
0x04. SharedInfo
According the GPCS 2.3 Amd F v 1.2 Sec.6.5.2.3, the SharedInfo consists of the following values.
  • Key usage qualifier (1 byte)
  • Key type (1 byte)
  • Key length (1 byte)
  • If Host and Card ID are requested:
  • o In the case of SCP11a and SCP11b: HostID-LV, SIN-LV, and SDIN-LV
  • o In the case of SCP11c: HostID-LV and Card Group ID-LV
0x05.Source code

Code: Select all

/**
 * @file CRTA6.java
 * @brief TODO
 * @author SCPlatform@outlook.com
 * @version 1.0
 * @date 2018-06-25
 *
 * @copyright SCPlatform@outlook.com All rights reserved.
 */
package com.scplatform.scp11;

import java.util.Arrays;

/**
 * @author SCPlatform@outlook.com
 *
 */
public class CRTA6 {
	public static final byte OFFSET_90 = 0;
	public static final byte SIZE_90 = 2;
	public static final byte OFFSET_95 = 2;
	public static final byte OFFSET_80 = 3;
	public static final byte OFFSET_81 = 4;
	public static final byte SIZE_ONE = 1;
	
	byte[] _a6Content = null;
	byte[] _keyUsage;
	byte[] _keyType;
	byte[] _keyLength;
	byte[] _hostID;
	byte[] _scpIDParam;
	
	public CRTA6(byte[] a6Content, byte[] hostID)
	{
		_a6Content = a6Content;
 		this._keyUsage = Arrays.copyOfRange(_a6Content, OFFSET_95, (OFFSET_95 + SIZE_ONE));
		this._keyType =  Arrays.copyOfRange(_a6Content, OFFSET_80, (OFFSET_80 + SIZE_ONE));
		this._keyLength =Arrays.copyOfRange(_a6Content, OFFSET_81, (OFFSET_81 + SIZE_ONE));
		this._hostID =  hostID;
		this._scpIDParam = Arrays.copyOfRange(_a6Content, OFFSET_90, (OFFSET_90 + SIZE_90));
	}
	
	public byte[] get_keyUsage() {
		return _keyUsage;
	}
	public void set_keyUsage(byte[] _keyUsage) {
		this._keyUsage = _keyUsage;
	}
	public byte[] get_keyType() {
		return _keyType;
	}
	public void set_keyType(byte[] _keyType) {
		this._keyType = _keyType;
	}
	public byte[] get_keyLength() {
		return _keyLength;
	}
	public void set_keyLength(byte[] _keyLength) {
		this._keyLength = _keyLength;
	}
	public byte[] get_hostID() {
		return _hostID;
	}
	public void set_hostID(byte[] _hostID) {
		this._hostID = _hostID;
	}
	public byte[] get_scpIDParam() {
		return _scpIDParam;
	}
	public void set_scpIDParam(byte[] _scpIDParam) {
		this._scpIDParam = _scpIDParam;
	}
	/**
	 * Get SharedInfo from CRTA6.
	 * @return
	 */
	public byte[] getSharedInfo()
	{
		short len = (short) (this._keyUsage.length + this._keyType.length + this._keyLength.length + this._hostID.length + 1);
		byte[] ret = new byte[len];
		short offset = 0;
		System.arraycopy(this._keyUsage, offset, ret, offset, this._keyUsage.length);
		offset += this._keyUsage.length;
		
		System.arraycopy(this._keyType, 0, ret, offset, this._keyType.length);
		offset += this._keyType.length;
		
		System.arraycopy(this._keyLength, 0, ret, offset, this._keyLength.length);
		offset += this._keyLength.length;
		
		ret[offset++] = (byte) this._hostID.length;
		System.arraycopy(this._hostID, 0, ret, offset, this._hostID.length);
		return ret;
	}
	/**
	 * toBytes
	 * @return
	 */
	public byte[] toBytes()
	{
		TLVUtil _90TLV = new TLVUtil(new byte[]{(byte) 0x90}, this._scpIDParam);
		TLVUtil _95TLV = new TLVUtil(new byte[]{(byte) 0x95}, this._keyUsage);
		TLVUtil _80TLV = new TLVUtil(new byte[]{(byte) 0x80}, this._keyType);
		TLVUtil _81TLV = new TLVUtil(new byte[]{(byte) 0x81}, this._keyLength);
		TLVUtil _84TLV = new TLVUtil(new byte[]{(byte) 0x84}, this._hostID);
		byte[] ret = new byte[_90TLV.size() + _95TLV.size() + _80TLV.size() + _81TLV.size() + _84TLV.size()];
		
		short offset = 0;
		offset = _90TLV.toBytes(ret, offset);
		offset = _95TLV.toBytes(ret, offset);
		offset = _80TLV.toBytes(ret, offset);		
		offset = _81TLV.toBytes(ret, offset);
		offset = _84TLV.toBytes(ret, offset);
		
		TLVUtil a6TLV = new TLVUtil(new byte[]{(byte) 0xA6}, ret);
		return a6TLV.toBytes();
	}
}

You do not have the required permissions to view the files attached to this post. Please login first.
Last edited by scplatform on Wed Sep 19, 2018 7:50 am, edited 3 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 8:17 am

Ⅶ.SCP11Lib
SCP11Lib consists of multiple functions that are used when the secure channel is opened.

0x01.deriveFunction

This function is core. Here, we use the KDF object of KDF2BytesGenerator of the algorithm library, directly initialize the corresponding secret and SharedInfo information, and then call the generateBytes() interface to generate the session key. Of course, the most important parameter is the length of the key to be generated.

Code: Select all

/**
 * X9.63 key derivation function, Derive AES Session key from secret and
 * sharedInfo.
 * 
 * @param zab
 *            secret
 * @param sharedInfo
 *            sharedInfo
 * @param keydatalen
 *            the byte length of the keying data to generate.e.g. 16*5
 * @return 5 keys
 */
public static byte[] deriveFunction(byte[] za, byte[] zb, byte[] sharedInfo, int keyDataLen) {
	int len = za.length + zb.length;
	byte[] secret = new byte[len];
	byte[] keys = new byte[keyDataLen];

	short offset = 0;
	System.arraycopy(za, 0, secret, offset, za.length);
	offset += za.length;

	System.arraycopy(zb, 0, secret, offset, zb.length);
	offset += zb.length;

	SHA256Digest hash = new SHA256Digest();
	KDF2BytesGenerator kdf = new KDF2BytesGenerator(hash);
	kdf.init(new KDFParameters(secret, sharedInfo));
	kdf.generateBytes(keys, (short) 0, keyDataLen);

	return keys;
}
0x02.Generate secret
The secret information is actually generated by the KeyAgreement algorithm, with SK and/or PK of the communicating parties as the parameters

Code: Select all

/**
 * deriveSessionKey
 * 
 * @param pk_sd_ecka
 * @param sk_oce_ecka
 * @param esk_oce_ecka
 * @param shared_info
 * @param keyLength
 * @return
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 * @throws InvalidKeyException
 */
public static byte[] deriveSessionKey(ECPublicKey pk_sd_ecka, ECPrivateKey sk_oce_ecka, ECPrivateKey esk_oce_ecka,
		byte[] shared_info, int keyLength)
		throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {

	KeyAgreement aKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
	MessageDigest hash = MessageDigest.getInstance("SHA1", "BC");

	aKeyAgree.init(esk_oce_ecka);
	aKeyAgree.doPhase(pk_sd_ecka, true);
	

	// generate the key bytes
	byte[] retx = aKeyAgree.generateSecret();
	byte[] shses = hash.digest(retx);
	
	aKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
	hash = MessageDigest.getInstance("SHA1", "BC");
	aKeyAgree.init(sk_oce_ecka);
	aKeyAgree.doPhase(pk_sd_ecka, true);

	hash.reset();
	byte[] ret = aKeyAgree.generateSecret();
	byte[] shsss = hash.digest(ret);

	return deriveFunction(shses, shsss, shared_info, keyLength);
}
0x03.MAC generation
Use CMac to generate the MAC of session, also the receipt. it's is the same with SCP03.

Code: Select all

/**
 * genCMAC
 * 
 * @param key
 * @param msg
 * @return
 */
public static byte[] genCMAC(byte[] key, byte[] msg) {
	BlockCipher cipher = new AESEngine();
	CMac cmac = new CMac(cipher);
	cmac.init(new KeyParameter(key));
	cmac.update(msg, 0, msg.length);
	byte[] out = new byte[cmac.getMacSize()];
	cmac.doFinal(out, 0);
	return out;
}
0x04.Source code

Code: Select all

/**
 * @file SCP11.java
 * @brief The implementation of SCP11.
 * @author SCPlatform@outlook.com
 * @version 1.0
 * @date 2018-06-25
 *
 * @copyright SCPlatform@outlook.com All rights reserved.
 */
package com.scplatform.scp11;

import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.KeyAgreement;
import javax.smartcardio.CommandAPDU;

import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
import org.bouncycastle.crypto.macs.CMac;
import org.bouncycastle.crypto.params.KDFParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;

public class SCP11Lib {
	public static final short SEC_TRUE = 0x5A5A;
	public static final short SEC_FALSE = (short) 0xA5A5;	
	
	public static byte[] pad80(byte[] text, int offset, int length, int blocksize) {
		if (length == -1) {
			length = text.length - offset;
		}
		int totalLength = length;
		for (totalLength++; (totalLength % blocksize) != 0; totalLength++) {
			;
		}
		int padlength = totalLength - length;
		byte[] result = new byte[totalLength];
		System.arraycopy(text, offset, result, 0, length);
		result[length] = (byte) 0x80;
		for (int i = 1; i < padlength; i++) {
			result[length + i] = (byte) 0x00;
		}
		return result;
	}

	public static byte[] pad80(byte[] text, int blocksize) {
		return pad80(text, 0, text.length, blocksize);
	}

	public static byte[] unpad80(byte[] text) throws Exception {
		if (text.length < 1)
			throw new Exception("Invalid ISO 7816-4 padding");
		int offset = text.length - 1;
		while (offset > 0 && text[offset] == 0) {
			offset--;
		}
		if (text[offset] != (byte) 0x80) {
			throw new Exception("Invalid ISO 7816-4 padding");
		}
		return Arrays.copyOf(text, offset);
	}

	private static void buffer_increment(byte[] buffer, int offset, int len) {
		if (len < 1)
			return;
		for (int i = offset + len - 1; i >= offset; i--) {
			if (buffer[i] != (byte) 0xFF) {
				buffer[i]++;
				break;
			} else
				buffer[i] = (byte) 0x00;
		}
	}

	public static void buffer_increment(byte[] buffer) {
		buffer_increment(buffer, 0, buffer.length);
	}

	/**
	 * Verify certificate by parent public key.
	 * 
	 * @param cert
	 *            Certificate which to be verified, the element of 5F73 mast be
	 *            present.
	 * @param parentPubKey
	 *            public key object used to verify the cert.
	 * @param hash
	 *            Message digest object
	 * @throws NoSuchProviderException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 * @return 0x5A5A:success, 0xA5A5:false
	 */
	public static short verifyCert(CertificateSCP11 cert, PublicKey parentPubKey)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
		byte[] message = cert.getSignatureMessage();
		byte[] sigBytes = cert.getSignatureBytes();
		Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
		// verify a signature

		signature.initVerify(parentPubKey);

		signature.update(message);

		if (signature.verify(sigBytes)) {
			return SEC_TRUE;
		} else {
			return SEC_FALSE;
		}
	}

	/**
	 * Signature the certificate by parent_privatekey
	 * 
	 * @param cert
	 *            Certificate which content the data to be sign.
	 * @param parentPrivKey
	 *            parent private key.
	 * @throws NoSuchProviderException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 */
	public static byte[] signCertificate(CertificateSCP11 cert, PrivateKey parentPrivKey)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
		byte[] message = cert.getSignatureMessage();
		Signature signature = Signature.getInstance("ECDSA", "BC");
		// generate a signature
		signature.initSign(parentPrivKey);

		signature.update(message);

		byte[] sigBytes = signature.sign();

		return sigBytes;
	}

	/**
	 * X9.63 key derivation function, Derive AES Session key from secret and
	 * sharedInfo.
	 * 
	 * @param zab
	 *            secret
	 * @param sharedInfo
	 *            sharedInfo
	 * @param keydatalen
	 *            the byte length of the keying data to generate.e.g. 16*5
	 * @return 5 keys
	 */
	public static byte[] deriveFunction(byte[] za, byte[] zb, byte[] sharedInfo, int keyDataLen) {
		int len = za.length + zb.length;
		byte[] secret = new byte[len];
		byte[] keys = new byte[keyDataLen];

		short offset = 0;
		System.arraycopy(za, 0, secret, offset, za.length);
		offset += za.length;

		System.arraycopy(zb, 0, secret, offset, zb.length);
		offset += zb.length;

		SHA256Digest hash = new SHA256Digest();
		KDF2BytesGenerator kdf = new KDF2BytesGenerator(hash);
		kdf.init(new KDFParameters(secret, sharedInfo));
		kdf.generateBytes(keys, (short) 0, keyDataLen);

		return keys;
	}

/**
 * genCMAC
 * 
 * @param key
 * @param msg
 * @return
 */
public static byte[] genCMAC(byte[] key, byte[] msg) {
	BlockCipher cipher = new AESEngine();
	CMac cmac = new CMac(cipher);
	cmac.init(new KeyParameter(key));
	cmac.update(msg, 0, msg.length);
	byte[] out = new byte[cmac.getMacSize()];
	cmac.doFinal(out, 0);
	return out;
}

	/**
	 * verifyReceipt in order to verify the device.
	 * @param str_pk_sd_ecka the public key of device.
	 * @param str_sk_oce_ecka the private key of server.
	 * @param str_epk_oce_ecka the ephemeral public key of server.
	 * @param str_esk_oce_ecka the ephemeral private key of server
	 * @param card_group_id the value of tag 5F20 in certificate.
	 * @param crtA6 an object of CERTA6.
	 * @param keyLength the session key length.
	 * @param curve value of EC_Curve.
	 * @param comparedReceipt the receipt which to be compared.
	 * @return
	 */
	public static boolean verifyReceipt(String str_pk_sd_ecka, String str_sk_oce_ecka, String str_epk_oce_ecka,
			String str_esk_oce_ecka, String card_group_id, CRTA6 crtA6, short keyLength, EC_Curve curve,
			String comparedReceipt) {
		byte[] mac = null;
		byte[] comparedMAC = Hex.decode(comparedReceipt);
		try {
			byte[] sharedInfoFromA6 = crtA6.getSharedInfo();
			byte[] shared_info = concateSharedInfo(sharedInfoFromA6, null, null, Hex.decode(card_group_id));

			ECPublicKey pk_sd_ecka = curve.buildPubKeyFromValue(Hex.decode(str_pk_sd_ecka));
			ECPrivateKey sk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_sk_oce_ecka));
			ECPrivateKey esk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_esk_oce_ecka));

			byte[] sessionKey = deriveSessionKey(pk_sd_ecka, sk_oce_ecka, esk_oce_ecka, shared_info, (short) (keyLength*5));

			byte[] receiptKey = Arrays.copyOfRange(sessionKey, 0, keyLength);

			mac = generateReceipt(receiptKey, crtA6, str_epk_oce_ecka, str_pk_sd_ecka);
			mac = Arrays.copyOfRange(mac, 0, comparedMAC.length);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return Arrays.areEqual(mac, comparedMAC);
	}

	/**
	 * deriveSessionKey
	 * 
	 * @param pk_sd_ecka
	 * @param sk_oce_ecka
	 * @param esk_oce_ecka
	 * @param shared_info
	 * @param keyLength
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws InvalidKeyException
	 */
	public static byte[] deriveSessionKey(ECPublicKey pk_sd_ecka, ECPrivateKey sk_oce_ecka, ECPrivateKey esk_oce_ecka,
			byte[] shared_info, int keyLength)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {

		KeyAgreement aKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
		MessageDigest hash = MessageDigest.getInstance("SHA1", "BC");

		aKeyAgree.init(esk_oce_ecka);
		aKeyAgree.doPhase(pk_sd_ecka, true);
		

		// generate the key bytes
		byte[] retx = aKeyAgree.generateSecret();
		byte[] shses = hash.digest(retx);
		
		aKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
		hash = MessageDigest.getInstance("SHA1", "BC");
		aKeyAgree.init(sk_oce_ecka);
		aKeyAgree.doPhase(pk_sd_ecka, true);

		hash.reset();
		byte[] ret = aKeyAgree.generateSecret();
		byte[] shsss = hash.digest(ret);

		return deriveFunction(shses, shsss, shared_info, keyLength);
	}

	/**
	 * concate the SharedInfo for Key Derivation
	 * 
	 * @return
	 */
	public static byte[] concateSharedInfo(byte[] sharedInfo, byte[] sin, byte[] sdin, byte[] card_group_id) {
		// byte[] sharedInfo = crtA6.getSharedInfo;
		byte[] sharedInfox = null;
		short len = (short) sharedInfo.length;
		short offset = 0;
		if (card_group_id != null) {
			len += card_group_id.length;
			sharedInfox = new byte[len];
			System.arraycopy(sharedInfo, 0, sharedInfox, offset, sharedInfo.length);
			offset += sharedInfo.length;

			System.arraycopy(card_group_id, 0, sharedInfox, offset, card_group_id.length);
		} else {
			len += sin.length;
			len += sdin.length;
			sharedInfox = new byte[len];
			System.arraycopy(sharedInfo, 0, sharedInfox, offset, sharedInfo.length);
			offset += sharedInfo.length;

			System.arraycopy(sin, 0, sharedInfox, offset, sin.length);
			offset += sin.length;

			System.arraycopy(sdin, 0, sharedInfox, offset, sdin.length);
		}
		return sharedInfox;
	}

	/**
	 * generateReceipt
	 * 
	 * @param receiptKey
	 * @param crtA6
	 * @param epk_oce_ecka
	 * @param pk_sd_ecka
	 * @return
	 */
	public static byte[] generateReceipt(byte[] receiptKey, CRTA6 crtA6, String epk_oce_ecka, String pk_sd_ecka) {
		byte[] crtA6Data = crtA6.toBytes();
		
		TLVUtil _5F49epk_oce = new TLVUtil(new byte[]{(byte) 0x5F, 0x49}, Hex.decode(epk_oce_ecka));		
		TLVUtil _5F49pk_sd = new TLVUtil(new byte[]{(byte) 0x5F, 0x49}, Hex.decode(pk_sd_ecka));
		
		byte[] receiptInput = new byte[crtA6Data.length + _5F49epk_oce.size() + _5F49pk_sd.size()];
		short offset = 0;
		System.arraycopy(crtA6Data, 0, receiptInput, offset, crtA6Data.length);
		offset += crtA6Data.length;

		offset = _5F49epk_oce.toBytes(receiptInput, offset);
		
		offset = _5F49pk_sd.toBytes(receiptInput, offset);

		return genCMAC(receiptKey, receiptInput);
	}

	/**
	 * Convert CAP files to download scripts.
	 * @param scriptCmd apdu command list.
	 * @param str_pk_sd_ecka the public key of device. 
	 * @param str_sk_oce_ecka the private key of server.
	 * @param str_epk_oce_ecka the ephemeral public key of server.
	 * @param str_esk_oce_ecka the ephemeral private key of server.
	 * @param card_group_id the value of tag 5F20 in certificate.
	 * @param crtA6 an object of CRTA6.
	 * @param keyLength the length of session key length, default is 16 bytes.
	 * @param curve value of EC_Curve, default value is EC_Curve.SECP_256k1
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws InvalidKeySpecException
	 */
	public static List<String> generateScript(List<CommandAPDU> scriptCmd, String str_pk_sd_ecka, String str_sk_oce_ecka,
			String str_epk_oce_ecka, String str_esk_oce_ecka, String card_group_id, CRTA6 crtA6, short keyLength, EC_Curve curve)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
		byte[] sharedInfoFromA6 = crtA6.getSharedInfo();
		byte[] shared_info = concateSharedInfo(sharedInfoFromA6, null, null, Hex.decode(card_group_id));

		short kLen = keyLength;
		ECPublicKey pk_sd_ecka = curve.buildPubKeyFromValue(Hex.decode(str_pk_sd_ecka));
		ECPrivateKey sk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_sk_oce_ecka));
		ECPrivateKey esk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_esk_oce_ecka));

		byte[] sessionKey = deriveSessionKey(pk_sd_ecka, sk_oce_ecka, esk_oce_ecka, shared_info, (short) (keyLength*5));

		byte[] receiptKey = Arrays.copyOfRange(sessionKey, 0, kLen);

		byte[] mac_chain = generateReceipt(receiptKey, crtA6, str_epk_oce_ecka, str_pk_sd_ecka);

		SCP11StaticInfo staticInfo = new SCP11StaticInfo();

		// staticInfo
		SCP11 scp11 = new SCP11(staticInfo, null);
		scp11.setSkey_receipt(receiptKey);
		scp11.setSkey_enc(Arrays.copyOfRange(sessionKey, 1 * keyLength, 2*keyLength));
		scp11.setSkey_mac(Arrays.copyOfRange(sessionKey, 2 * keyLength, 3*keyLength));
		scp11.setSkey_rmac(Arrays.copyOfRange(sessionKey, 3 * keyLength, 4*keyLength));
		scp11.setSkey_dek(Arrays.copyOfRange(sessionKey, 4 * keyLength, 5*keyLength));
		scp11.setMac_chaining(mac_chain);
		
		byte[] kUsage = crtA6.get_keyUsage();
		short keyUsage = kUsage.length == 1?kUsage[0]:TLVUtil.getShort(kUsage, (short) 0);
		byte sLevel = SecureChannel.getSecureLevelFromKeyUsage(keyUsage);
		scp11.setsLevel(sLevel);
		scp11.setHasAuthed(true);

		List<String> wrapCmdLst = new ArrayList<String>();
		for (CommandAPDU cmd : scriptCmd) {
			String apdu = Hex.toHexString(scp11.wrap(cmd).getBytes()).toUpperCase();
			wrapCmdLst.add(apdu);
		}
		return wrapCmdLst;
	}
	/**
	 * publicKey2String
	 * @param pubKey
	 * @return
	 */
	public static String publicKey2String(ECPublicKey pubKey)
	{
		byte[] pubx = pubKey.getW().getAffineX().toByteArray();
		byte[] puby = pubKey.getW().getAffineY().toByteArray();
		if(pubx[0] == 0)
		{
			pubx = Arrays.copyOfRange(pubx, 1, pubx.length);
		}
		if(puby[0] == 0)
		{
			puby = Arrays.copyOfRange(puby, 1, puby.length);
		}
		 String wx = Hex.toHexString(pubx);//maybe leading with 0x00
		 String wy = Hex.toHexString(puby);//maybe leading with 0x00
		 String epk_oce_ecka_value = "04" + wx + wy;
		 return epk_oce_ecka_value;
	}
	/**
	 * privateKey2String
	 * @param privKey
	 * @return
	 */
	public static String privateKey2String(ECPrivateKey privKey)
	{
		byte[] priv = privKey.getS().toByteArray();
		if(priv[0] == 0)
		{
			priv = Arrays.copyOfRange(priv, 1, priv.length);
		}
		return Hex.toHexString(priv);
	}
}

Last edited by scplatform on Thu Sep 20, 2018 7:26 am, edited 7 times in total.

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 8:18 am

Ⅷ.SecureChannel

scplatform
Posts: 39
Joined: Wed Aug 31, 2016 9:55 pm
Points :372
Contact:

Re: Step-by-step implementation of SCP11

Post by scplatform » Tue Sep 18, 2018 8:19 am

Ⅸ.Script
0x01.Script generation
Just SCP11c supporte script mode, according the Figure 4-4 of AmdF v1.2, we can see the flow:
0x02.Prepare a lot of static info
In fact, before generating the script, we need to simulate the establishment of a secure channel, generate corresponding session keys, and then use these session keys to encrypt or complete the APDU that needs to be sent.Need to send the APDU, such as Cap download, install, delete, and so on.
0x03.Script generate
This function is in SCP11Lib file.

Code: Select all

/**
 * Convert CAP files to download scripts.
 * @param scriptCmd apdu command list.
 * @param str_pk_sd_ecka the public key of device. 
 * @param str_sk_oce_ecka the private key of server.
 * @param str_epk_oce_ecka the ephemeral public key of server.
 * @param str_esk_oce_ecka the ephemeral private key of server.
 * @param card_group_id the value of tag 5F20 in certificate.
 * @param crtA6 an object of CRTA6.
 * @param keyLength the length of session key length, default is 16 bytes.
 * @param curve value of EC_Curve, default value is EC_Curve.SECP_256k1
 * @return
 * @throws InvalidKeyException
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 * @throws InvalidKeySpecException
 */
public static List<String> generateScript(List<CommandAPDU> scriptCmd, String str_pk_sd_ecka, String str_sk_oce_ecka,
		String str_epk_oce_ecka, String str_esk_oce_ecka, String card_group_id, CRTA6 crtA6, short keyLength, EC_Curve curve)
		throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
	byte[] sharedInfoFromA6 = crtA6.getSharedInfo();
	byte[] shared_info = concateSharedInfo(sharedInfoFromA6, null, null, Hex.decode(card_group_id));

	short kLen = keyLength;
	ECPublicKey pk_sd_ecka = curve.buildPubKeyFromValue(Hex.decode(str_pk_sd_ecka));
	ECPrivateKey sk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_sk_oce_ecka));
	ECPrivateKey esk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_esk_oce_ecka));

	byte[] sessionKey = deriveSessionKey(pk_sd_ecka, sk_oce_ecka, esk_oce_ecka, shared_info, (short) (keyLength*5));

	byte[] receiptKey = Arrays.copyOfRange(sessionKey, 0, kLen);

	byte[] mac_chain = generateReceipt(receiptKey, crtA6, str_epk_oce_ecka, str_pk_sd_ecka);

	SCP11StaticInfo staticInfo = new SCP11StaticInfo();

	// staticInfo
	SCP11 scp11 = new SCP11(staticInfo, null);
	scp11.setSkey_receipt(receiptKey);
	scp11.setSkey_enc(Arrays.copyOfRange(sessionKey, 1 * keyLength, 2*keyLength));
	scp11.setSkey_mac(Arrays.copyOfRange(sessionKey, 2 * keyLength, 3*keyLength));
	scp11.setSkey_rmac(Arrays.copyOfRange(sessionKey, 3 * keyLength, 4*keyLength));
	scp11.setSkey_dek(Arrays.copyOfRange(sessionKey, 4 * keyLength, 5*keyLength));
	scp11.setMac_chaining(mac_chain);
	
	byte[] kUsage = crtA6.get_keyUsage();
	short keyUsage = kUsage.length == 1?kUsage[0]:TLVUtil.getShort(kUsage, (short) 0);
	byte sLevel = SecureChannel.getSecureLevelFromKeyUsage(keyUsage);
	scp11.setsLevel(sLevel);
	scp11.setHasAuthed(true);

	List<String> wrapCmdLst = new ArrayList<String>();
	for (CommandAPDU cmd : scriptCmd) {
		String apdu = Hex.toHexString(scp11.wrap(cmd).getBytes()).toUpperCase();
		wrapCmdLst.add(apdu);
	}
	return wrapCmdLst;
}
You do not have the required permissions to view the files attached to this post. Please login first.
Last edited by scplatform on Thu Sep 20, 2018 7:09 am, edited 4 times in total.

Post Reply Previous topicNext topic

Who is online

Users browsing this forum: No registered users and 60 guests

JavaCard OS : Disclaimer