Skip to content

Commit

Permalink
Merge pull request WeBankBlockchain#92 from WeBankFinTech/feature/emb…
Browse files Browse the repository at this point in the history
…edded-credential-replace-sd

Embedded credential supports change of disclosure policy
  • Loading branch information
chaoxinhu authored Dec 2, 2019
2 parents e638782 + 293d12a commit fef3a26
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 13 deletions.
15 changes: 13 additions & 2 deletions src/main/java/com/webank/weid/constant/CredentialConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,23 @@ public String getTypeName() {
}

/**
* Default CPT value for embedded credential signature subject (multi-sign support).
* Default CPT ID for embedded credential signature subject (multi-sign support).
*/
public static final Integer CREDENTIAL_EMBEDDED_SIGNATURE_CPT = 106;

/**
* Default CPT value for embedded credentialPojo subject (multi-sign support).
* Default CPT ID for embedded credentialPojo subject (multi-sign support).
*/
public static final Integer CREDENTIALPOJO_EMBEDDED_SIGNATURE_CPT = 107;

/**
* Embedded trusted timestamp default CPT ID.
*/
public static final Integer EMBEDDED_TIMESTAMP_CPT = 108;

/**
* Embedded trusted timestamp envelop default CPT ID.
*/
public static final Integer EMBEDDED_TIMESTAMP_ENVELOP_CPT = 109;

}
67 changes: 67 additions & 0 deletions src/main/java/com/webank/weid/protocol/cpt/Cpt108.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright© (2019) WeBank Co., Ltd.
*
* This file is part of weid-java-sdk.
*
* weid-java-sdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* weid-java-sdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with weid-java-sdk. If not, see <https://www.gnu.org/licenses/>.
*/

package com.webank.weid.protocol.cpt;

import java.util.List;

import com.github.reinert.jjschema.Attributes;
import lombok.Data;

import com.webank.weid.protocol.base.CredentialPojo;

/**
* Trusted timestamping envelope.
*
* @author junqizhang 2019.11
*/

@Data
@Attributes(title = "Trusted Timestamping", description = "Trusted timestamping envelope")
public class Cpt108 {

/**
* ID of timestamp authority.
*/
String timestampAuthorityId;

/**
* information about timestamp authority.
*/
String timestampAuthority;

/**
* caculate the hash from the credentials.
*/
String claimHash;

/**
* trusted timestamping provided by the trusted third party or by the consensus of each node in
* the consortium chain.
*/
Long timestamp;

/**
* signed by Timestamp authority. authoritySignature = sign( hashKey )
*/
String authoritySignature;

@Attributes(required = true, description = "Original credential list to be signed")
List<CredentialPojo> credentialList;
}
53 changes: 53 additions & 0 deletions src/main/java/com/webank/weid/protocol/cpt/Cpt109.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright© (2019) WeBank Co., Ltd.
*
* This file is part of weid-java-sdk.
*
* weid-java-sdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* weid-java-sdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with weid-java-sdk. If not, see <https://www.gnu.org/licenses/>.
*/

package com.webank.weid.protocol.cpt;

import lombok.Data;

/**
* Trusted timestamping envelope.
*
* @author junqizhang 2019.11
*/

