Skip to content

Commit

Permalink
feat: support new algorithm (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreibogus authored and aleksandra-bel committed Mar 15, 2024
1 parent c16a096 commit 9dd6f27
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class WalletKey extends MIWBaseEntity {

private String keyId;

@Column(nullable = false)
private String algorithm;

public KeyPair toDto() {
return new KeyPair(keyId, privateKey, publicKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public interface WalletKeyRepository extends BaseRepository<WalletKey, Long> {
* @param id the id
* @return the by wallet id
*/
WalletKey getByWalletId(Long id);
WalletKey getByWalletIdAndAlgorithm(Long id, String alg);

WalletKey findFirstByWallet_Bpn(String bpn);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public VerifiableCredential issueCredential(Map<String, Object> data, String cal
Validate.isFalse(callerBpn.equals(issuerWallet.getBpn())).launch(new ForbiddenException(BASE_WALLET_BPN_IS_NOT_MATCHING_WITH_REQUEST_BPN_FROM_TOKEN));

// get Key
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId());
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId(), issuerWallet.getAlgorithm());

// check if the expiryDate is set
Date expiryDate = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public PageImpl<VerifiableCredential> getCredentials(String credentialId, String
*/
@Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRED)
public VerifiableCredential issueBpnCredential(Wallet baseWallet, Wallet holderWallet, boolean authority) {
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(baseWallet.getId());
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(baseWallet.getId(), baseWallet.getAlgorithm());
List<String> types = List.of(VerifiableCredentialType.VERIFIABLE_CREDENTIAL, MIWVerifiableCredentialType.BPN_CREDENTIAL);
VerifiableCredentialSubject verifiableCredentialSubject = new VerifiableCredentialSubject(Map.of(StringPool.TYPE, MIWVerifiableCredentialType.BPN_CREDENTIAL,
StringPool.ID, holderWallet.getDid(),
Expand Down Expand Up @@ -233,7 +233,7 @@ public VerifiableCredential issueFrameworkCredential(IssueFrameworkCredentialReq

validateAccess(callerBPN, baseWallet);
// get Key
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(baseWallet.getId());
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(baseWallet.getId(), baseWallet.getAlgorithm());

//if base wallet issue credentials to itself
boolean isSelfIssued = isSelfIssued(holderWallet.getBpn());
Expand Down Expand Up @@ -284,7 +284,7 @@ public VerifiableCredential issueDismantlerCredential(IssueDismantlerCredentialR
//check duplicate
isCredentialExit(holderWallet.getDid(), MIWVerifiableCredentialType.DISMANTLER_CREDENTIAL);

byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId());
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId(), issuerWallet.getAlgorithm());

//if base wallet issue credentials to itself
boolean isSelfIssued = isSelfIssued(request.getBpn());
Expand Down Expand Up @@ -335,7 +335,7 @@ public VerifiableCredential issueMembershipCredential(IssueMembershipCredentialR

validateAccess(callerBPN, issuerWallet);

byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId());
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId(), issuerWallet.getAlgorithm());
List<String> types = List.of(VerifiableCredentialType.VERIFIABLE_CREDENTIAL, VerifiableCredentialType.MEMBERSHIP_CREDENTIAL);

//if base wallet issue credentials to itself
Expand Down Expand Up @@ -392,7 +392,7 @@ public VerifiableCredential issueCredentialUsingBaseWallet(String holderDid, Map
validateAccess(callerBpn, issuerWallet);

// get issuer Key
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId());
byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(issuerWallet.getId(), issuerWallet.getAlgorithm());

boolean isSelfIssued = isSelfIssued(holderWallet.getBpn());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import lombok.RequiredArgsConstructor;
import org.eclipse.tractusx.ssi.lib.model.did.Did;
import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential;
import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation;
import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentationBuilder;
import org.eclipse.tractusx.ssi.lib.serialization.jsonLd.JsonLdSerializer;
import org.eclipse.tractusx.ssi.lib.serialization.jwt.SerializedVerifiablePresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.security.interfaces.ECPrivateKey;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

@RequiredArgsConstructor
public class JwtPresentationFactoryService {
private static final Logger log = LoggerFactory.getLogger(JwtPresentationFactoryService.class);
private final JsonLdSerializer jsonLdSerializer;
private final Did agentDid;

public JwtPresentationFactoryService(Did agentDid, JsonLdSerializer jsonLdSerializer) {
this.agentDid = agentDid;
this.jsonLdSerializer = jsonLdSerializer;
}

public SignedJWT createPresentation(Did issuer, List<VerifiableCredential> credentials, String audience, ECPrivateKey ecPrivateKey) throws IOException {
VerifiablePresentationBuilder verifiablePresentationBuilder = new VerifiablePresentationBuilder();
URI uri = agentDid.toUri();
VerifiablePresentation verifiablePresentation = verifiablePresentationBuilder.id(URI.create("" + uri + "#" + UUID.randomUUID())).type(List.of("VerifiablePresentation")).verifiableCredentials(credentials).build();
SerializedVerifiablePresentation serializedVerifiablePresentation = jsonLdSerializer.serializePresentation(verifiablePresentation);
return create(issuer, audience, serializedVerifiablePresentation, ecPrivateKey);
}

public SignedJWT create(Did didIssuer, String audience, SerializedVerifiablePresentation serializedPresentation, ECPrivateKey ecPrivateKey) throws IOException {
try {
String issuer = didIssuer.toString();
String subject = didIssuer.toString();
Map<String, Object> vp = (Map)(new ObjectMapper()).readValue(serializedPresentation.getJson(), HashMap.class);
JWTClaimsSet claimsSet = (new JWTClaimsSet.Builder()).issuer(issuer).subject(subject).audience(audience)
.claim("vp", vp).expirationTime(new Date((new Date()).getTime() + 60000L)).jwtID(UUID.randomUUID().toString()).build();
return createSignedES256KJwt(ecPrivateKey, claimsSet, issuer);
} catch (Exception ex) {
log.error(ex.toString());
throw ex;
}
}

private static SignedJWT createSignedES256KJwt(ECPrivateKey ecPrivateKey, JWTClaimsSet claimsSet, String issuer) {
try {
JWSSigner signer = new ECDSASigner(ecPrivateKey);
if (!signer.supportedJWSAlgorithms().contains(JWSAlgorithm.ES256K)) {
throw new RuntimeException(String.format("Invalid signing method. Supported signing methods: %s", signer.supportedJWSAlgorithms().stream().map(Algorithm::getName).collect(Collectors.joining(", "))));
} else {
JWSAlgorithm algorithm = JWSAlgorithm.ES256K;
JOSEObjectType type = JOSEObjectType.JWT;
JWSHeader header = new JWSHeader(algorithm, type, null, null, null, null, null, null, null, null, issuer, true, (Map)null, (Base64URL)null);
SignedJWT vc = new SignedJWT(header, claimsSet);
vc.sign(signer);
return vc;
}
} catch (Exception e) {
log.error(e.toString());
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings;
import org.eclipse.tractusx.managedidentitywallets.constant.StringPool;
import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential;
Expand All @@ -40,6 +41,7 @@
import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException;
import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException;
import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException;
import org.eclipse.tractusx.managedidentitywallets.utils.SupportedAlgorithms;
import org.eclipse.tractusx.managedidentitywallets.utils.Validate;
import org.eclipse.tractusx.ssi.lib.crypt.octet.OctetKeyPairFactory;
import org.eclipse.tractusx.ssi.lib.crypt.x21559.x21559PrivateKey;
Expand All @@ -64,7 +66,9 @@
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.URI;
import java.security.interfaces.ECPrivateKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -135,31 +139,45 @@ public Map<String, Object> createPresentation(Map<String, Object> data, boolean
verifiableCredentials.add(verifiableCredential);
});

return buildVP(asJwt, audience, callerBpn, callerWallet, verifiableCredentials);
return buildVP(asJwt, audience, callerBpn, callerWallet, verifiableCredentials, false);
}

@SneakyThrows({ InvalidePrivateKeyFormat.class })
private Map<String, Object> buildVP(boolean asJwt, String audience, String callerBpn, Wallet callerWallet, List<VerifiableCredential> verifiableCredentials) {
private Map<String, Object> buildVP(boolean asJwt, String audience, String callerBpn, Wallet callerWallet,
List<VerifiableCredential> verifiableCredentials, boolean fromIatp) {
Map<String, Object> response = new HashMap<>();
if (asJwt) {
log.debug("Creating VP as JWT for bpn ->{}", callerBpn);
Validate.isFalse(StringUtils.hasText(audience)).launch(new BadDataException("Audience needed to create VP as JWT"));
String algo;
if (asJwt && fromIatp) {
algo = SupportedAlgorithms.ES256K.name();
Pair<Did, Object> result = getPKey(callerWallet, algo, audience, callerBpn);
ECPrivateKey ecPrivateKey = (ECPrivateKey) result;

//Issuer of VP is holder of VC
Did vpIssuerDid = DidParser.parse(callerWallet.getDid());
//JWT Factory
JwtPresentationFactoryService presentationFactory = new JwtPresentationFactoryService(new JsonLdSerializerImpl(), result.getLeft());
SignedJWT presentation;
try {
presentation = presentationFactory.createPresentation(result.getLeft()
, verifiableCredentials, audience, ecPrivateKey);
} catch (IOException e) {
throw new RuntimeException(e);
}

response.put(StringPool.VP, presentation.serialize());
} else if (asJwt && !fromIatp){
algo = SupportedAlgorithms.ECDSA.name();
Pair<Did, Object> result = getPKey(callerWallet, algo, audience, callerBpn);

//JWT Factory
SerializedJwtPresentationFactory presentationFactory = new SerializedJwtPresentationFactoryImpl(
new SignedJwtFactory(new OctetKeyPairFactory()), new JsonLdSerializerImpl(), vpIssuerDid);
new SignedJwtFactory(new OctetKeyPairFactory()), new JsonLdSerializerImpl(), result.getKey());

//Build JWT
x21559PrivateKey ed25519Key = walletKeyService.getPrivateKeyByWalletIdentifier(callerWallet.getId());
x21559PrivateKey ed25519Key = (x21559PrivateKey) result.getRight();
x21559PrivateKey privateKey = new x21559PrivateKey(ed25519Key.asByte());
SignedJWT presentation = presentationFactory.createPresentation(vpIssuerDid
, verifiableCredentials, audience, privateKey);
SignedJWT presentation = presentationFactory.createPresentation(result.getLeft(), verifiableCredentials, audience, privateKey);

response.put(StringPool.VP, presentation.serialize());
} else {
}
else {
log.debug("Creating VP as JSON-LD for bpn ->{}", callerBpn);
VerifiablePresentationBuilder verifiablePresentationBuilder =
new VerifiablePresentationBuilder();
Expand All @@ -176,6 +194,17 @@ private Map<String, Object> buildVP(boolean asJwt, String audience, String calle
return response;
}

private Pair<Did, Object> getPKey (Wallet callerWallet, String algorithm, String audience, String callerBpn) {
log.debug("Creating VP as JWT for bpn ->{}", callerBpn);
Validate.isFalse(StringUtils.hasText(audience)).launch(new BadDataException("Audience needed to create VP as JWT"));

//Issuer of VP is holder of VC
Did vpIssuerDid = DidParser.parse(callerWallet.getDid());

//Build JWT
return Pair.of(vpIssuerDid, walletKeyService.getPrivateKeyByWalletIdentifierAndAlgorithm(callerWallet.getId(), algorithm));
}


/**
* Validate presentation map.
Expand Down Expand Up @@ -329,8 +358,9 @@ public Map<String, Object> createVpWithRequiredScopes(SignedJWT innerJWT, boolea

holdersCredentials.forEach(c -> verifiableCredentials.add(c.getData()));

// if as JWT true -> get key ES256K and sign with it
Map<String, Object> vp = buildVP(asJwt, jwtClaimsSet.getAudience().get(0), callerWallet.getBpn(),
callerWallet, verifiableCredentials);
callerWallet, verifiableCredentials, true);
changeJtiStatus(jtiRecord);
return vp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@
import org.bouncycastle.util.io.pem.PemReader;
import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey;
import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletKeyRepository;
import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException;
import org.eclipse.tractusx.managedidentitywallets.utils.EncryptionUtils;
import org.eclipse.tractusx.managedidentitywallets.utils.SupportedAlgorithms;
import org.eclipse.tractusx.ssi.lib.crypt.x21559.x21559PrivateKey;
import org.springframework.stereotype.Service;

import java.io.StringReader;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;

/**
* The type Wallet key service.
Expand Down Expand Up @@ -67,10 +72,14 @@ protected SpecificationUtil<WalletKey> getSpecificationUtil() {
* @return the byte [ ]
*/
@SneakyThrows
public byte[] getPrivateKeyByWalletIdentifierAsBytes(long walletId) {
return getPrivateKeyByWalletIdentifier(walletId).asByte();
public byte[] getPrivateKeyByWalletIdentifierAsBytes(long walletId, String algorithm) {
Object privateKey = getPrivateKeyByWalletIdentifierAndAlgorithm(walletId, algorithm);
if (privateKey instanceof x21559PrivateKey) {
return ((x21559PrivateKey) privateKey).asByte();
} else {
return ((ECPrivateKey) privateKey).getEncoded();
}
}

/**
* Gets private key by wallet identifier.
*
Expand All @@ -79,11 +88,18 @@ public byte[] getPrivateKeyByWalletIdentifierAsBytes(long walletId) {
*/
@SneakyThrows

public x21559PrivateKey getPrivateKeyByWalletIdentifier(long walletId) {
WalletKey wallet = walletKeyRepository.getByWalletId(walletId);
public Object getPrivateKeyByWalletIdentifierAndAlgorithm(long walletId, String algorithm) {
WalletKey wallet = walletKeyRepository.getByWalletIdAndAlgorithm(walletId, algorithm);
String privateKey = encryptionUtils.decrypt(wallet.getPrivateKey());
byte[] content = new PemReader(new StringReader(privateKey)).readPemObject().getContent();
return new x21559PrivateKey(content);
if(SupportedAlgorithms.ECDSA.name().equals(algorithm)){
return new x21559PrivateKey(content);
} else if (SupportedAlgorithms.ES256K.name().equals(algorithm)){
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePrivate(new PKCS8EncodedKeySpec(content));
} else {
throw new BadDataException("not supported algorithm");
}
}

}

0 comments on commit 9dd6f27

Please sign in to comment.