diff --git a/pom.xml b/pom.xml
index 6d14b13..f1c216b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@ SPDX-License-Identifier: CC0-1.0
se.digg.wallet
eudiw-wallet-token-lib
- 0.0.7-SNAPSHOT
+ 0.0.8-SNAPSHOT
EUDI Wallet -- Token Library
Library for handling data types in the EUDI Wallet PoC project.
https://github.com/diggsweden/eudiw-wallet-token-lib
diff --git a/src/main/java/se/digg/wallet/datatypes/mdl/data/CBORUtils.java b/src/main/java/se/digg/wallet/datatypes/mdl/data/CBORUtils.java
index 7969c15..7dfbfd2 100644
--- a/src/main/java/se/digg/wallet/datatypes/mdl/data/CBORUtils.java
+++ b/src/main/java/se/digg/wallet/datatypes/mdl/data/CBORUtils.java
@@ -12,8 +12,16 @@
import com.upokecenter.cbor.CBORType;
import com.upokecenter.numbers.EInteger;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
@@ -22,12 +30,17 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import javax.crypto.KeyAgreement;
import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
+import org.bouncycastle.crypto.params.HKDFParameters;
import se.digg.cose.AlgorithmID;
import se.digg.cose.Attribute;
import se.digg.cose.COSEKey;
import se.digg.cose.CoseException;
import se.digg.cose.HeaderKeys;
+import se.digg.cose.MAC0COSEObject;
import se.digg.cose.Sign1COSEObject;
/**
@@ -242,4 +255,107 @@ public static Sign1COSEObject sign(
coseSignature.sign(key);
return coseSignature;
}
+
+ public static MAC0COSEObject deviceComputedMac(byte[] deviceAuthenticationBytes, PrivateKey privateKey, PublicKey publicKey) throws GeneralSecurityException, CoseException {
+ byte[] sharedSecret = deriveSharedSecret(privateKey, publicKey);
+ MAC0COSEObject mac0COSEObject = new MAC0COSEObject();
+ mac0COSEObject.addAttribute(
+ HeaderKeys.Algorithm,
+ AlgorithmID.HMAC_SHA_256.AsCBOR(),
+ Attribute.PROTECTED);
+ mac0COSEObject.SetContent(deviceAuthenticationBytes);
+ byte[] macKey = deriveEMacKey(sharedSecret, deviceAuthenticationBytes);
+ mac0COSEObject.Create(macKey);
+ return mac0COSEObject;
+ }
+
+ /**
+ * Derives the EMacKey using the HKDF function as defined in RFC 5869.
+ *
+ * @param zab input keying material (IKM) as byte array.
+ * @param sessionTranscriptBytes session transcript bytes to be hashed with SHA-256 as salt
+ * @return A 32-byte EMacKey derived from HKDF.
+ */
+ public static byte[] deriveEMacKey(byte[] zab, byte[] sessionTranscriptBytes) {
+ // Step 1: Create salt as SHA-256(sessionTranscriptBytes)
+ SHA256Digest sha256Digest = new SHA256Digest(); // Use BouncyCastle's Digest
+ byte[] salt = hash(sha256Digest, sessionTranscriptBytes);
+
+ // Step 2: Define the info parameter as "EMacKey" encoded in UTF-8
+ byte[] info = "EMacKey".getBytes(StandardCharsets.UTF_8);
+
+ // Step 3: Setup HKDF parameters with SHA-256 hash, IKM, salt, and info.
+ HKDFParameters hkdfParameters = new HKDFParameters(zab, salt, info);
+
+ // Step 4: Create the HKDF generator
+ HKDFBytesGenerator hkdfGenerator = new HKDFBytesGenerator(sha256Digest);
+
+ // Step 5: Initialize the generator with our parameters
+ hkdfGenerator.init(hkdfParameters);
+
+ // Step 6: Generate the key (L = 32 bytes)
+ byte[] eMacKey = new byte[32];
+ hkdfGenerator.generateBytes(eMacKey, 0, eMacKey.length);
+
+ // Return the derived EMacKey
+ return eMacKey;
+ }
+
+ /**
+ * Helper method to hash input data using a given Digest.
+ *
+ * @param digest The SHA-256 Digest instance.
+ * @param input The input data to hash.
+ * @return The hashed result as a byte array.
+ */
+ private static byte[] hash(SHA256Digest digest, byte[] input) {
+ digest.reset();
+ digest.update(input, 0, input.length);
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+ return output;
+ }
+
+
+
+ /**
+ * Derives a shared secret using Diffie-Hellman (DH) key derivation.
+ *
+ * @param privateKey The private key (either RSA or EC).
+ * @param publicKey The public key (must be of the same type as the private key).
+ * @return A byte array representing the derived shared secret.
+ * @throws IllegalArgumentException if the keys are not of the same type or unsupported key types are provided.
+ * @throws GeneralSecurityException if key agreement fails.
+ */
+ public static byte[] deriveSharedSecret(PrivateKey privateKey, PublicKey publicKey)
+ throws GeneralSecurityException {
+ // Ensure the key types match
+ if (privateKey instanceof RSAPrivateKey || publicKey instanceof RSAPublicKey) {
+ throw new IllegalArgumentException("RSA keys cannot be used for key agreement (DH). Use EC keys instead.");
+ } else if (privateKey instanceof ECPrivateKey && publicKey instanceof ECPublicKey) {
+ return deriveECSharedSecret((ECPrivateKey) privateKey, (ECPublicKey) publicKey);
+ } else {
+ throw new IllegalArgumentException("Key types do not match or are unsupported. Use RSA or EC keys.");
+ }
+ }
+
+ /**
+ * Derives a shared secret using EC keys.
+ *
+ * @param privateKey The EC private key.
+ * @param publicKey The EC public key.
+ * @return A byte array representing the derived shared secret.
+ * @throws GeneralSecurityException if key agreement fails.
+ */
+ private static byte[] deriveECSharedSecret(ECPrivateKey privateKey, ECPublicKey publicKey)
+ throws GeneralSecurityException {
+ // Create EC Key Agreement
+ KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
+ keyAgreement.init(privateKey);
+ keyAgreement.doPhase(publicKey, true);
+
+ // Generate shared secret
+ return keyAgreement.generateSecret();
+ }
+
}
diff --git a/src/main/java/se/digg/wallet/datatypes/mdl/data/DeviceResponse.java b/src/main/java/se/digg/wallet/datatypes/mdl/data/DeviceResponse.java
index 19f76e4..630ebb2 100644
--- a/src/main/java/se/digg/wallet/datatypes/mdl/data/DeviceResponse.java
+++ b/src/main/java/se/digg/wallet/datatypes/mdl/data/DeviceResponse.java
@@ -36,6 +36,7 @@ public class DeviceResponse {
/**
* Constructor for the DeviceResponse class.
* Initializes a new instance of the DeviceResponse with the specified parameters.
+ * including signature device authentication
*
* @param docType the document type associated with the response.
* @param issuerSigned the issuer-signed data associated with the device response.
@@ -55,6 +56,29 @@ public DeviceResponse(
this.deviceMac = null;
}
+ /**
+ * Constructor for the DeviceResponse class.
+ * Initializes a new instance of the DeviceResponse with the specified parameters
+ * including MAC device authentication.
+ *
+ * @param deviceMac the byte array representing the device MAC.
+ * @param docType the document type associated with the response.
+ * @param issuerSigned the issuer-signed data associated with the device response.
+ */
+ public DeviceResponse(
+ byte[] deviceMac,
+ String docType,
+ IssuerSigned issuerSigned
+ ) {
+ this.issuerSigned = issuerSigned;
+ this.docType = docType;
+ this.version = "1.0";
+ this.status = 0;
+ this.deviceNameSpaces = CBORObject.NewMap();
+ this.deviceMac = deviceMac;
+ this.deviceSignature = null;
+ }
+
/** Status code. Default 0 for successful responses */
private final int status;
/** DocType for the response document */
@@ -67,7 +91,7 @@ public DeviceResponse(
private final CBORObject deviceNameSpaces;
/** The bytes of the device signature */
private final byte[] deviceSignature;
- /** The bytes of a wallet provided MAC (Currently not supported) */
+ /** The bytes of a wallet provided MAC */
private final byte[] deviceMac;
/**
diff --git a/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationInput.java b/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationInput.java
index 76ec02b..b5c4473 100644
--- a/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationInput.java
+++ b/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationInput.java
@@ -4,6 +4,7 @@
package se.digg.wallet.datatypes.mdl.data;
+import java.security.PublicKey;
import java.util.List;
import java.util.Map;
import lombok.Getter;
@@ -26,6 +27,10 @@ public class MdlPresentationInput
private String mdocGeneratedNonce;
/** The response URL where the presentation response is delivered */
private String responseUri;
+ /** Client MAC key derivation key **/
+ private PublicKey clientPublicKey;
+ /** Set to true to use MAC device authentication. If set to true clientPublicKey MUST be set */
+ boolean macDeviceAuthentication = false;
/**
* Creates and returns a new instance of the {@link MdlPresentationInputBuilder} class.
@@ -135,6 +140,32 @@ public MdlPresentationInputBuilder algorithm(
return this;
}
+ /**
+ * Sets the optional client public key to be used in the {@code MdlPresentationInput} object being built.
+ * If this key is provided, this will enable derivation of a MAC key to provide a MAC device key proof.
+ *
+ * @param clientPublicKey the {@code PublicKey} instance representing the client's public key
+ * @return the {@code MdlPresentationInputBuilder} instance for method chaining
+ */
+ public MdlPresentationInputBuilder clientPublicKey(PublicKey clientPublicKey) {
+ mdlPresentationInput.clientPublicKey = clientPublicKey;
+ return this;
+ }
+
+ /**
+ * Sets whether MAC (Message Authentication Code) device authentication should be enabled
+ * in the {@code MdlPresentationInput} object being built.
+ *
+ * @param macDeviceAuthentication a boolean value indicating whether MAC device authentication
+ * should be enabled. If set to true, MAC device authentication
+ * will be used; otherwise, device signature will be applied (default false).
+ * @return the {@code MdlPresentationInputBuilder} instance for method chaining.
+ */
+ public MdlPresentationInputBuilder macDeviceAuthentication(boolean macDeviceAuthentication) {
+ mdlPresentationInput.macDeviceAuthentication = macDeviceAuthentication;
+ return this;
+ }
+
/**
* Builds and returns the fully constructed {@code MdlPresentationInput} object.
*
diff --git a/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationValidationInput.java b/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationValidationInput.java
index e15d758..7ed8ac0 100644
--- a/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationValidationInput.java
+++ b/src/main/java/se/digg/wallet/datatypes/mdl/data/MdlPresentationValidationInput.java
@@ -4,6 +4,7 @@
package se.digg.wallet.datatypes.mdl.data;
+import java.security.PrivateKey;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@@ -46,4 +47,6 @@ public MdlPresentationValidationInput(
private String responseUri;
/** The wallet generated nonce included as the apu header parameter in the presentation response JWT */
private String mdocGeneratedNonce;
+ /** Optional private key for MAC validation */
+ private PrivateKey clientPrivateKey;
}
diff --git a/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidator.java b/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidator.java
index 566dcba..b603450 100644
--- a/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidator.java
+++ b/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidator.java
@@ -13,6 +13,7 @@
import org.bouncycastle.util.encoders.Hex;
import se.digg.cose.COSEObjectTag;
import se.digg.cose.CoseException;
+import se.digg.cose.MAC0COSEObject;
import se.digg.cose.Sign1COSEObject;
import se.digg.wallet.datatypes.common.PresentationValidationInput;
import se.digg.wallet.datatypes.common.PresentationValidator;
@@ -124,23 +125,12 @@ public MdlPresentationValidationResult validatePresentation(
new MdlIssuerSignedValidator(timeSkew);
MdlIssuerSignedValidationResult issuerSignedValidationResult =
issuerSignedValidator.validateToken(issuerSignedBytes, trustedKeys);
- // For now only accept device signatures
- if (deviceResponse.getDeviceMac() != null) {
- // TODO support device Mac authentication
- log.debug(
- "This presentation has a device mac. This is not supported yet and ignored"
- );
- }
- if (deviceResponse.getDeviceSignature() == null) {
- // As we only support device signature. One must be available
+ // Ensure that device MAC or signature is present
+ if (deviceResponse.getDeviceMac() == null && deviceResponse.getDeviceSignature() == null) {
throw new TokenValidationException(
- "Token presentation must have a device signature"
+ "Token presentation must name device mac or device signature"
);
}
- // Get the detached device signature
- CBORObject deviceSignatureObject = CBORObject.DecodeFromBytes(
- deviceResponse.getDeviceSignature()
- );
// Reconstruct the detached data
DeviceAuthentication deviceAuthentication = new DeviceAuthentication(
deviceResponse.getDocType(),
@@ -151,32 +141,73 @@ public MdlPresentationValidationResult validatePresentation(
input.getMdocGeneratedNonce()
)
);
- // Insert the detached data as payload
- deviceSignatureObject.set(
- 2,
- CBORObject.FromByteArray(
- deviceAuthentication.getDeviceAuthenticationBytes()
- )
- );
- // Create the signed object with the restored payload
- Sign1COSEObject sign1COSEObject =
- (Sign1COSEObject) Sign1COSEObject.DecodeFromBytes(
- deviceSignatureObject.EncodeToBytes(),
- COSEObjectTag.Sign1
- );
- // Get the device key
+ // Get the wallet device key
MobileSecurityObject.DeviceKeyInfo deviceKeyInfo =
issuerSignedValidationResult.getMso().getDeviceKeyInfo();
- // Validate signature against device key
- boolean deviceSignatureValid = sign1COSEObject.validate(
- deviceKeyInfo.getDeviceKey()
- );
- if (!deviceSignatureValid) {
- // Device signature was invalid
- throw new TokenValidationException("Device signature is invalid");
+
+ // Validate MAC if present
+ if (deviceResponse.getDeviceMac() != null) {
+ if (input.getClientPrivateKey() == null) {
+ throw new TokenValidationException(
+ "Client private key must be provided for MAC validation"
+ );
+ }
+ CBORObject deviceMacObject = CBORObject.DecodeFromBytes(
+ deviceResponse.getDeviceMac()
+ );
+ // Insert the detached data as payload
+ deviceMacObject.set(
+ 2,
+ CBORObject.FromByteArray(
+ deviceAuthentication.getDeviceAuthenticationBytes()
+ )
+ );
+ MAC0COSEObject mac0COSEObject =
+ (MAC0COSEObject) MAC0COSEObject.DecodeFromBytes(
+ deviceMacObject.EncodeToBytes(),
+ COSEObjectTag.MAC0
+ );
+ boolean validMac = mac0COSEObject.Validate(CBORUtils.deriveEMacKey(
+ CBORUtils.deriveSharedSecret(input.getClientPrivateKey(), deviceKeyInfo.getDeviceKey().AsPublicKey()),
+ deviceAuthentication.getDeviceAuthenticationBytes()
+ ));
+ if (!validMac) {
+ // Device signature was invalid
+ throw new TokenValidationException("Device signature is invalid");
+ }
+ log.debug("Device MAC is valid");
+ }
+ // Validate device signature if present
+ if (deviceResponse.getDeviceSignature() != null) {
+ // Get the detached device signature
+ CBORObject deviceSignatureObject = CBORObject.DecodeFromBytes(
+ deviceResponse.getDeviceSignature()
+ );
+ // Insert the detached data as payload
+ deviceSignatureObject.set(
+ 2,
+ CBORObject.FromByteArray(
+ deviceAuthentication.getDeviceAuthenticationBytes()
+ )
+ );
+ // Create the signed object with the restored payload
+ Sign1COSEObject sign1COSEObject =
+ (Sign1COSEObject) Sign1COSEObject.DecodeFromBytes(
+ deviceSignatureObject.EncodeToBytes(),
+ COSEObjectTag.Sign1
+ );
+ // Validate signature against device key
+ boolean deviceSignatureValid = sign1COSEObject.validate(
+ deviceKeyInfo.getDeviceKey()
+ );
+ if (!deviceSignatureValid) {
+ // Device signature was invalid
+ throw new TokenValidationException("Device signature is invalid");
+ }
+ // Signature is valid. Provide result data
+ log.debug("Device signature is valid");
}
- // Signature is valid. Provide result data
- log.debug("Device signature is valid");
+ // Retrieve disclosed signatures
Map disclosedAttributes =
getDisclosedAttributes(issuerSigned.getNameSpaces());
issuerSignedValidationResult.setPresentationRequestNonce(
diff --git a/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenter.java b/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenter.java
index 7b29fcb..acefd76 100644
--- a/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenter.java
+++ b/src/main/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenter.java
@@ -6,12 +6,14 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.upokecenter.cbor.CBORObject;
+import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.util.List;
import java.util.Map;
import se.digg.cose.COSEKey;
import se.digg.cose.CoseException;
+import se.digg.cose.MAC0COSEObject;
import se.digg.cose.Sign1COSEObject;
import se.digg.wallet.datatypes.common.PresentationInput;
import se.digg.wallet.datatypes.common.TokenParsingException;
@@ -76,21 +78,45 @@ public byte[] presentToken(
);
COSEKey key = new COSEKey(null, privateKey);
- Sign1COSEObject signedCOSEObject = CBORUtils.sign(
- deviceAuthentication.getDeviceAuthenticationBytes(),
- key,
- input.getAlgorithm().getAlgorithmID(),
- null,
- null,
- false
- );
- signedCOSEObject.SetContent((byte[]) null);
- CBORObject deviceSignature = signedCOSEObject.EncodeToCBORObject();
+ byte[] deviceSignatureCbor = null;
+ byte[] deviceMacCbor = null;
+ if (input.isMacDeviceAuthentication()) {
+ if (input.getClientPublicKey() == null) {
+ throw new TokenPresentationException(
+ "Client public key must be provided for MAC device authentication"
+ );
+ }
+ MAC0COSEObject mac0COSEObject = CBORUtils.deviceComputedMac(
+ deviceAuthentication.getDeviceAuthenticationBytes(),
+ privateKey,
+ input.getClientPublicKey()
+ );
+ mac0COSEObject.SetContent((byte[]) null);
+ deviceMacCbor = mac0COSEObject.EncodeToBytes();
+ } else {
+ Sign1COSEObject signedCOSEObject = CBORUtils.sign(
+ deviceAuthentication.getDeviceAuthenticationBytes(),
+ key,
+ input.getAlgorithm().getAlgorithmID(),
+ null,
+ null,
+ false
+ );
+ signedCOSEObject.SetContent((byte[]) null);
+ CBORObject deviceSignature = signedCOSEObject.EncodeToCBORObject();
+ deviceSignatureCbor = deviceSignature.EncodeToBytes();
+ }
- DeviceResponse deviceResponse = new DeviceResponse(
+ DeviceResponse deviceResponse = deviceSignatureCbor != null
+ ? new DeviceResponse(
docType,
issuerSigned,
- deviceSignature.EncodeToBytes()
+ deviceSignatureCbor
+ )
+ : new DeviceResponse(
+ deviceMacCbor,
+ docType,
+ issuerSigned
);
return CBORUtils.CBOR_MAPPER.writeValueAsBytes(deviceResponse);
} catch (
@@ -104,6 +130,8 @@ public byte[] presentToken(
throw new TokenPresentationException("Missing required input", e);
} catch (JsonProcessingException e) {
throw new TokenPresentationException("Error serializing token", e);
+ } catch (GeneralSecurityException e) {
+ throw new TokenPresentationException("Error creating MAC", e);
}
} else {
throw new TokenPresentationException(
diff --git a/src/test/java/se/digg/wallet/datatypes/common/TestCredentials.java b/src/test/java/se/digg/wallet/datatypes/common/TestCredentials.java
index b223672..0f3f1a4 100644
--- a/src/test/java/se/digg/wallet/datatypes/common/TestCredentials.java
+++ b/src/test/java/se/digg/wallet/datatypes/common/TestCredentials.java
@@ -26,6 +26,7 @@ public class TestCredentials {
public static final PkiCredential rsa_issuerCredential;
public static final ECKey p256_walletKey;
public static final RSAKey rsa_walletKey;
+ public static final ECKey p256_clientKey;
static {
if (Security.getProvider("BC") == null) {
@@ -48,6 +49,7 @@ public class TestCredentials {
"Test1234".toCharArray()
);
p256_walletKey = new ECKeyGenerator(Curve.P_256).generate();
+ p256_clientKey = new ECKeyGenerator(Curve.P_256).generate();
rsa_walletKey = new RSAKeyGenerator(3072).generate();
} catch (
KeyStoreException
diff --git a/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlIssuerSignedValidatorTest.java b/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlIssuerSignedValidatorTest.java
index cf3fcaa..4af210a 100644
--- a/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlIssuerSignedValidatorTest.java
+++ b/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlIssuerSignedValidatorTest.java
@@ -20,6 +20,7 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Disabled;
import se.digg.wallet.datatypes.common.TestCredentials;
import se.digg.wallet.datatypes.common.TestData;
import se.digg.wallet.datatypes.common.TokenInput;
@@ -42,24 +43,48 @@ static void setup() {
}
@Test
- void testReferenceImplPIDExample() throws Exception {
+ void testReferenceImplPIDExampleExpired() throws Exception {
byte[] issuerSignedBytes = Base64.getUrlDecoder()
.decode(
"omppc3N1ZXJBdXRohEOhASahGCFZAukwggLlMIICaqADAgECAhRoQu0mnaibjqEFrDO7g1RxBIyzBDAKBggqhkjOPQQDAjBcMR4wHAYDVQQDDBVQSUQgSXNzdWVyIENBIC0gVVQgMDExLTArBgNVBAoMJEVVREkgV2FsbGV0IFJlZmVyZW5jZSBJbXBsZW1lbnRhdGlvbjELMAkGA1UEBhMCVVQwHhcNMjQwNzAxMTAwMzA2WhcNMjUwOTI0MTAwMzA1WjBUMRYwFAYDVQQDDA1QSUQgRFMgLSAwMDAyMS0wKwYDVQQKDCRFVURJIFdhbGxldCBSZWZlcmVuY2UgSW1wbGVtZW50YXRpb24xCzAJBgNVBAYTAlVUMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE66T6UUJ8d2wrkB_g0zroSJ_boX3LL1wToHmFgFCaVQoS5OQ6gx64rPFJ36iBrfXBZbWUOvORiayYAE6H1XXyVKOCARAwggEMMB8GA1UdIwQYMBaAFLNsuJEXHNekGmYxh0Lhi8BAzJUbMBYGA1UdJQEB_wQMMAoGCCuBAgIAAAECMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHBzOi8vcHJlcHJvZC5wa2kuZXVkaXcuZGV2L2NybC9waWRfQ0FfVVRfMDEuY3JsMB0GA1UdDgQWBBQEfQ5D1-0ZE9VvaFJOS-fzBhMSyjAOBgNVHQ8BAf8EBAMCB4AwXQYDVR0SBFYwVIZSaHR0cHM6Ly9naXRodWIuY29tL2V1LWRpZ2l0YWwtaWRlbnRpdHktd2FsbGV0L2FyY2hpdGVjdHVyZS1hbmQtcmVmZXJlbmNlLWZyYW1ld29yazAKBggqhkjOPQQDAgNpADBmAjEAkfm_P8cc8y1BtYvC4tH1-iB1spuGpMRpYvxZZxpbhoMZ10fyDDwXC-knmtzkP0p7AjEA2l-9N2LXnG-vqaO2rCgylMXMV8L_HHB-fW_WThZoljQc5_XuOihslQXdIyY-BTvbWQJZ2BhZAlSmZ2RvY1R5cGV3ZXUuZXVyb3BhLmVjLmV1ZGkucGlkLjFndmVyc2lvbmMxLjBsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMTEtMTlUMDc6NDk6MjJaaXZhbGlkRnJvbcB0MjAyNC0xMS0xOVQwNzo0OToyMlpqdmFsaWRVbnRpbMB0MjAyNS0wMi0xN1QwMDowMDowMFpsdmFsdWVEaWdlc3RzoXdldS5ldXJvcGEuZWMuZXVkaS5waWQuMagAWCCGNciAN9igIeefh-AQ24CTD1s5ORel1XP6hPVz3K3QmQFYINawr_Kdw9G05xrGf1TFQD85TNYkdT60thyE9MrcSoBNAlggBq91GsYxZmah6lMMfvL9CizLxzuw1uAgwDtRIk42pJ4DWCBtTtVrgul7w-q4MZQ0hEMADThV8av9NB3qWvYnsUA8JwRYIK3lQYLuc_Kqz0Tdwh1AYG3GIGEVx3LGmbYsdHBjNP7wBVggGOM7qlE0zCuypNJlRA7kji-bajVG0AjFyb9hH8W8hNsGWCBVss1tDxnZKwHgGstmqCOXquTRUc0mFGIlXPMOS_o07wdYIAju4pNtAereVAFZs5P73nx0gLd7gDEnCrINUFfPFvIUbWRldmljZUtleUluZm-haWRldmljZUtleaQBAiABIVggGrIEwKcz8CGMXiHuLu9_lhhjS3o7CpFMAQig0fsjVAgiWCDtwkQGOvgl5Qwbrf8iHmhkFE_8Xg0OrYUwCNh5jgzaAm9kaWdlc3RBbGdvcml0aG1nU0hBLTI1NlhAq7N4-Y7IRpOmwhoUix4mNNXKwAzyOAPnRsqXofpjWWEGvGoFI8n3u35SoRYRDFHBBhYOH_INJG5tswXXMeKnjmpuYW1lU3BhY2VzoXdldS5ldXJvcGEuZWMuZXVkaS5waWQuMYjYGFhvpGZyYW5kb21YIGVoS8sXJh-ZQ_LQATusxoxaTHZ4Rwdcpd9KWWSu_ULqaGRpZ2VzdElEAGxlbGVtZW50VmFsdWXZA-xqMjAyNC0xMS0xOXFlbGVtZW50SWRlbnRpZmllcm1pc3N1YW5jZV9kYXRl2BhYZqRmcmFuZG9tWCDdaC8sDcj0xRude-HNRYyYSk4DfYcZkqOT_f93BTQPWWhkaWdlc3RJRAFsZWxlbWVudFZhbHVlYkZDcWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyedgYWGWkZnJhbmRvbVgg2P2y7jnQZSixYYHTEc23U34Hv16jjF98VE_KTdnd1K5oZGlnZXN0SUQCbGVsZW1lbnRWYWx1ZWZKb2hubnlxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZdgYWGCkZnJhbmRvbVgge5HlU0GvXGjGRtFznX_gBDoDNN221Usojn5f30IZiztoZGlnZXN0SUQDbGVsZW1lbnRWYWx1ZfVxZWxlbWVudElkZW50aWZpZXJrYWdlX292ZXJfMTjYGFhspGZyYW5kb21YIBkLoFfpzjJsL3c92pTNT78-AabhwNJnRl4VO6VGKjfCaGRpZ2VzdElEBGxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMi0yMXFlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRl2BhYbaRmcmFuZG9tWCBwu_EZgKggzK40rVlY84u4yF2fT9ZO_4gdUa5tZuCInWhkaWdlc3RJRAVsZWxlbWVudFZhbHVl2QPsajIwMjUtMDItMTdxZWxlbWVudElkZW50aWZpZXJrZXhwaXJ5X2RhdGXYGFhnpGZyYW5kb21YIBRpZIUVP1qZhWZ54HoFOIGhiAAXXDTveL2sUoz_IDKwaGRpZ2VzdElEBmxlbGVtZW50VmFsdWVnVGh1bGFuZHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZdgYWHWkZnJhbmRvbVggznvLq6zlDkRQIKVzyWWzGdcWxgCa3Xl8mlrXBHwEFYNoZGlnZXN0SUQHbGVsZW1lbnRWYWx1ZW9UZXN0IFBJRCBpc3N1ZXJxZWxlbWVudElkZW50aWZpZXJxaXNzdWluZ19hdXRob3JpdHk="
);
MdlIssuerSignedValidator validator = new MdlIssuerSignedValidator();
+
+ try {
+ validator.validateToken(
+ issuerSignedBytes,
+ null
+ );
+ } catch (TokenValidationException e) {
+ log.info(
+ "Expired token throw error on validation suceeded. token:\n{}",
+ Hex.toHexString(issuerSignedBytes)
+ );
+ return;
+ }
+ throw new Exception("Expired token should not validate");
+ }
+
+ @Test
+ @Disabled // The token in this test is expired
+ void testReferenceImplPIDExample() throws Exception {
+ byte[] issuerSignedBytes = Base64.getUrlDecoder()
+ .decode(
+ "omppc3N1ZXJBdXRohEOhASahGCFZAukwggLlMIICaqADAgECAhRoQu0mnaibjqEFrDO7g1RxBIyzBDAKBggqhkjOPQQDAjBcMR4wHAYDVQQDDBVQSUQgSXNzdWVyIENBIC0gVVQgMDExLTArBgNVBAoMJEVVREkgV2FsbGV0IFJlZmVyZW5jZSBJbXBsZW1lbnRhdGlvbjELMAkGA1UEBhMCVVQwHhcNMjQwNzAxMTAwMzA2WhcNMjUwOTI0MTAwMzA1WjBUMRYwFAYDVQQDDA1QSUQgRFMgLSAwMDAyMS0wKwYDVQQKDCRFVURJIFdhbGxldCBSZWZlcmVuY2UgSW1wbGVtZW50YXRpb24xCzAJBgNVBAYTAlVUMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE66T6UUJ8d2wrkB_g0zroSJ_boX3LL1wToHmFgFCaVQoS5OQ6gx64rPFJ36iBrfXBZbWUOvORiayYAE6H1XXyVKOCARAwggEMMB8GA1UdIwQYMBaAFLNsuJEXHNekGmYxh0Lhi8BAzJUbMBYGA1UdJQEB_wQMMAoGCCuBAgIAAAECMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHBzOi8vcHJlcHJvZC5wa2kuZXVkaXcuZGV2L2NybC9waWRfQ0FfVVRfMDEuY3JsMB0GA1UdDgQWBBQEfQ5D1-0ZE9VvaFJOS-fzBhMSyjAOBgNVHQ8BAf8EBAMCB4AwXQYDVR0SBFYwVIZSaHR0cHM6Ly9naXRodWIuY29tL2V1LWRpZ2l0YWwtaWRlbnRpdHktd2FsbGV0L2FyY2hpdGVjdHVyZS1hbmQtcmVmZXJlbmNlLWZyYW1ld29yazAKBggqhkjOPQQDAgNpADBmAjEAkfm_P8cc8y1BtYvC4tH1-iB1spuGpMRpYvxZZxpbhoMZ10fyDDwXC-knmtzkP0p7AjEA2l-9N2LXnG-vqaO2rCgylMXMV8L_HHB-fW_WThZoljQc5_XuOihslQXdIyY-BTvbWQJZ2BhZAlSmZ2RvY1R5cGV3ZXUuZXVyb3BhLmVjLmV1ZGkucGlkLjFndmVyc2lvbmMxLjBsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMTEtMTlUMDc6NDk6MjJaaXZhbGlkRnJvbcB0MjAyNC0xMS0xOVQwNzo0OToyMlpqdmFsaWRVbnRpbMB0MjAyNS0wMi0xN1QwMDowMDowMFpsdmFsdWVEaWdlc3RzoXdldS5ldXJvcGEuZWMuZXVkaS5waWQuMagAWCCGNciAN9igIeefh-AQ24CTD1s5ORel1XP6hPVz3K3QmQFYINawr_Kdw9G05xrGf1TFQD85TNYkdT60thyE9MrcSoBNAlggBq91GsYxZmah6lMMfvL9CizLxzuw1uAgwDtRIk42pJ4DWCBtTtVrgul7w-q4MZQ0hEMADThV8av9NB3qWvYnsUA8JwRYIK3lQYLuc_Kqz0Tdwh1AYG3GIGEVx3LGmbYsdHBjNP7wBVggGOM7qlE0zCuypNJlRA7kji-bajVG0AjFyb9hH8W8hNsGWCBVss1tDxnZKwHgGstmqCOXquTRUc0mFGIlXPMOS_o07wdYIAju4pNtAereVAFZs5P73nx0gLd7gDEnCrINUFfPFvIUbWRldmljZUtleUluZm-haWRldmljZUtleaQBAiABIVggGrIEwKcz8CGMXiHuLu9_lhhjS3o7CpFMAQig0fsjVAgiWCDtwkQGOvgl5Qwbrf8iHmhkFE_8Xg0OrYUwCNh5jgzaAm9kaWdlc3RBbGdvcml0aG1nU0hBLTI1NlhAq7N4-Y7IRpOmwhoUix4mNNXKwAzyOAPnRsqXofpjWWEGvGoFI8n3u35SoRYRDFHBBhYOH_INJG5tswXXMeKnjmpuYW1lU3BhY2VzoXdldS5ldXJvcGEuZWMuZXVkaS5waWQuMYjYGFhvpGZyYW5kb21YIGVoS8sXJh-ZQ_LQATusxoxaTHZ4Rwdcpd9KWWSu_ULqaGRpZ2VzdElEAGxlbGVtZW50VmFsdWXZA-xqMjAyNC0xMS0xOXFlbGVtZW50SWRlbnRpZmllcm1pc3N1YW5jZV9kYXRl2BhYZqRmcmFuZG9tWCDdaC8sDcj0xRude-HNRYyYSk4DfYcZkqOT_f93BTQPWWhkaWdlc3RJRAFsZWxlbWVudFZhbHVlYkZDcWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyedgYWGWkZnJhbmRvbVgg2P2y7jnQZSixYYHTEc23U34Hv16jjF98VE_KTdnd1K5oZGlnZXN0SUQCbGVsZW1lbnRWYWx1ZWZKb2hubnlxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZdgYWGCkZnJhbmRvbVgge5HlU0GvXGjGRtFznX_gBDoDNN221Usojn5f30IZiztoZGlnZXN0SUQDbGVsZW1lbnRWYWx1ZfVxZWxlbWVudElkZW50aWZpZXJrYWdlX292ZXJfMTjYGFhspGZyYW5kb21YIBkLoFfpzjJsL3c92pTNT78-AabhwNJnRl4VO6VGKjfCaGRpZ2VzdElEBGxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMi0yMXFlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRl2BhYbaRmcmFuZG9tWCBwu_EZgKggzK40rVlY84u4yF2fT9ZO_4gdUa5tZuCInWhkaWdlc3RJRAVsZWxlbWVudFZhbHVl2QPsajIwMjUtMDItMTdxZWxlbWVudElkZW50aWZpZXJrZXhwaXJ5X2RhdGXYGFhnpGZyYW5kb21YIBRpZIUVP1qZhWZ54HoFOIGhiAAXXDTveL2sUoz_IDKwaGRpZ2VzdElEBmxlbGVtZW50VmFsdWVnVGh1bGFuZHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZdgYWHWkZnJhbmRvbVggznvLq6zlDkRQIKVzyWWzGdcWxgCa3Xl8mlrXBHwEFYNoZGlnZXN0SUQHbGVsZW1lbnRWYWx1ZW9UZXN0IFBJRCBpc3N1ZXJxZWxlbWVudElkZW50aWZpZXJxaXNzdWluZ19hdXRob3JpdHk="
+ );
+ MdlIssuerSignedValidator validator = new MdlIssuerSignedValidator();
MdlIssuerSignedValidationResult validationResult = validator.validateToken(
- issuerSignedBytes,
- null
+ issuerSignedBytes,
+ null
);
log.info(
- "Validation suceeded of token:\n{}",
- Hex.toHexString(issuerSignedBytes)
+ "Validation suceeded of token:\n{}",
+ Hex.toHexString(issuerSignedBytes)
);
log.info(
- "Signing certificate:\n{}",
- validationResult.getValidationCertificate().toString()
+ "Signing certificate:\n{}",
+ validationResult.getValidationCertificate().toString()
);
}
diff --git a/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidatorTest.java b/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidatorTest.java
index 1236b65..23e6ade 100644
--- a/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidatorTest.java
+++ b/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlPresentationValidatorTest.java
@@ -84,12 +84,20 @@ void testCases() throws Exception {
MdlPresentationInput presentationInput =
MdlTokenPresenterTest.getInputBuilder(ecToken, nameDisclosure).build();
+ MdlPresentationInput macPresentationInput =
+ MdlTokenPresenterTest.getInputBuilder(ecToken, nameDisclosure)
+ .clientPublicKey(TestCredentials.p256_clientKey.toPublicKey())
+ .macDeviceAuthentication(true)
+ .build();
MdlPresentationInput rsaPresentationInput =
MdlTokenPresenterTest.getInputBuilder(rsaToken, nameDisclosure).build();
MdlPresentationValidationInput validationInput =
new MdlPresentationValidationInput(presentationInput);
+ MdlPresentationValidationInput macValidationInput =
+ new MdlPresentationValidationInput(presentationInput);
+ macValidationInput.setClientPrivateKey(TestCredentials.p256_clientKey.toPrivateKey());
MdlPresentationValidationInput wrongNonceInput =
new MdlPresentationValidationInput();
wrongNonceInput.setClientId(presentationInput.getClientId());
@@ -111,6 +119,10 @@ void testCases() throws Exception {
presentationInput,
walletPrivate
);
+ byte[] macPresentedToken = tokenPresenter.presentToken(
+ macPresentationInput,
+ walletPrivate
+ );
byte[] rsaPresentedToken = tokenPresenter.presentToken(
rsaPresentationInput,
walletPrivate
@@ -126,6 +138,14 @@ void testCases() throws Exception {
allTrusted,
null
);
+ performTestCase(
+ "MAC test case",
+ defaultValidator,
+ macValidationInput,
+ macPresentedToken,
+ allTrusted,
+ null
+ );
performTestCase(
"RSA test case",
defaultValidator,
diff --git a/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenterTest.java b/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenterTest.java
index 343d408..13c4ecb 100644
--- a/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenterTest.java
+++ b/src/test/java/se/digg/wallet/datatypes/mdl/process/MdlTokenPresenterTest.java
@@ -87,6 +87,16 @@ void testCases() throws Exception {
walletPrivate,
null
);
+ performTestCase(
+ "Mac test case",
+ defaultPresenter,
+ getInputBuilder(ecToken, allDisclosure)
+ .clientPublicKey(TestCredentials.p256_clientKey.toPublicKey())
+ .macDeviceAuthentication(true)
+ .build(),
+ walletPrivate,
+ null
+ );
performTestCase(
"RSA issuer key",
defaultPresenter,