@Data
public class Cpt109 {

/**
* caculate the hash from the entire list rather than from any single credential.
*/
String claimHash;

/**
* trusted timestamping provided by the trusted third party or by the consensus of each node in
* the consortium chain.
*/
Long timestamp;

/**
* hashKey = hash(claimHash + timestamp) hashKey will be the key in the smart contract.
*/
String hashKey;

/**
* signed by Timestamp authority signature = sign( hashKey ).
*/
String signatureList;
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import com.webank.weid.util.DateUtils;
import com.webank.weid.util.WeIdUtils;


/**
* Service implementations for operations on Credential.
*
Expand Down Expand Up @@ -428,9 +429,14 @@ private static ErrorCode verifyContentInner(CredentialPojo credential, String pu
for (Object innerCredentialObject : innerCredentialList) {
// PublicKey can only be used in the passed-external check, so pass-in null key
try {
Map<String, Object> map = (Map<String, Object>) innerCredentialObject;
CredentialPojo innerCredential = DataToolUtils
.mapToObj(map, CredentialPojo.class);
CredentialPojo innerCredential;
if (!(innerCredentialObject instanceof CredentialPojo)) {
Map<String, Object> map = (Map<String, Object>) innerCredentialObject;
innerCredential = DataToolUtils
.mapToObj(map, CredentialPojo.class);
} else {
innerCredential = (CredentialPojo) innerCredentialObject;
}
errorCode = verifyContentInner(innerCredential, null);
if (errorCode != ErrorCode.SUCCESS) {
return errorCode;
Expand All @@ -446,7 +452,7 @@ private static ErrorCode verifyContentInner(CredentialPojo credential, String pu
}

private static ErrorCode verifySingleSignedCredential(
CredentialPojo credential,
CredentialPojo credential,
String publicKey
) {
ErrorCode errorCode = verifyCptFormat(
Expand All @@ -458,8 +464,28 @@ private static ErrorCode verifySingleSignedCredential(
return errorCode;
}
Map<String, Object> salt = credential.getSalt();
String rawData = CredentialPojoUtils
.getCredentialThumbprintWithoutSig(credential, salt, null);
String rawData;
if (CredentialPojoUtils.isEmbeddedCredential(credential)) {
List<Object> objList = (ArrayList<Object>) credential.getClaim().get("credentialList");
List<CredentialPojo> credentialList = new ArrayList<>();
try {
for (Object obj : objList) {
if (obj instanceof CredentialPojo) {
credentialList.add((CredentialPojo) obj);
} else {
credentialList.add(DataToolUtils
.mapToObj((HashMap<String, Object>) obj, CredentialPojo.class));
}
}
} catch (Exception e) {
logger.error("Failed to convert credentialPojo: " + e.getMessage());
return ErrorCode.CREDENTIAL_CLAIM_DATA_ILLEGAL;
}
rawData = CredentialPojoUtils.getEmbeddedCredentialThumbprintWithoutSig(credentialList);
} else {
rawData = CredentialPojoUtils
.getCredentialThumbprintWithoutSig(credential, salt, null);
}
String issuerWeid = credential.getIssuer();
if (StringUtils.isEmpty(publicKey)) {
// Fetch public key from chain
Expand Down Expand Up @@ -709,10 +735,11 @@ public ResponseData<CredentialPojo> addSignature(
claim.put("credentialList", trimmedCredentialMapList);
result.setClaim(claim);

// For embedded signature, salt here is totally meaningless - hence we left it blank
Map<String, Object> saltMap = DataToolUtils.clone(claim);
generateSalt(saltMap);
CredentialPojoUtils.clearMap(saltMap);
String rawData = CredentialPojoUtils
.getCredentialThumbprintWithoutSig(result, saltMap, null);
.getEmbeddedCredentialThumbprintWithoutSig(credentialList);
String signature = DataToolUtils.sign(rawData, privateKey);

result.putProofValue(ParamKeyConstant.PROOF_CREATED, result.getIssuanceDate());
Expand Down
93 changes: 93 additions & 0 deletions src/main/java/com/webank/weid/util/CredentialPojoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -190,6 +191,66 @@ public static String getCredentialPojoHash(CredentialPojo credentialPojo,
return DataToolUtils.sha3(rawData);
}

/**
* Concat the credential list (embedded) into a selective disclosure resistant String.
*
* @param credentialList the credential list
* @return the String
*/
public static String getEmbeddedCredentialThumbprintWithoutSig(
List<CredentialPojo> credentialList) {
String result = StringUtils.EMPTY;
// 1. sort against id
Map<String, CredentialPojo> credMap = new HashMap<>();
for (CredentialPojo credential : credentialList) {
credMap.put(credential.getId(), credential);
}
Map<String, CredentialPojo> treeMap = new TreeMap<>(credMap);
List<CredentialPojo> credList = new ArrayList<>();
for (String id : treeMap.keySet()) {
credList.add(treeMap.get(id));
}
// 2. do recursive compute
for (CredentialPojo credential : credList) {
if (!isEmbeddedCredential(credential)) {
result += getCredentialPojoHash(credential, null);
} else {
List<Object> objList = (ArrayList<Object>) credential.getClaim()
.get("credentialList");
List<CredentialPojo> newList = new ArrayList<>();
try {
for (Object obj : objList) {
if (obj instanceof CredentialPojo) {
newList.add((CredentialPojo) obj);
} else {
newList.add(DataToolUtils
.mapToObj((HashMap<String, Object>) obj, CredentialPojo.class));
}
}
} catch (Exception e) {
logger.error("Failed to convert credentialPojo: " + e.getMessage());
return null;
}
result += getEmbeddedCredentialThumbprintWithoutSig(newList);
}
}
return result;
}

/**
* Check whether a Credential is an embedded credential. Embedded Credential does not support
* Selective disclosure and its proof is empty.
*
* @param credential the credentialPojo
* @return true if yes, false otherwise
*/
public static boolean isEmbeddedCredential(CredentialPojo credential) {
int cptId = credential.getCptId();
return cptId == CredentialConstant.CREDENTIAL_EMBEDDED_SIGNATURE_CPT
|| cptId == CredentialConstant.CREDENTIALPOJO_EMBEDDED_SIGNATURE_CPT
|| cptId == CredentialConstant.EMBEDDED_TIMESTAMP_CPT
|| cptId == CredentialConstant.EMBEDDED_TIMESTAMP_ENVELOP_CPT;
}

/**
* Check if the given CredentialPojo is selectively disclosed, or not.
Expand Down Expand Up @@ -367,6 +428,38 @@ private static void addSaltAndGetHashForList(
}
}

/**
* Set all the values in a map to be null while preserving its key structure recursively.
*
* @param map the map
*/
public static void clearMap(Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object mapObj = map.get(key);
if (mapObj instanceof Map) {
clearMap((HashMap<String, Object>) mapObj);
} else if (mapObj instanceof List) {
clearMapList((ArrayList<Object>) mapObj);
} else {
map.put(key, StringUtils.EMPTY);
}
}
}

