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,