Skip to content

Commit

Permalink
[INJICERT-617] add support for EC K1 key generation & VC signing (mos…
Browse files Browse the repository at this point in the history
…ip#200)

* [INJICERT-617] add support for EC K1 key generation & VC signing

Signature support added:

- EcdsaKoblitzSignature2016
- EcdsaSecp256k1Signature2019 (verified)

Signed-off-by: Harsh Vardhan <[email protected]>

* [INJICERT-544] add context for ldp_vc in docker compose

Signed-off-by: Harsh Vardhan <[email protected]>

---------

Signed-off-by: Harsh Vardhan <[email protected]>
  • Loading branch information
vharsh authored Feb 6, 2025
1 parent 332ba69 commit 24aa28d
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ public class Constants {
public static final String TEMPLATE_NAME = "templateName";
public static final String ISSUER_URI = "issuerURI";
public static final String RENDERING_TEMPLATE_ID = "renderingTemplateId";
public static final String CERTIFY_VC_SIGN_EC_K1 = "CERTIFY_VC_SIGN_EC_K1";
public static final String CERTIFY_VC_SIGN_EC_R1 = "CERTIFY_VC_SIGN_EC_R1";
public static final String EC_SECP256K1_SIGN = "EC_SECP256K1_SIGN";
public static final String EC_SECP256R1_SIGN = "EC_SECP256R1_SIGN";
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class SignatureAlg {
public static final String ED25519_SIGNATURE_SUITE_2018 = "Ed25519Signature2018";

public static final String ED25519_SIGNATURE_SUITE_2020 = "Ed25519Signature2020";
// EC K1 curves
public static final String EC_K1_2016 = "EcdsaKoblitzSignature2016";
public static final String EC_SECP256K1_2019 = "EcdsaSecp256k1Signature2019"; // secp256k1

// RS256, PS256, ES256 --> JWSAlgorithm.RS256.getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ public void run(ApplicationArguments args) throws Exception {
ed25519Req.setApplicationId(Constants.CERTIFY_VC_SIGN_ED25519);
ed25519Req.setReferenceId(Constants.ED25519_REF_ID);
keymanagerService.generateECSignKey("certificate", ed25519Req);

// Generate an EC K1 Key
KeyPairGenerateRequestDto ecK1Req = new KeyPairGenerateRequestDto();
ecK1Req.setApplicationId(Constants.CERTIFY_VC_SIGN_EC_K1);
ecK1Req.setReferenceId(Constants.EC_SECP256K1_SIGN);
keymanagerService.generateECSignKey("certificate", ecK1Req);

// Generate an EC R1 Key
KeyPairGenerateRequestDto ecR1Req = new KeyPairGenerateRequestDto();
ecR1Req.setApplicationId(Constants.CERTIFY_VC_SIGN_EC_R1);
ecR1Req.setReferenceId(Constants.EC_SECP256R1_SIGN);
keymanagerService.generateECSignKey("certificate", ecK1Req);
}
log.info("===================== CERTIFY KEY SETUP COMPLETED ========================");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.mosip.certify.proofgenerators;

import com.danubetech.keyformats.jose.JWSAlgorithm;
import info.weboftrust.ldsignatures.LdProof;
import info.weboftrust.ldsignatures.canonicalizer.Canonicalizer;
import info.weboftrust.ldsignatures.canonicalizer.URDNA2015Canonicalizer;
import io.mosip.certify.core.constants.Constants;
import io.mosip.certify.core.constants.SignatureAlg;
import io.mosip.kernel.signature.dto.JWSSignatureRequestDto;
import io.mosip.kernel.signature.dto.JWTSignatureResponseDto;
import io.mosip.kernel.signature.service.SignatureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
* EcdsaKoblitzSignature2016 as per https://w3c-ccg.github.io/lds-koblitz2016/
* - secp256k1
* - used by Bitcoin & Etherium
* - from W3C Web Payments Community Group
*
* // NOTE: Avoid using this signature and use the 2019 EC K1 instead.
*/
@Component
@ConditionalOnProperty(name = "mosip.certify.data-provider-plugin.issuer.vc-sign-algo", havingValue = SignatureAlg.EC_K1_2016)
public class EcdsaKoblitzSignature2016ProofGenerator implements ProofGenerator {

@Autowired
SignatureService signatureService;

Canonicalizer canonicalizer = new URDNA2015Canonicalizer();

@Override
public String getName() {
return SignatureAlg.EC_K1_2016;
}

@Override
public Canonicalizer getCanonicalizer() {
return canonicalizer;
}

@Override
public LdProof generateProof(LdProof vcLdProof, String vcEncodedHash, Map<String, String> keyID) {
JWSSignatureRequestDto payload = new JWSSignatureRequestDto();
payload.setDataToSign(vcEncodedHash);
payload.setApplicationId(keyID.get(Constants.APPLICATION_ID));
payload.setReferenceId(keyID.get(Constants.REFERENCE_ID));
payload.setIncludePayload(false);
payload.setIncludeCertificate(false);
payload.setIncludeCertHash(true);
payload.setValidateJson(false);
payload.setB64JWSHeaderParam(false);
payload.setCertificateUrl("");
payload.setSignAlgorithm(JWSAlgorithm.ES256K);
JWTSignatureResponseDto jwsSignedData = signatureService.jwsSign(payload);
return LdProof.builder().base(vcLdProof).defaultContexts(false)
.jws(jwsSignedData.getJwtSignedData()).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.mosip.certify.proofgenerators;

import com.danubetech.keyformats.jose.JWSAlgorithm;
import info.weboftrust.ldsignatures.LdProof;
import info.weboftrust.ldsignatures.canonicalizer.Canonicalizer;
import info.weboftrust.ldsignatures.canonicalizer.URDNA2015Canonicalizer;
import io.mosip.certify.core.constants.Constants;
import io.mosip.certify.core.constants.SignatureAlg;
import io.mosip.kernel.signature.dto.JWSSignatureRequestDto;
import io.mosip.kernel.signature.dto.JWTSignatureResponseDto;
import io.mosip.kernel.signature.service.SignatureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
* EcdsaSecp256k1Signature2019 as per https://w3c-ccg.github.io/lds-ecdsa-secp256k1-2019/
* - secp256k1
* - from W3C Web Payments Community Group
* - ref: https://w3c-ccg.github.io/ld-cryptosuite-registry/#ecdsasecp256k1signature2019
*/
@Component
@ConditionalOnProperty(name = "mosip.certify.data-provider-plugin.issuer.vc-sign-algo", havingValue = SignatureAlg.EC_SECP256K1_2019)
public class EcdsaSecp256k1Signature2019ProofGenerator implements ProofGenerator {

@Autowired
SignatureService signatureService;

Canonicalizer canonicalizer = new URDNA2015Canonicalizer();

@Override
public String getName() {
return SignatureAlg.EC_SECP256K1_2019;
}

@Override
public Canonicalizer getCanonicalizer() {
return canonicalizer;
}

@Override
public LdProof generateProof(LdProof vcLdProof, String vcEncodedHash, Map<String, String> keyID) {
JWSSignatureRequestDto payload = new JWSSignatureRequestDto();
payload.setDataToSign(vcEncodedHash);
payload.setApplicationId(keyID.get(Constants.APPLICATION_ID));
payload.setReferenceId(keyID.get(Constants.REFERENCE_ID));
payload.setIncludePayload(false);
payload.setIncludeCertificate(false);
payload.setIncludeCertHash(true);
payload.setValidateJson(false);
payload.setB64JWSHeaderParam(false);
payload.setCertificateUrl("");
payload.setSignAlgorithm(JWSAlgorithm.ES256K);
JWTSignatureResponseDto jwsSignedData = signatureService.jwsSign(payload);
return LdProof.builder().base(vcLdProof).defaultContexts(false)
.jws(jwsSignedData.getJwtSignedData()).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ public class CertifyIssuanceServiceImpl implements VCIssuanceService {
public static final Map<String, List<String>> keyChooser = Map.of(
SignatureAlg.RSA_SIGNATURE_SUITE_2018, List.of(Constants.CERTIFY_VC_SIGN_RSA, Constants.EMPTY_REF_ID),
SignatureAlg.ED25519_SIGNATURE_SUITE_2018, List.of(Constants.CERTIFY_VC_SIGN_ED25519, Constants.ED25519_REF_ID),
SignatureAlg.ED25519_SIGNATURE_SUITE_2020, List.of(Constants.CERTIFY_VC_SIGN_ED25519, Constants.ED25519_REF_ID));
SignatureAlg.ED25519_SIGNATURE_SUITE_2020, List.of(Constants.CERTIFY_VC_SIGN_ED25519, Constants.ED25519_REF_ID),
SignatureAlg.EC_K1_2016, List.of(Constants.CERTIFY_VC_SIGN_EC_K1, Constants.EC_SECP256K1_SIGN),
SignatureAlg.EC_SECP256K1_2019, List.of(Constants.CERTIFY_VC_SIGN_EC_K1, Constants.EC_SECP256K1_SIGN));
@Value("${mosip.certify.data-provider-plugin.issuer.vc-sign-algo:Ed25519Signature2020}")
private String vcSignAlgorithm;
@Value("#{${mosip.certify.key-values}}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HexFormat;
import java.util.Map;

import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

Expand Down Expand Up @@ -41,6 +44,9 @@ public static Map<String, Object> generateDIDDocument(String vcSignAlgorithm, St
PublicKey publicKey = loadPublicKeyFromCertificate(certificateString);
try {
switch (vcSignAlgorithm) {
case SignatureAlg.EC_SECP256K1_2019:
verificationMethod = generateECK12019VerificationMethod(publicKey, issuerURI, issuerPublicKeyURI);
break;
case SignatureAlg.ED25519_SIGNATURE_SUITE_2018:
verificationMethod = generateEd25519VerificationMethod(publicKey, issuerURI, issuerPublicKeyURI);
break;
Expand Down Expand Up @@ -107,5 +113,28 @@ private static Map<String, Object> generateRSAVerificationMethod(PublicKey publi
verificationMethod.put("publicKeyJwk", publicKeyJwk);
return verificationMethod;
}


private static Map<String, Object> generateECK12019VerificationMethod(PublicKey publicKey, String issuerURI, String issuerPublicKeyURI) {
// TODO: can validate the key or directly assume the curve here and
// go ahead or use P_256 only if `nimbusCurve` is having same value.
ECKey nimbusKey = new ECKey.Builder(Curve.SECP256K1, (ECPublicKey) publicKey)
.build();

Map<String, Object> verificationMethod = new HashMap<>();
verificationMethod.put("id", issuerPublicKeyURI);

// ref: https://github.com/w3c-ccg/lds-ecdsa-secp256k1-2019/issues/8
verificationMethod.put("type", "EcdsaSecp256k1VerificationKey2019");
verificationMethod.put("@context", "https://w3id.org/security/v1");
// (improvement): can also add expires key here
verificationMethod.put("controller", issuerURI);
verificationMethod.put("publicKeyJwk", nimbusKey.toJSONObject());
// NOTE: Advice against using publicKeyHex by the spec author
// ref: https://github.com/w3c-ccg/lds-ecdsa-secp256k1-2019/issues/4
// ref: https://w3c.github.io/vc-data-integrity/vocab/security/vocabulary.html#publicKeyHex

// As per the below spec, publicKeyBase58 is also supported
// ref: https://w3c-ccg.github.io/ld-cryptosuite-registry/#ecdsasecp256k1signature2019
return verificationMethod;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ mosip.certify.key-values={\
'cryptographic_suites_supported': {'Ed25519Signature2020'},\
'proof_types_supported': {'jwt'},\
'credential_definition': {\
'type': {'VerifiableCredential','MockCredential'},\
'type': {'VerifiableCredential','MockCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,31 @@ void testGenerateDIDDocumentRSASignature2018() throws Exception {
assertEquals(verificationMethod.get("@context"), "https://w3id.org/security/suites/jws-2020/v1");
}

@Test
@SuppressWarnings("unchecked")
void testGenerateDIDDocumentECK1Signature2019() {
String vcSignAlgorithm = SignatureAlg.EC_SECP256K1_2019;
String certificateString = "-----BEGIN CERTIFICATE-----\nMIIDDTCCAfWgAwIBAgIIborC968KpkYwDQYJKoZIhvcNAQELBQAweDELMAkGA1UE\nBhMCSU4xCzAJBgNVBAgMAktBMRIwEAYDVQQHDAlCQU5HQUxPUkUxDjAMBgNVBAoM\nBUlJSVRCMRcwFQYDVQQLDA5FWEFNUExFLUNFTlRFUjEfMB0GA1UEAwwWd3d3LmV4\nYW1wbGUuY29tIChST09UKTAeFw0yNTAxMzAwMjI3MzBaFw0yODAxMzAwMjI3MzBa\nMIGbMQswCQYDVQQGEwJJTjELMAkGA1UECAwCS0ExEjAQBgNVBAcMCUJBTkdBTE9S\nRTEOMAwGA1UECgwFSUlJVEIxFzAVBgNVBAsMDkVYQU1QTEUtQ0VOVEVSMUIwQAYD\nVQQDDDl3d3cuZXhhbXBsZS5jb20gKENFUlRJRllfVkNfU0lHTl9FQ19LMS1FQ19T\nRUNQMjU2SzFfU0lHTikwVjAQBgcqhkjOPQIBBgUrgQQACgNCAARJNEyvwE3dOioZ\nhZATESbn6aPKJKqr2IazrTT7hQyJlsDAto8mGANVD8+U43h0ZEgGVesuvwuaMgj7\nLnVrRUDzo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTT5j1MZSg4\nfONSPosQb8SmeZ/OdDAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEB\nALFGo9udwFWHLrPfQth186H2VuSJ2qNu9vJiNUAgwTAwfM6q2rgpJKywD5Hmqtz1\nioFhr19eXznjQh3m4J75OKQEagmzVPeQvFEtMYuRZAkrX0kLNYhn/5pP8JnQ/W+p\nx+hm+MW0Txn3sX7QWHaSsIMk+vuemQwpjkfoJEzKzqISYlivS6ICCREye93oP57n\n2ZUvm42aKYZ188S9LLmAaXI9vJjXs+oG2G+OGf5P94jDsSvBnIxcU55ztTFogKug\n/N0TUTsda2ljpwhkxDkXYOc1hQTDJD2FIm+pwxwNyv35TYtcUxZhKvgM5AYyQHdG\neRsEPaaCsiGXftuXmdLzMkU=\n-----END CERTIFICATE-----\n";
String issuerURI = "did:example:123";
String issuerPublicKeyURI = "did:example:123#key-0";

Map<String, Object> didDocument = DIDDocumentUtil.generateDIDDocument(vcSignAlgorithm, certificateString, issuerURI, issuerPublicKeyURI);
assertEquals(didDocument.get("@context"), Collections.singletonList("https://www.w3.org/ns/did/v1"));
assertEquals(issuerURI, didDocument.get("id"));
assertEquals(Collections.singletonList(issuerPublicKeyURI), didDocument.get("authentication"));
assertEquals(Collections.singletonList(issuerPublicKeyURI), didDocument.get("assertionMethod"));

Map<String,Object> verificationMethod = ((List<Map<String,Object>>)didDocument.get("verificationMethod")).get(0);
assertEquals(((Map<String,String>)verificationMethod.get("publicKeyJwk")).get("kty"), "EC");
assertEquals(((Map<String,String>)verificationMethod.get("publicKeyJwk")).get("crv"), "secp256k1");
assertEquals(((Map<String,String>)verificationMethod.get("publicKeyJwk")).get("x"), "STRMr8BN3ToqGYWQExEm5-mjyiSqq9iGs600-4UMiZY");
assertEquals(((Map<String,String>)verificationMethod.get("publicKeyJwk")).get("y"), "wMC2jyYYA1UPz5TjeHRkSAZV6y6_C5oyCPsudWtFQPM");
assertEquals(verificationMethod.get("controller"), issuerURI);
assertEquals(verificationMethod.get("id"), issuerPublicKeyURI);
assertEquals(verificationMethod.get("type"), "EcdsaSecp256k1VerificationKey2019");
assertEquals(verificationMethod.get("@context"), "https://w3id.org/security/v1");
}

@Test
void testGenerateDIDDocumentUnsupportedAlgorithm() {
String vcSignAlgorithm = "UnsupportedAlgorithm";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mosip.certify.key-values={\
'proof_types_supported': {'jwt'},\
'credential_definition': {\
'type': {'VerifiableCredential','InsuranceCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down Expand Up @@ -75,6 +76,7 @@ mosip.certify.key-values={\
'proof_types_supported': {'jwt'},\
'credential_definition': {\
'type': {'VerifiableCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down Expand Up @@ -109,6 +111,7 @@ mosip.certify.key-values={\
'proof_types_supported': {'jwt': {'proof_signing_alg_values_supported': {'RS256', 'PS256'}}},\
'credential_definition': {\
'type': {'VerifiableCredential','InsuranceCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down Expand Up @@ -136,6 +139,7 @@ mosip.certify.key-values={\
'proof_types_supported': {'jwt': {'proof_signing_alg_values_supported': {'RS256', 'ES256'}}},\
'credential_definition': {\
'type': {'VerifiableCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down Expand Up @@ -169,6 +173,7 @@ mosip.certify.key-values={\
'proof_types_supported': {'jwt': {'proof_signing_alg_values_supported': {'RS256', 'PS256'}}},\
'credential_definition': {\
'type': {'VerifiableCredential','InsuranceCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down Expand Up @@ -197,6 +202,7 @@ mosip.certify.key-values={\
'proof_types_supported': {'jwt': {'proof_signing_alg_values_supported': {'RS256', 'ES256'}}},\
'credential_definition': {\
'type': {'VerifiableCredential'},\
'context': {'https://www.w3.org/2018/credentials/v1'},\
'credentialSubject': {\
'fullName': {'display': {{'name': 'Name','locale': 'en'}}}, \
'mobile': {'display': {{'name': 'Phone Number','locale': 'en'}}},\
Expand Down
2 changes: 1 addition & 1 deletion certify-service/src/test/resources/data.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@

MERGE INTO KEY_POLICY_DEF (APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) KEY(APP_ID) VALUES ('ROOT', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_SERVICE', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_PARTNER', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_VC_SIGN_RSA', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_VC_SIGN_ED25519', 1095, 50, 'NA', true, 'mosipadmin', now()), ('BASE', 730, 30, 'NA', true, 'mosipadmin', now());
MERGE INTO KEY_POLICY_DEF (APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) KEY(APP_ID) VALUES ('ROOT', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_SERVICE', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_PARTNER', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_VC_SIGN_RSA', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_VC_SIGN_ED25519', 1095, 50, 'NA', true, 'mosipadmin', now()), ('BASE', 730, 30, 'NA', true, 'mosipadmin', now()), ('CERTIFY_VC_SIGN_EC_K1', 1095, 50, 'NA', true, 'mosipadmin', now()), ('CERTIFY_VC_SIGN_EC_R1', 1095, 50, 'NA', true, 'mosipadmin', now());
2 changes: 2 additions & 0 deletions db_scripts/mosip_certify/dml/certify-key_policy_def.csv
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ CERTIFY_PARTNER,1095,60,NA,TRUE,mosipadmin,now()
CERTIFY_VC_SIGN_RSA,1095,60,NA,TRUE,mosipadmin,now()
BASE,730,60,NA,TRUE,mosipadmin,now()
CERTIFY_VC_SIGN_ED25519,1095,60,NA,TRUE,mosipadmin,now()
CERTIFY_VC_SIGN_EC_K1,1095,60,NA,TRUE,mosipadmin,now()
CERTIFY_VC_SIGN_EC_R1,1095,60,NA,TRUE,mosipadmin,now()
Loading

0 comments on commit 24aa28d

Please sign in to comment.