# Sample Source Code
예제로 사용된 전체 소스코드입니다.
# Source code for issuer
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import foundation.icon.did.Credential;
import foundation.icon.did.Presentation;
import foundation.icon.did.core.Algorithm;
import foundation.icon.did.core.AlgorithmProvider;
import foundation.icon.did.core.DidKeyHolder;
import foundation.icon.did.document.Document;
import foundation.icon.did.document.PublicKeyProperty;
import foundation.icon.did.exceptions.KeyPairException;
import foundation.icon.did.jwe.ECDHKey;
import foundation.icon.did.jwe.EphemeralPublicKey;
import foundation.icon.did.jwt.Jwt;
import foundation.icon.did.jwt.Payload;
import foundation.icon.did.protocol.ClaimRequest;
import foundation.icon.did.protocol.ProtocolMessage;
import foundation.icon.did.protocol.ProtocolType;
import foundation.icon.did.protocol.jsonld.*;
import foundation.icon.icx.KeyWallet;
import foundation.icon.myid.BaseService;
import foundation.icon.myid.IssuerService;
import foundation.icon.myid.credential.CredentialInfo;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.security.PublicKey;
import java.util.*;
public class PhoneVCIssVrfFullSource {
// Issuer 또는 Verifier의 KeyHolder. DID 및 privateKey를 가지고 있음
private DidKeyHolder didKeyHolder;
private String nonce; // 각 Holder마다 다른 nonce
private ECDHKey ecdhKey;
// DID 조회 및 VC 발급 시 사용하기 위한 클래스
private IssuerService issuerService;
// Holder Information
private String holderDid;
private EphemeralPublicKey holderEcdhPubKey;
// MyID 서비스를 사용하기 위한 MyID Server URL
private static final String MYID_WAS_URL = "https://iv-test.zzeung.kr";
private void init() {
try {
String did = "did:icon:5000:9c87958f05ef8bdaa21767fe10b85666e25189f59cf635aa";
KeyWallet wallet = KeyWallet.load("P@ssw0rd", new File("/did_icon_5000_9c87958f05ef8bdaa21767fe10b85666e25189f59cf635aa.json"));
didKeyHolder = getDidKeyHolder(wallet, did);
nonce = JsonLdUtil.getRandomNonce(32);
ecdhKey = ECDHKey.generateKey(ECDHKey.CURVE_P256K);
} catch (Exception e) {
e.printStackTrace();
}
issuerService = IssuerService.create(MYID_WAS_URL);
}
private DidKeyHolder getDidKeyHolder(KeyWallet wallet, String did) throws KeyPairException {
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K;
Algorithm algorithm = AlgorithmProvider.create(type);
return new DidKeyHolder.Builder()
.did(did)
.keyId("key-1") // 고정
.privateKey(algorithm.byteToPrivateKey(wallet.getPrivateKey().toByteArray()))
.type(type)
.build();
}
public IvProtocolMessage didInit() throws Exception {
EphemeralPublicKey serverPublicKey = generateEphemeralPublicKey(nonce, ecdhKey);
ClaimRequest claimRequest = new ClaimRequest.DidBuilder(ClaimRequest.Type.INIT)
.version("2.0") // 고정
.nonce(nonce)
.didKeyHolder(didKeyHolder)
.publicKey(serverPublicKey)
.build();
ProtocolMessage protocolMessage = new ProtocolMessage.RequestBuilder()
.type(ProtocolType.DID_INIT)
.claimRequest(claimRequest)
.build();
ProtocolMessage.SignResult signResult = protocolMessage.signEncrypt(didKeyHolder);
if (!signResult.isSuccess())
throw new Exception(); // 서명 실패
return new IvProtocolMessage(signResult.getResult());
}
private EphemeralPublicKey generateEphemeralPublicKey(String nonce, ECDHKey ecdhKey) {
return new EphemeralPublicKey.Builder()
.kid(nonce)
.epk(ecdhKey)
.build();
}
public boolean verifyDidAuth(IvProtocolMessage ivProtocolMessage) throws Exception {
// parse did_auth message
ProtocolMessage authMessage = ProtocolMessage.valueOf(ivProtocolMessage.toString());
// decrypt did_auth message (JWE -> JWT)
if (authMessage.isProtected()) {
authMessage.decryptJwe(ecdhKey);
}
// verify did_auth message (JWT)
boolean authVerifyResult = verifyProtocolMessage(authMessage, ProtocolType.DID_AUTH.getTypeName());
if (!authVerifyResult) {
// did_auth message 서명 검증 실패
return false;
}
String nonce = authMessage.getClaimResponseJwt().getNonce();
if(!this.nonce.equals(nonce)) {
return false; // nonce unmatched
}
holderDid = authMessage.getClaimResponseJwt().getDid();
holderEcdhPubKey = generateEphemeralPublicKey(nonce, authMessage.getJwe().getJweHeader().getEpk());
return true;
}
private boolean verifyProtocolMessage(ProtocolMessage protocolMessage, String type) throws Exception {
if (type == null) throw new IllegalArgumentException("The type for VCP message is null!");
if (!protocolMessage.getType().equalsIgnoreCase(type)) {
throw new Exception(); // VCP message's type is mismatched
}
Jwt jwt = protocolMessage.getJwt();
boolean verifyResult = verifyJwt(jwt);
if (!verifyResult) {
return false;
}
// VCP message 발급 시간 확인
verifyResult = jwt.verifyIat(3 * 60 * 1000L).isSuccess();
return verifyResult;
}
private boolean verifyJwt(Jwt jwt) throws Exception {
// jwt's getDid()
String did = jwt.getHeader().getKid().split("#")[0];
// jwt's getKeyId()
String keyId = jwt.getHeader().getKid().split("#")[1];
// document 조회
Document doc = issuerService.getDid(did);
if (doc == null) {
return false; // Failed to get DID Document from Blockchain
}
PublicKeyProperty publicKeyProperty = doc.getPublicKeyProperty(keyId);
if (publicKeyProperty == null || publicKeyProperty.isRevoked()) {
// DID Document is invalid
return false;
} else {
PublicKey publicKey = publicKeyProperty.getPublicKey();
Jwt.VerifyResult verifyResult = jwt.verify(publicKey);
return verifyResult.isSuccess();
}
}
public IvProtocolMessage issueCredential() throws Exception {
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("name", "홍길동");
claims.put("birthDate", "19981010");
claims.put("gender", "1");
claims.put("telco", "LGT");
claims.put("phoneNumber", "01012345678");
claims.put("connectingInformation", "ddd/wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww8==");
claims.put("citizenship", "0");
MobileAuthenticationKorCredential mobileAuthenticationKorCredential = new MobileAuthenticationKorCredential();
mobileAuthenticationKorCredential.setClaims(claims);
ProtocolMessage credProtocolMessage = mobileAuthenticationKorCredential.createCredential(
nonce,
holderDid,
holderEcdhPubKey,
didKeyHolder,
365 * 24 * 60 * 60 * 1000L); // VC 유효 기간 1년
ProtocolMessage.SignResult credSignResult = credProtocolMessage.signEncrypt(didKeyHolder, this.ecdhKey);
if(!Boolean.TRUE.equals(credSignResult.isSuccess())) {
// VC 서명 실패
throw new Exception();
}
Credential credential = Credential.valueOf(credProtocolMessage.getJwt());
BaseService.ServiceResult registerResult = issuerService.registerVC(credential, didKeyHolder);
if(!Boolean.TRUE.equals(registerResult.isSuccess())) {
// Blockchain에 VC 등록 실패
throw new Exception();
}
return new IvProtocolMessage(credSignResult.getResult());
}
public IvProtocolMessage createVerifiablePresentationRequest() throws Exception {
String nonce = JsonLdUtil.getRandomNonce(32);
String conditionId = UUID.randomUUID().toString();
VprCondition condition = new VprCondition.SimpleBuilder()
.conditionId(conditionId)
.context(Collections.singletonList("https://vc.zzeung.kr/credentials/mobile_authentication/kor/v1.json"))
.credentialType(MobileAuthenticationKorCredential.VC_TYPE)
.property(Arrays.asList("name", "birthDate", "phoneNumber"))
.build();
JsonLdVpr vpr = new JsonLdVpr.Builder()
.context(Collections.singletonList("https://vc.zzeung.kr/credentials/v1.json"))
.id("https://phonevrf.zzeung.kr/v1/verifier/vpr/" + nonce)
.presentationUrl("https://phonevrf.zzeung.kr/v1/verifier/vp")
.purpose("Confirmation of identity information")
.verifier(didKeyHolder.getDid())
.condition(condition)
.build();
ClaimRequest requestPresentation = new ClaimRequest.PresentationBuilder()
.didKeyHolder(didKeyHolder)
.requestDate(new Date())
.nonce(nonce)
.publicKey(generateEphemeralPublicKey(nonce, ecdhKey))
.version("2.0")
.vpr(vpr)
.build();
ProtocolMessage pm = new ProtocolMessage.RequestBuilder()
.type(ProtocolType.REQUEST_PRESENTATION)
.claimRequest(requestPresentation)
.build();
ProtocolMessage.SignResult signResult = pm.signEncrypt(didKeyHolder);
if (!signResult.isSuccess()) {
throw new Exception(); // vpr 생성 실패
}
return new IvProtocolMessage(signResult.getResult());
}
public boolean verifyPresentation(IvProtocolMessage ivProtocolMessage) throws Exception {
ProtocolMessage vpMessage = ProtocolMessage.valueOf(ivProtocolMessage.toString());
// presentation 서명 확인
if (!verifyVp(vpMessage)) {
return false;
}
Presentation presentation = vpMessage.getPresentationJwt();
JsonLdVp jsonLdVp = presentation.getVp();
VpCriteria vpCriteria = jsonLdVp.getFulfilledCriteria().get(0);
// check credential's type
List<String> vcTypes = vpCriteria.getVcParam().getTypes();
if(!vcTypes.contains(MobileAuthenticationKorCredential.VC_TYPE)) {
// 제출 받고자 하는 VC와 제출 받은 VC의 타입이 일치하지 않는 경우
return false;
}
return true;
}
private boolean verifyVp(ProtocolMessage vpMessage) throws Exception {
if (vpMessage.isProtected()) {
vpMessage.decryptJwe(ecdhKey);
}
// presentation 서명 확인
boolean vpVerifyResult = verifyProtocolMessage(vpMessage, ProtocolType.RESPONSE_PRESENTATION.getTypeName());
if (!vpVerifyResult) {
return false;
}
Presentation presentation = vpMessage.getPresentationJwt();
VpCriteria criteria = presentation.getVp().getFulfilledCriteria().get(0);
Credential credential = Credential.valueOf(criteria.getVc());
// presentation sender 와 credential target did 확인 (동일한 holder인지 확인)
if (!presentation.getDid().equals(credential.getTargetDid())) {
return false;
}
// credential 서명 확인
if (!verifyJwt(credential.getJwt())) {
return false;
}
// check vc is revoked or expired
String issuerDID = credential.getDid();
String vcSig = credential.getJwt().getSignature();
CredentialInfo credentialInfo = issuerService.getVC(issuerDID, vcSig);
if (credentialInfo == null) {
return false; // 블록체인에 등록되지 않은 VC
}
if(credentialInfo.getIsRevoke() != null && credentialInfo.getIsRevoke()) {
return false; // 폐기된 VC
}
Date expiryDate = new Date(credentialInfo.getExpiryDate()*1000L);
if(expiryDate.before(new Date())) {
return false; // 만료된 VC
}
// param 과 credentialSubject verify
if (!criteria.isVerifyParam()) {
return false;
}
return true;
}
public boolean revokeVc(IvProtocolMessage ivProtocolMessage) throws Exception {
// parse request_revocation message
ProtocolMessage revokeMessage = ProtocolMessage.valueOf(ivProtocolMessage.toString());
// verify request_revocation message (JWT)
boolean revokeVerifyResult = verifyProtocolMessage(revokeMessage, ProtocolType.REQUEST_REVOCATION.getTypeName());
if (!revokeVerifyResult) {
return false;
}
Payload payload = revokeMessage.getJwt().getPayload();
String sig = payload.getSig();
if (sig == null) {
return false;
}
BaseService.ServiceResult revokeResult = issuerService.revokeVC(sig, didKeyHolder.getDid(), didKeyHolder);
if (!Boolean.TRUE.equals(revokeResult.isSuccess())) {
return false;
}
return true;
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IvProtocolMessage {
@NotNull
String type;
String message;
@SerializedName("protected")
@JsonProperty("protected")
String _protected;
String param;
public IvProtocolMessage() {}
public IvProtocolMessage(JsonObject json) {
if(json.has("type")) {
type = json.get("type").getAsString();
}
if(json.has("message")) {
message = json.get("message").getAsString();
}
if(json.has("protected")) {
_protected = json.get("protected").getAsString();
}
if(json.has("param")) {
param = json.get("param").getAsString();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
if(type != null) {
sb.append("\"type\":\"").append(type).append("\"").append(",");
}
if(_protected != null) {
sb.append("\"protected\":\"").append(_protected).append("\"").append(",");
}
if(message != null) {
sb.append("\"message\":\"").append(message).append("\"").append(",");
}
if(param != null) {
sb.append("\"param\":\"").append(param).append("\"").append(",");
}
sb.delete(sb.length()-1, sb.length());
sb.append("}");
return sb.toString();
}
}
}