private static void clearMapList(ArrayList<Object> listObj) {
for (int i = 0; listObj != null && i < listObj.size(); i++) {
Object obj = listObj.get(i);
if (obj instanceof Map) {
clearMap((HashMap<String, Object>) obj);
} else if (obj instanceof List) {
clearMapList((ArrayList<Object>) obj);
} else {
listObj.set(i, StringUtils.EMPTY);
}
}
}

/**
* Check the credential and proof of presentationE.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,40 @@ public void testCreateMultiSignSdCredentialPojo_success() {
}

@Test
public void testMultiSignPojo_fromToJson() throws Exception {
public void testMultiSignPojo_fromToJson_ReplaceInnerCredential() throws Exception {
List<CredentialPojo> credPojoList = new ArrayList<>();
credPojoList.add(selectiveCredentialPojo);
credPojoList.add(credentialPojo);
WeIdAuthentication callerAuth = TestBaseUtil
.buildWeIdAuthentication(createWeIdResultWithSetAttr);
CredentialPojo doubleSigned =
credentialPojoService.addSignature(credPojoList, callerAuth).getResult();
System.out.println(doubleSigned);
System.out.println("A part: " + CredentialPojoUtils
.getEmbeddedCredentialThumbprintWithoutSig(credPojoList));
String serializedjson = doubleSigned.toJson();
System.out.println(serializedjson);
System.out.println("A is: " + serializedjson);
CredentialPojo cpj = CredentialPojo.fromJson(serializedjson);
Assert.assertTrue(CredentialPojoUtils.isEqual(cpj, doubleSigned));

ClaimPolicy claimPolicy = new ClaimPolicy();
claimPolicy.setFieldsToBeDisclosed("{\"name\":0,\"gender\":0,\"age\":0,\"id\":0}");
ResponseData<CredentialPojo> response =
credentialPojoService.createSelectiveCredential(credentialPojo, claimPolicy);
CredentialPojo tempPojo = response.getResult();
credPojoList = new ArrayList<>();
credPojoList.add(selectiveCredentialPojo);
credPojoList.add(tempPojo);
System.out.println("B part: " + CredentialPojoUtils
.getEmbeddedCredentialThumbprintWithoutSig(credPojoList));
CredentialPojo modifiedDoubleSigned = copyCredentialPojo(doubleSigned);
Map<String, Object> modifiedClaim = new HashMap<>();
modifiedClaim.put("credentialList", credPojoList);
modifiedDoubleSigned.setClaim(modifiedClaim);
serializedjson = modifiedDoubleSigned.toJson();
System.out.println("B is: " + serializedjson);
Assert.assertTrue(
credentialPojoService.verify(modifiedDoubleSigned.getIssuer(), modifiedDoubleSigned)
.getResult());
}

@Test
Expand Down

0 comments on commit fef3a26

Please sign in to comment.