summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/uic/barcode/staticFrame/StaticFrame.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/uic/barcode/staticFrame/StaticFrame.java')
-rw-r--r--src/main/java/org/uic/barcode/staticFrame/StaticFrame.java764
1 files changed, 764 insertions, 0 deletions
diff --git a/src/main/java/org/uic/barcode/staticFrame/StaticFrame.java b/src/main/java/org/uic/barcode/staticFrame/StaticFrame.java
new file mode 100644
index 0000000..5246ac7
--- /dev/null
+++ b/src/main/java/org/uic/barcode/staticFrame/StaticFrame.java
@@ -0,0 +1,764 @@
+package org.uic.barcode.staticFrame;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.Provider.Service;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.uic.barcode.ticket.EncodingFormatException;
+
+
+/**
+ * The Class StaticHeader implements the static bar code header frame defined in UIC IRS 90918-9.
+ * It allows to decode and encode the bar code content and to add sub-records as defined in the IRS 90918-9 for:
+ * - additional header data
+ * - Ticket Layout content
+ * - Flexible content
+ * - bilateral data records
+ */
+public class StaticFrame {
+
+ /** The additional header record. */
+ private UHEADDataRecord headerRecord;
+
+ /** The bar code version. */
+ private int version;
+
+ /** The u_flex. */
+ private UFLEXDataRecord uFlex;
+
+ /** The u_tlay. */
+ private UTLAYDataRecord uTlay;
+
+ /** The security provider. */
+ private String securityProvider;
+
+ /** The signature key. */
+ private String signatureKey;
+
+ /** The signature. */
+ private byte[] signature;
+
+ /** The data records. */
+ private ArrayList<DataRecord> dataRecords = new ArrayList<DataRecord>();
+
+
+ private byte[] signedData = null;
+
+ /**
+ * Instantiates a new static header frame.
+ */
+ public StaticFrame (){ }
+
+
+
+ /**
+ * Instantiates a new static header and decodes the provided data.
+ *
+ * @param bytes the bar code data
+ * @throws EncodingFormatException the encoding format exception
+ * @throws DataFormatException the data format exception
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public StaticFrame (byte[] bytes) throws EncodingFormatException, DataFormatException, IOException{
+ decode(bytes);
+ }
+
+
+ /**
+ * Encode the barcode data.
+ *
+ * @param version the barcode version
+ * @return byte[] the encoded data as
+ * @throws IOException Signals that an I/O exception has occurred.
+ * @throws Exception the exception
+ */
+ /*
+ * creates a UIC bar code of version 1
+ *
+ * limits:
+ * - version 1 allows for signatures up to 50 byte length
+ * - max data length 2048 Byte
+ * input:
+ * data to be included
+ * provider of the signature
+ * processing:
+ * 1. create header informations
+ * 2. compression of the data content
+ * 3. adding a signature
+ * output:
+ * raw data to be included in an aztec bar code
+ *
+ */
+ public byte[] encode() throws IOException, Exception {
+
+ if (headerRecord == null && uFlex == null && uTlay == null
+ && (dataRecords == null || dataRecords.isEmpty())) return null;
+
+ if (signedData == null) {
+ signedData = encodeData();
+ }
+
+ if (version != 1 && version != 2) {
+ throw (new Exception(String.format("UIC Barcode Version %d not supported", version)));
+ }
+
+ if (signedData.length < 1) {
+ throw new IOException("data missing!");
+ }
+ if (signedData.length > 2048) {
+ throw new IOException("too many data!"); //2048 should be enough
+ }
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ //UIC bar code version 1
+ String header = "#UT01";
+ if (version == 2) {
+ header = "#UT02";
+ }
+ outputStream.write(header.getBytes());
+
+ outputStream.write(securityProvider.getBytes());
+
+
+ while (signatureKey.length() < 5) {
+ signatureKey = "0" + signatureKey;
+ }
+ outputStream.write(signatureKey.getBytes());
+
+ if (signature.length < 1) {
+ // signature too small for bar code version 1
+ throw new IOException("signature size too small!");
+ }
+
+ if (version == 1) {
+ if (signature.length > 50) {
+ // signature too large for bar code version 1
+ throw new IOException("signature size too large!");
+ }
+ outputStream.write(Arrays.copyOfRange(signature, 0, 50));
+ } else if (version == 2) {
+ BigInteger[] bInts = null;
+ byte zeroByte = 0;
+
+ bInts = decodeSignatureIntegerSequence(signature);
+ byte[] r = toUnsignedBytes(bInts[0]);
+
+ byte[] s = toUnsignedBytes(bInts[1]);
+
+ if (r.length > 32 || s.length > 32) {
+ throw (new EncodingFormatException(String.format("DSA signature too big")));
+ }
+ for (int i = 0; i < 32 - r.length; i++) {
+ outputStream.write(zeroByte);
+ }
+ outputStream.write(r);
+ for (int i = 0; i < 32 - s.length; i++) {
+ outputStream.write(zeroByte);
+ }
+ outputStream.write(s);
+ //outputStream.write(Arrays.copyOfRange(signature, 0, 64));
+ }
+
+ String length = String.format("%04d", signedData.length);
+ outputStream.write(length.getBytes());
+
+ outputStream.write(signedData);
+
+ outputStream.close();
+
+ return outputStream.toByteArray();
+ }
+
+
+ /**
+ * Adds a proprietary data record.
+ *
+ * @param record the record
+ */
+ public void addDataRecord(DataRecord record) {
+ dataRecords.add(record);
+ }
+
+ /**
+ * Gets the version of the header frame.
+ *
+ * @return the version
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the version of the header frame.
+ * supported values are 1 and 2
+ *
+ * @param version the new version
+ */
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ /**
+ * Gets the security provider.
+ *
+ * @return the security provider
+ */
+ public String getSecurityProvider() {
+ return securityProvider;
+ }
+
+ /**
+ * Sets the security provider.
+ *
+ * @param securityProvider the new security provider
+ */
+ public void setSecurityProvider(String securityProvider) {
+ this.securityProvider = securityProvider;
+ }
+
+ /**
+ * Gets the signature key identifier.
+ *
+ * @return the signature key
+ */
+ public String getSignatureKey() {
+ return signatureKey;
+ }
+
+ /**
+ * Sets the signature key identifier.
+ *
+ * @param signatureKey the new signature key
+ */
+ public void setSignatureKey(String signatureKey) {
+ this.signatureKey = signatureKey;
+ }
+
+ /**
+ * Gets the signature.
+ *
+ * @return the signature
+ */
+ public byte[] getSignature() {
+ return signature;
+ }
+
+ /**
+ * Sets the signature.
+ *
+ * @param signature the new signature
+ */
+ public void setSignature(byte[] signature) {
+ this.signature = signature;
+ }
+
+ /**
+ * Gets the additional header record.
+ *
+ * @return the header record
+ */
+ public UHEADDataRecord getHeaderRecord() {
+ return headerRecord;
+ }
+
+ /**
+ * Gets the list of bilateral data records.
+ *
+ * @return the data records
+ */
+ public ArrayList<DataRecord> getDataRecords() {
+ return dataRecords;
+ }
+
+ /**
+ * Gets the data for signing.
+ *
+ * @return the data to be signed
+ * @throws IOException Signals that an I/O exception has occurred.
+ * @throws EncodingFormatException the encoding format exception
+ */
+ public byte[] getDataForSignature() throws IOException, EncodingFormatException {
+ // data compression
+ if (signedData != null) return signedData;
+
+ Deflater deflater = new Deflater();
+ byte[] data = encodeData();
+ deflater.setInput(data);
+ ByteArrayOutputStream compressStream = new ByteArrayOutputStream(data.length);
+ byte[] buffer = new byte[2048];
+ deflater.finish();
+ while (!deflater.finished()) {
+ int count = deflater.deflate(buffer); // returns the number of result bytes
+ compressStream.write(buffer, 0, count);
+ }
+ compressStream.close();
+
+ return compressStream.toByteArray();
+ }
+
+ /**
+ * Get the encoded data for the bar code.
+ *
+ * @return the byte[]
+ * @throws IOException Signals that an I/O exception has occurred.
+ * @throws EncodingFormatException the encoding format exception
+ */
+ private byte[] encodeData() throws IOException, EncodingFormatException {
+
+ if (this.uFlex == null && this.uTlay == null && this.headerRecord == null &&
+ (dataRecords == null || dataRecords.isEmpty())) return null;
+
+ ByteArrayOutputStream totalStream = new ByteArrayOutputStream();
+
+ //encode header for layout
+ if (headerRecord != null) {
+ byte[] header = headerRecord.encode();
+
+ if (header != null && header.length > 0) {
+ totalStream.write(header);
+ }
+ }
+
+ //encode layout
+ if (uTlay != null) {
+ byte[] layout = uTlay.encode();
+ if (layout != null && layout.length > 0) {
+ totalStream.write(layout);
+ }
+ }
+
+ if (uFlex != null) {
+ byte[] content = uFlex.encode();
+ if (content != null && content.length > 0){
+ totalStream.write(content);
+ }
+ }
+
+ //third party content
+ for (DataRecord dataRecord : dataRecords){
+
+ byte[] content = dataRecord.encode();
+ if (content != null && content.length > 0){
+ totalStream.write(content);
+ }
+ }
+ return totalStream.toByteArray();
+ }
+
+ /**
+ * Encode signature integer sequence.
+ *
+ * Support function to format two parameters as DER encoded integer list
+ * to get a valid formated DSA signature from the signature parameter
+ *
+ * @param i1 the i 1
+ * @param i2 the i 2
+ * @return the byte[]
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public static byte[] encodeSignatureIntegerSequence(BigInteger i1, BigInteger i2) throws IOException {
+
+ //SEQUENCE OF --> tag 16
+ int sequenceTag = 16 + 32; // (bits 6 = 1 constructed)
+ //INTEGER --> tag 2
+ int integerTag = 2;
+
+ byte[] b1 = i1.toByteArray();
+ int lb1 = b1.length;
+ byte[] b2 = i2.toByteArray();
+ int lb2 = b2.length;
+
+ int sequenceLength = lb1 + lb2 + 4;
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write((byte) sequenceTag);
+ out.write((byte) sequenceLength);
+ out.write((byte) integerTag);
+ out.write((byte) lb1);
+ out.write(b1);
+ out.write((byte) integerTag);
+ out.write((byte) lb2);
+ out.write(b2);
+
+ return out.toByteArray();
+ }
+
+ /**
+ * Decode signature integer sequence.
+ *
+ * Support function to decode a DSA signature
+ * Provides the two DSA signature parameter encoded in a DSA signature
+ *
+ * @param bytes the bytes
+ * @return the big integer[]
+ * @throws Exception the exception
+ */
+ public static BigInteger[] decodeSignatureIntegerSequence(byte[] bytes) throws Exception {
+
+ int sequenceTag = (int) bytes[0];
+
+ if (sequenceTag != 48) throw new Exception("signature is not a sequence");
+
+ int sequenceLength = (int) bytes[1];
+
+ if (sequenceLength < 6) throw new Exception("signature sequence too short");
+
+ BigInteger[] result = new BigInteger[2];
+
+ int offset = 2;
+ int i = 0;
+ while (offset < bytes.length && i < 2) {
+ int integerTag = (int) bytes[offset];
+ if (integerTag != 2) throw new Exception("signature is not an integer sequence");
+ int integerLength = (int) bytes[offset + 1];
+ byte[] value = Arrays.copyOfRange(bytes, offset + 2, offset + 2 + integerLength);
+ result[i] = new BigInteger(+1, value);
+ offset = offset + integerLength + 2;
+ i++;
+ }
+
+ return result;
+ }
+
+ /**
+ * Decode.
+ *
+ * @param inputData the input data
+ * @throws EncodingFormatException the encoding format exception
+ * @throws DataFormatException the data format exception
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public void decode(byte[] inputData) throws EncodingFormatException, DataFormatException, IOException {
+
+
+ int offset = 0;
+ String headerTag = new String( Arrays.copyOfRange(inputData,offset,offset + 3));
+ offset = offset + 3;
+ if (!headerTag.equals("#UT")) {
+ throw (new EncodingFormatException("not a UIC barcode"));
+ }
+
+
+ String versionValue = new String(Arrays.copyOfRange(inputData,offset,offset + 2));
+ offset = offset + 2;
+ int barcodeVersion = 0;
+ try {
+ barcodeVersion = Integer.parseInt(versionValue);
+ this.setVersion(barcodeVersion);
+ } catch (NumberFormatException e2) {
+ throw (new EncodingFormatException(String.format("UIC Barcode Version %s not supported", versionValue)));
+ }
+
+ String providerValue = new String( Arrays.copyOfRange(inputData,offset,offset + 4));
+ this.setSecurityProvider(providerValue);
+ offset = offset + 4;
+
+ String signatureKeyIdValue = new String( Arrays.copyOfRange(inputData,offset,offset + 5));
+ this.setSignatureKey(signatureKeyIdValue);
+ offset = offset + 5;
+
+ byte[] sealdata = null;
+
+ if (barcodeVersion == 1) {
+ sealdata = Arrays.copyOfRange(inputData, offset, offset + 50);
+ signature = trimDsaSignature(sealdata);
+ offset = offset + 50;
+ } else if (barcodeVersion == 2) {
+ sealdata = Arrays.copyOfRange(inputData, offset, offset + 64);
+ signature = recombineDsaSignature(sealdata);
+ offset = offset + 64;
+ } else {
+ throw (new EncodingFormatException(String.format("UIC Barcode Version %s not supported", versionValue)));
+ }
+
+
+ String lengthValue = new String( Arrays.copyOfRange(inputData,offset,offset + 4));
+ offset = offset + 4;
+
+ int dataLength = 0;
+ dataLength = Integer.parseInt(lengthValue);
+
+ signedData = Arrays.copyOfRange(inputData, offset, offset + dataLength);
+
+ ByteBuffer containedDataBuffer = ByteBuffer.allocate(dataLength);
+ containedDataBuffer.put(signedData);
+
+ byte[] inflatedDataBuffer = new byte[2000];
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ Inflater inflater = new Inflater();
+ byte[] inflaterInput = containedDataBuffer.array();
+ inflater.setInput(inflaterInput);
+ while (!inflater.finished()) {
+ int count = inflater.inflate(inflatedDataBuffer,0,2000);
+ if (inflater.needsDictionary()) {
+ break;
+ }
+ outputStream.write(inflatedDataBuffer, 0, count);
+ }
+
+ outputStream.close();
+
+ byte[] byteData = outputStream.toByteArray();
+
+ offset = 0;
+ int remainingBytes = byteData.length;
+
+ while (remainingBytes > 0) {
+
+ String tag = new String(Arrays.copyOfRange(byteData, offset, offset + 6));
+ int length = 0;
+
+ if (tag.startsWith("U_TLAY")) {
+ UTLAYDataRecord record = new UTLAYDataRecord();
+ length = record.decode(Arrays.copyOfRange(byteData, offset, byteData.length));
+ this.uTlay = record;
+ } else if (tag.startsWith("U_FLEX")) {
+ UFLEXDataRecord record = new UFLEXDataRecord();
+ length = record.decode(Arrays.copyOfRange(byteData, offset, byteData.length));
+ this.uFlex = record;
+ } else if (tag.startsWith("U_HEAD")) {
+ UHEADDataRecord record = new UHEADDataRecord();
+ length = record.decode(Arrays.copyOfRange(byteData, offset, byteData.length));
+ this.headerRecord = record;
+ } else {
+ DataRecord record = new GENERICDataRecord(tag);
+ length = record.decode(Arrays.copyOfRange(byteData, offset, byteData.length));
+ addDataRecord(record);
+ }
+ offset = offset + length;
+ remainingBytes = remainingBytes - length;
+ }
+ }
+
+
+ private byte[] recombineDsaSignature(byte[] sealdata) throws IOException {
+
+ //check whether the encoding is wrong and the sealdata contain a signature
+ //remove trailing zeroes from the signature
+ BigInteger[] bInts = null;
+ try {
+ bInts = decodeSignatureIntegerSequence(sealdata);
+ byte[] sig = encodeSignatureIntegerSequence(bInts[0],bInts[1]);
+ //decoding the entire signature was ok, so there was no split
+ return sig;
+ } catch (Exception e) {
+ //the signature is correctly implemented, continue with recombination
+ }
+
+ // split the data into two blocks
+ int length = sealdata.length / 2;
+ byte[] rBytes = Arrays.copyOfRange(sealdata, 0, length);
+ byte[] sBytes = Arrays.copyOfRange(sealdata, length, length + length);
+
+ //convert to BigInteger to get rid of leading zeroes
+ BigInteger r = new BigInteger(1,rBytes);
+ BigInteger s = new BigInteger(1,sBytes);
+
+ //encode as DSA signature structure
+ //SEQUENCE OF --> tag 16
+ int sequenceTag = 16 + 32; // (bits 6 = 1 constructed)
+ //INTEGER --> tag 2
+ int integerTag = 2;
+
+ byte[] b1 = r.toByteArray();
+ int lb1 = b1.length;
+
+ byte[] b2 = s.toByteArray();
+ int lb2 = b2.length;
+ int sequenceLength = lb1 + lb2 + 4;
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write((byte) sequenceTag);
+ out.write((byte) sequenceLength);
+ out.write((byte) integerTag);
+ out.write((byte) lb1);
+ out.write(b1);
+ out.write((byte) integerTag);
+ out.write((byte) lb2);
+ out.write(b2);
+ return out.toByteArray();
+
+
+ }
+
+ private static byte[] toUnsignedBytes(BigInteger i) {
+ byte[] b = i.abs().toByteArray();
+ //remove top sign bit
+ if (b[0] == 0) {
+ b = Arrays.copyOfRange(b, 1, b.length);
+ }
+ return b;
+ }
+
+
+ private byte[] trimDsaSignature(byte[] sealdata) throws EncodingFormatException {
+ //remove trailing zeroes from the signature
+ BigInteger[] bInts = null;
+ try {
+ bInts = decodeSignatureIntegerSequence(sealdata);
+ return encodeSignatureIntegerSequence(bInts[0],bInts[1]);
+ } catch (Exception e) {
+ throw (new EncodingFormatException(String.format("Invalid DSA signature")));
+ }
+ }
+
+
+
+ /**
+ * Verify the signature
+ *
+ * Note: an appropriate security provider (e.g. BC) must be registered before
+ *
+ * @param key the key
+ * @param algo the algorithm name
+ * @return true, if successful
+ * @throws InvalidKeyException the invalid key exception
+ * @throws NoSuchAlgorithmException the no such algorithm exception
+ * @throws SignatureException the signature exception
+ * @throws IllegalArgumentException the illegal argument exception
+ * @throws UnsupportedOperationException the unsupported operation exception
+ * @throws EncodingFormatException
+ * @throws IOException
+ */
+ public boolean ByAlgorithmName(PublicKey key, String algo) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IllegalArgumentException, UnsupportedOperationException, IOException, EncodingFormatException {
+ Signature sig = Signature.getInstance(algo);
+ sig.initVerify(key);
+ sig.update(this.getDataForSignature());
+ return sig.verify(this.getSignature());
+ }
+
+ /**
+ * Verify the signature
+ *
+ * Note: an appropriate security provider (e.g. BC) must be registered before
+ *
+ * @param key the key
+ * @param singningAlg the Object ID of the signing algorithm
+ * @return true, if successful
+ * @throws InvalidKeyException the invalid key exception
+ * @throws NoSuchAlgorithmException the no such algorithm exception
+ * @throws SignatureException the signature exception
+ * @throws IllegalArgumentException the illegal argument exception
+ * @throws UnsupportedOperationException the unsupported operatign exception
+ * @throws EncodingFormatException
+ * @throws IOException
+ */
+ public boolean verifyByAlgorithmOid(PublicKey key, String signingAlg) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IllegalArgumentException, UnsupportedOperationException, IOException, EncodingFormatException {
+ //find the algorithm name for the signature OID
+ String algo = null;
+ Provider[] provs = Security.getProviders();
+ for (Provider prov : provs) {
+ Service service = prov.getService("Signature",signingAlg);
+ if (service != null) {
+ algo = service.getAlgorithm();
+ }
+ }
+ Signature sig = Signature.getInstance(algo);
+ sig.initVerify(key);
+ sig.update(getDataForSignature());
+ return sig.verify(this.getSignature());
+ }
+
+ /**
+ * Sign the contained data block.
+ *
+ * Note: an appropriate security provider (e.g. BC) must be registered before
+ *
+ * @param key the key
+ * @param singningAlg the Object ID of the signing algorithm
+ * @return
+ * @throws NoSuchAlgorithmException the no such algorithm exception
+ * @throws InvalidKeyException the invalid key exception
+ * @throws SignatureException the signature exception
+ * @throws EncodingFormatException
+ * @throws IOException
+ */
+ public void signByAlgorithmOID(PrivateKey key,String signingAlg) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException, EncodingFormatException {
+ //find the algorithm name for the signature OID
+ String algo = null;
+ Provider[] provs = Security.getProviders();
+ for (Provider prov : provs) {
+ Service service = prov.getService("Signature",signingAlg);
+ if (service != null) {
+ algo = service.getAlgorithm();
+ }
+ }
+ Signature sig = Signature.getInstance(algo);
+ sig.initSign(key);
+ signedData = getDataForSignature();
+ sig.update(signedData);
+ signature = sig.sign();
+ }
+
+ /**
+ * Sign the contained data block.
+ *
+ * Note: an appropriate security provider (e.g. BC) must be registered before
+ *
+ * @param key the key
+ * @param algo the name of the signing algorithm
+ * @return
+ * @throws NoSuchAlgorithmException the no such algorithm exception
+ * @throws InvalidKeyException the invalid key exception
+ * @throws SignatureException the signature exception
+ * @throws EncodingFormatException
+ * @throws IOException
+ */
+ public void signUsingAlgorithmName(PrivateKey key,String algo) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException, EncodingFormatException {
+ Signature sig = Signature.getInstance(algo);
+ sig.initSign(key);
+ sig.update(getDataForSignature());
+ signature = sig.sign();
+ }
+
+
+
+ public UFLEXDataRecord getuFlex() {
+ return uFlex;
+ }
+
+
+
+ public UTLAYDataRecord getuTlay() {
+ return uTlay;
+ }
+
+
+
+ public void setuFlex(UFLEXDataRecord uFlex) {
+ this.uFlex = uFlex;
+ }
+
+
+
+ public void setuTlay(UTLAYDataRecord uTlay) {
+ this.uTlay = uTlay;
+ }
+
+
+
+ public void setHeaderRecord(UHEADDataRecord headerRecord) {
+ this.headerRecord = headerRecord;
+ }
+
+
+
+
+}