Commit be005616 authored by Cypres TAC's avatar Cypres TAC
Browse files

Merge branch 'java-enhancements' into 'master'

better API to manipulate decoded LSP

See merge request !16
parents 565224f9 ecca6914
Pipeline #228975 passed with stages
in 3 minutes and 26 seconds
......@@ -9,4 +9,8 @@ public class CleaEncryptionException extends Exception {
public CleaEncryptionException(Throwable cause) {
super(cause);
}
public CleaEncryptionException(String message) {
super(message);
}
}
......@@ -3,13 +3,12 @@
*/
package fr.inria.clea.lsp;
import java.util.Arrays;
import java.util.UUID;
import javax.validation.constraints.Max;
import fr.devnied.bitlib.BitUtils;
import fr.inria.clea.lsp.LocationSpecificPart.LocationSpecificPartBuilder;
import org.bouncycastle.util.Arrays;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
......@@ -49,38 +48,16 @@ public class EncryptedLocationSpecificPart {
*/
private byte[] encryptedLocationMessage;
/**
* Unpack the data message (binary format) :
* | Staff | pad2 |CRIexp | vType |
* vCat1 | vCat2 | countryCode | | periodDuration | ct_periodStart | t_qrStart |
* LTKey | to extract parameters
*/
public LocationSpecificPart decodeMessage() {
byte[] messageBinary = Arrays.copyOfRange(encryptedLocationMessage, 0, CleaEciesEncoder.MSG_BYTES_SIZE);
BitUtils message = new BitUtils(messageBinary);
byte[] encryptedLocationContactMessage = Arrays.copyOfRange(encryptedLocationMessage,
CleaEciesEncoder.MSG_BYTES_SIZE, encryptedLocationMessage.length);
if (encryptedLocationContactMessage.length == 0) {
encryptedLocationContactMessage = null;
}
LocationSpecificPartBuilder locationSpecificPartbuilder = LocationSpecificPart.builder()
.version(version)
.type(type)
.locationTemporaryPublicId(locationTemporaryPublicId)
.staff(message.getNextInteger(1) == 1);
message.getNextInteger(1); // skip locationContactMessagePresent
locationSpecificPartbuilder
.countryCode(message.getNextInteger(12))
.qrCodeRenewalIntervalExponentCompact(message.getNextInteger(5))
.venueType(message.getNextInteger(5))
.venueCategory1(message.getNextInteger(4))
.venueCategory2(message.getNextInteger(4))
.periodDuration(message.getNextInteger(8))
.compressedPeriodStartTime(message.getNextInteger(24))
.qrCodeValidityStartTime(message.getNextInteger(32))
.locationTemporarySecretKey(message.getNextByte(256))
.encryptedLocationContactMessage(encryptedLocationContactMessage);
return locationSpecificPartbuilder.build();
public LocationSpecificPart decrypt(LocationSpecificPartDecoder decoder) throws CleaEncryptionException, CleaEncodingException {
return decoder.decrypt(this.binaryEncoded());
}
protected byte[] binaryEncoded() {
return Arrays.concatenate(this.binaryEncodedHeader(), encryptedLocationMessage);
}
public byte[] binaryEncodedHeader() {
return new LocationSpecificPartEncoder().binaryEncodedHeader(this.type, this.version, this.locationTemporaryPublicId);
}
}
......@@ -9,26 +9,29 @@ import java.util.UUID;
import javax.validation.constraints.Max;
import fr.inria.clea.lsp.utils.TimeUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
/**
* LocationSpecificPart (LSP) contents data respecting the CLEA protocol
*
* @see <a href="https://hal.inria.fr/hal-03146022">CLEA protocol</a>
*/
@Builder
@SuperBuilder(toBuilder = true)
@Getter
@EqualsAndHashCode
@AllArgsConstructor
@ToString
public class LocationSpecificPart {
/* Clea protocol version number */
@Builder.Default
@Max(value = 8)
private int version = 0;
protected int version = 0;
/*
* LSP type, in order to be able to use multiple formats in parallel in the
......@@ -36,7 +39,7 @@ public class LocationSpecificPart {
*/
@Builder.Default
@Max(value = 8)
private int type = 0;
protected int type = 0;
/*
* Country code, coded as the ISO 3166-1 country code, for instance 0x250 for
......@@ -44,55 +47,55 @@ public class LocationSpecificPart {
*/
@Builder.Default
@Max(value = 4096)
private int countryCode = 33;
protected int countryCode = 33;
/* regular users or staff member of the location */
private boolean staff;
protected boolean staff;
/*
* Location Temporary public universally unique Identifier (UUID), specific to a
* given location at a given period.
*/
@Setter
private UUID locationTemporaryPublicId;
protected UUID locationTemporaryPublicId;
/*
* qrCodeRenewalInterval value in a compact manner, as the exponent of a power
* of two.
*/
@Max(value = 32)
private int qrCodeRenewalIntervalExponentCompact;
protected int qrCodeRenewalIntervalExponentCompact;
/* Type of the location/venue */
@Max(value = 32)
private int venueType;
protected int venueType;
/* Reserved: a first level of venue category */
@Max(value = 16)
private int venueCategory1;
protected int venueCategory1;
/* Reserved: a second level of venue category */
@Max(value = 16)
private int venueCategory2;
protected int venueCategory2;
/* Duration, in terms of number of hours, of the period */
@Max(value = 255)
private int periodDuration;
protected int periodDuration;
/* Starting time of the period in a compressed manner (round hour) */
@Max(value = 16777216)
private int compressedPeriodStartTime;
protected int compressedPeriodStartTime;
/* Starting time of the QR code validity timespan in seconds */
@Setter
private int qrCodeValidityStartTime;
protected int qrCodeValidityStartTime;
/* Temporary location key for the period */
@Setter
private byte[] locationTemporarySecretKey;
protected byte[] locationTemporarySecretKey;
@Setter
private byte[] encryptedLocationContactMessage;
protected byte[] encryptedLocationContactMessage;
/**
* Indicates if the location contact message is present in the message
......
......@@ -8,11 +8,13 @@ import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Objects;
import org.bouncycastle.crypto.InvalidCipherTextException;
import fr.devnied.bitlib.BitUtils;
import fr.inria.clea.lsp.EncryptedLocationSpecificPart.EncryptedLocationSpecificPartBuilder;
import fr.inria.clea.lsp.LocationSpecificPart.LocationSpecificPartBuilder;
import lombok.extern.slf4j.Slf4j;
/**
......@@ -41,7 +43,7 @@ public class LocationSpecificPartDecoder {
}
/**
* Unpack the data decrypted header (binary format):
* Unpack the data header (binary format, already base64 decrypted):
* | version | LSPtype | pad | LTId | to extract parameters
*/
public EncryptedLocationSpecificPart decodeHeader(byte[] binaryLocationSpecificPart) throws CleaEncodingException {
......@@ -74,17 +76,83 @@ public class LocationSpecificPartDecoder {
* @throws CleaEncryptionException
* @throws CleaEncodingException
*/
public EncryptedLocationSpecificPart decrypt(String lspBase64) throws CleaEncryptionException, CleaEncodingException {
public LocationSpecificPart decrypt(String lspBase64) throws CleaEncryptionException, CleaEncodingException {
byte[] encryptedLocationSpecificPart = Base64.getDecoder().decode(lspBase64);
log.debug("Base 64 decoded LSP: {}", encryptedLocationSpecificPart);
return this.decrypt(encryptedLocationSpecificPart);
}
/**
* Decrypt and unpack a location Specific Part (LSP)
*
* @param encryptedLocationSpecificPart Location Specific Part base64-decoded
* @throws CleaEncryptionException
* @throws CleaEncodingException
*/
public LocationSpecificPart decrypt(byte[] encryptedLocationSpecificPart) throws CleaEncryptionException, CleaEncodingException {
if (Objects.isNull(serverAuthoritySecretKey)) {
throw new CleaEncryptionException("Cannot encrypt, serverAuthoritySecretKey is null!");
}
EncryptedLocationSpecificPart encryptedLsp = this.decodeHeader(encryptedLocationSpecificPart);
return this.decrypt(encryptedLsp);
}
/**
* Decrypt and unpack a location Specific Part (LSP)
*
* @param encryptedLocationSpecificPart Encrypted Location Specific Part (Header decoded)
* @throws CleaEncryptionException
* @throws CleaEncodingException
*/
public LocationSpecificPart decrypt(EncryptedLocationSpecificPart encryptedLocationSpecificPart) throws CleaEncryptionException, CleaEncodingException {
byte[] binaryLocationSpecificPart;
try {
binaryLocationSpecificPart = this.cleaEciesEncoder.decrypt(encryptedLocationSpecificPart, this.serverAuthoritySecretKey, true);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IllegalStateException | InvalidCipherTextException
| IOException e) {
binaryLocationSpecificPart = this.cleaEciesEncoder.decrypt(
encryptedLocationSpecificPart.binaryEncoded(), this.serverAuthoritySecretKey, true);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IllegalStateException
| InvalidCipherTextException | IOException e) {
throw new CleaEncryptionException(e);
}
return this.decodeHeader(binaryLocationSpecificPart);
//this.decodeMessage(binaryLocationSpecificPart);
return this.decodeMessage(binaryLocationSpecificPart, newLocationSpecificPartBuilder(encryptedLocationSpecificPart));
}
private LocationSpecificPartBuilder newLocationSpecificPartBuilder(EncryptedLocationSpecificPart encryptedLocationSpecificPart) {
return LocationSpecificPart.builder()
.version(encryptedLocationSpecificPart.getVersion())
.type(encryptedLocationSpecificPart.getType())
.locationTemporaryPublicId(encryptedLocationSpecificPart.getLocationTemporaryPublicId());
}
/**
* Unpack the data message (binary format) :
* | Staff | pad2 |CRIexp | vType |
* vCat1 | vCat2 | countryCode | | periodDuration | ct_periodStart | t_qrStart |
* LTKey | to extract parameters
*/
public LocationSpecificPart decodeMessage(byte[] binaryLocationSpecificPart, LocationSpecificPartBuilder locationSpecificPartbuilder) {
byte[] messageBinary = Arrays.copyOfRange(binaryLocationSpecificPart, CleaEciesEncoder.HEADER_BYTES_SIZE,
CleaEciesEncoder.HEADER_BYTES_SIZE + CleaEciesEncoder.MSG_BYTES_SIZE);
BitUtils message = new BitUtils(messageBinary);
byte[] encryptedLocationContactMessage = Arrays.copyOfRange(binaryLocationSpecificPart,
CleaEciesEncoder.HEADER_BYTES_SIZE + CleaEciesEncoder.MSG_BYTES_SIZE, binaryLocationSpecificPart.length);
if (encryptedLocationContactMessage.length == 0) {
encryptedLocationContactMessage = null;
}
locationSpecificPartbuilder
.staff(message.getNextInteger(1) == 1);
message.getNextInteger(1); // skip locationContactMessagePresent
locationSpecificPartbuilder
.countryCode(message.getNextInteger(12))
.qrCodeRenewalIntervalExponentCompact(message.getNextInteger(5))
.venueType(message.getNextInteger(5))
.venueCategory1(message.getNextInteger(4))
.venueCategory2(message.getNextInteger(4))
.periodDuration(message.getNextInteger(8))
.compressedPeriodStartTime(message.getNextInteger(24))
.qrCodeValidityStartTime(message.getNextInteger(32))
.locationTemporarySecretKey(message.getNextByte(256))
.encryptedLocationContactMessage(encryptedLocationContactMessage);
return locationSpecificPartbuilder.build();
}
}
......@@ -4,6 +4,8 @@
package fr.inria.clea.lsp;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
import fr.devnied.bitlib.BitUtils;
import lombok.extern.slf4j.Slf4j;
......@@ -19,12 +21,19 @@ public class LocationSpecificPartEncoder {
private CleaEciesEncoder cleaEciesEncoder;
private String serverAuthorityPublicKey;
public LocationSpecificPartEncoder(String serverAuthorityPublicKey) {
public LocationSpecificPartEncoder() {
this.cleaEciesEncoder = new CleaEciesEncoder();
}
public LocationSpecificPartEncoder(String serverAuthorityPublicKey) {
this();
this.serverAuthorityPublicKey = serverAuthorityPublicKey;
}
public byte[] encode(LocationSpecificPart locationSpecificPart) throws CleaEncryptionException {
if (Objects.isNull(serverAuthorityPublicKey)) {
throw new CleaEncryptionException("Cannot encrypt, serverAuthorityPublicKey is null!");
}
byte[] header = this.binaryEncodedHeader(locationSpecificPart);
byte[] msg = this.binaryEncodedMessage(locationSpecificPart);
byte[] encryptedLocationSpecificPart = this.encrypt(header, msg, this.serverAuthorityPublicKey);
......@@ -38,18 +47,28 @@ public class LocationSpecificPartEncoder {
* @return data header in binary format
*/
public byte[] binaryEncodedHeader(LocationSpecificPart locationSpecificPart) {
return this.binaryEncodedHeader(locationSpecificPart.getVersion(),
locationSpecificPart.getType(),
locationSpecificPart.getLocationTemporaryPublicId());
}
/**
* Encode the data header in binary format: | version | LSPtype | pad | LTId |
* @return data header in binary format
*/
public byte[] binaryEncodedHeader(int version, int type, UUID locationTemporaryPublicId) {
BitUtils header = new BitUtils(8 * CleaEciesEncoder.HEADER_BYTES_SIZE);
/* version (3 bits) */
header.setNextInteger(locationSpecificPart.getVersion(), 3);
header.setNextInteger(version, 3);
/* LSPtype (3 bits) */
header.setNextInteger(locationSpecificPart.getType(), 3);
header.setNextInteger(type, 3);
/* padding (2 bits) */
header.setNextInteger(0x0, 2);
/* LTId (16 bytes) */
byte[] uuidB = new byte[16];
uuidB = this.cleaEciesEncoder.uuidToBytes(locationSpecificPart.getLocationTemporaryPublicId());
uuidB = this.cleaEciesEncoder.uuidToBytes(locationTemporaryPublicId);
header.setNextByte(uuidB, 128);
return header.getData();
}
......
......@@ -32,7 +32,7 @@ public class LspEncoderDecoder {
String serverAuthoritySecretKey = args[2];
String manualContactTracingAuthoritySecretKey = args[3];
LocationSpecificPartDecoder lspDecoder = new LocationSpecificPartDecoder(serverAuthoritySecretKey);
LocationSpecificPart lsp = lspDecoder.decrypt(lspBase64).decodeMessage();
LocationSpecificPart lsp = lspDecoder.decrypt(lspBase64);
String valuesToreturn = (lsp.isStaff()? 1 : 0) + " " + lsp.getCountryCode() + " " + lsp.getQrCodeRenewalIntervalExponentCompact() + " " + lsp.getVenueType();
valuesToreturn += " " + lsp.getVenueCategory1() + " " + lsp.getVenueCategory2() + " " + lsp.getPeriodDuration() + " " + lsp.getLocationTemporaryPublicId();
......
......@@ -12,6 +12,7 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Random;
import java.util.UUID;
......@@ -109,7 +110,7 @@ class LocationSpecificPartTest {
assertThat(encryptedLocationSpecificPart).isNotNull();
/* Decode the encoded LSP */
LocationSpecificPart decodedLsp = new LocationSpecificPartDecoder(serverAuthorityKeyPair[0])
.decrypt(encryptedLocationSpecificPart).decodeMessage();
.decrypt(encryptedLocationSpecificPart);
assertThat(decodedLsp).isEqualTo(lsp);
assertThat(lsp.getEncryptedLocationContactMessage()).isNotNull();
......@@ -132,7 +133,7 @@ class LocationSpecificPartTest {
String encryptedLocationSpecificPart = location.getLocationSpecificPartEncryptedBase64();
/* Decode the encoded LSP */
LocationSpecificPart decodedLsp = new LocationSpecificPartDecoder(serverAuthorityKeyPair[0])
.decrypt(encryptedLocationSpecificPart).decodeMessage();
.decrypt(encryptedLocationSpecificPart);
assertThat(decodedLsp).isEqualTo(lsp);
}
......@@ -198,7 +199,7 @@ class LocationSpecificPartTest {
int periodStartTime, long qrStartTime, String serverAuthoritySecretKey, String serverAuthorityPublicKey,
String lspbase64) throws CleaEncryptionException, CleaEncodingException {
LocationSpecificPartDecoder decoder = new LocationSpecificPartDecoder(serverAuthoritySecretKey);
LocationSpecificPart lsp = decoder.decrypt(lspbase64).decodeMessage();
LocationSpecificPart lsp = decoder.decrypt(lspbase64);
assertThat(lsp.isStaff()).isEqualTo(staff == 1);
assertThat(lsp.getCountryCode()).isEqualTo(countryCode);
......@@ -246,7 +247,7 @@ class LocationSpecificPartTest {
String encryptedLocationSpecificPart = location.getLocationSpecificPartEncryptedBase64();
/* Decode the encoded LSP */
LocationSpecificPart decodedLsp = new LocationSpecificPartDecoder(serverAuthoritySecretKey)
.decrypt(encryptedLocationSpecificPart).decodeMessage();
.decrypt(encryptedLocationSpecificPart);
assertThat(decodedLsp).isEqualTo(lsp);
}
......@@ -264,11 +265,13 @@ class LocationSpecificPartTest {
String lspbase64) throws CleaEncryptionException, CleaEncodingException {
/* Decode the encoded LSP */
LocationSpecificPartDecoder decoder = new LocationSpecificPartDecoder(serverAuthoritySecretKey);
LocationSpecificPart lsp = decoder.decrypt(lspbase64).decodeMessage();
LocationSpecificPart lsp = decoder.decrypt(lspbase64);
System.out.println("EncryptedLocationContactMessage= "+Arrays.toString(lsp.getEncryptedLocationContactMessage()));
byte[] encryptedLocationContactMessage = lsp.getEncryptedLocationContactMessage();
LocationContact decodedLocationContact = new LocationContactMessageEncoder(
manualContactTracingAuthoritySecretKey).decode(encryptedLocationContactMessage);
System.out.println("decodedLocationContact= "+decodedLocationContact);
assertThat(decodedLocationContact.getLocationPhone()).isEqualTo(locationPhone);
assertThat(decodedLocationContact.getLocationPin()).isEqualTo(locationPin);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment