From e023e674020f1a435f7b8c8b9276704f576ea6e5 Mon Sep 17 00:00:00 2001 From: CGantert345 <57003061+CGantert345@users.noreply.github.com> Date: Mon, 29 Mar 2021 14:08:45 +0200 Subject: structure change 1 --- .../org/uic/barcode/staticFrame/StaticFrame.java | 764 +++++++++++++++++++++ 1 file changed, 764 insertions(+) create mode 100644 src/main/java/org/uic/barcode/staticFrame/StaticFrame.java (limited to 'src/main/java/org/uic/barcode/staticFrame/StaticFrame.java') 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 dataRecords = new ArrayList(); + + + 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 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; + } + + + + +} -- cgit v1.2.3