diff --git a/.example.env b/.example.env index 93da3f7..1c4a5b7 100644 --- a/.example.env +++ b/.example.env @@ -63,6 +63,7 @@ SECP256K1_KEY=/secp256k1 SECP256K1_SIGN_ETHEREUM_TRANSACTION=/secp256k1/sign/ethereum-tx SECP256K1_SIGN_LACCHAIN_TRANSACTION=/secp256k1/sign/lacchain-tx ED25519_CREATE_KEY=/ed25519 +P256_CREATE_KEY=/p256 # Did Registry CHAIN_ID = 0x9e55c diff --git a/.example.env.dev b/.example.env.dev index e66d225..5cc0baa 100644 --- a/.example.env.dev +++ b/.example.env.dev @@ -62,6 +62,7 @@ SECP256K1_KEY=/secp256k1 SECP256K1_SIGN_ETHEREUM_TRANSACTION=/secp256k1/sign/ethereum-tx SECP256K1_SIGN_LACCHAIN_TRANSACTION=/secp256k1/sign/lacchain-tx ED25519_CREATE_KEY=/ed25519 +P256_CREATE_KEY=/p256 # Did Registry diff --git a/CHANGELOG.md b/CHANGELOG.md index 683d471..a7bb81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.0.3 + +* Add support to associate a P-256 JWK to a DID ## 0.0.1 ### Additions and Improvements diff --git a/package.json b/package.json index 82c58bb..e3b77b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lacchain-identity", - "version": "0.0.2", + "version": "0.0.6", "description": "Rest api for lacchain identity manager", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -85,7 +85,7 @@ "helmet": "^5.0.2", "json-canonicalize": "^1.0.6", "jsonwebtoken": "^9.0.0", - "lacchain-key-manager": "^0.0.2", + "lacchain-key-manager": "^0.0.6", "morgan": "^1.10.0", "multer": "^1.4.4", "nodemailer": "^6.7.3", diff --git a/src/config/index.ts b/src/config/index.ts index db79185..b27996b 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -208,5 +208,6 @@ export const { LACCHAIN_IDENTITY_IS_DEPENDENT_SERVICE, SECP256K1_SIGN_ETHEREUM_TRANSACTION, SECP256K1_SIGN_LACCHAIN_TRANSACTION, - ED25519_CREATE_KEY + ED25519_CREATE_KEY, + P256_CREATE_KEY } = process.env; diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts index d85fd1b..335596d 100644 --- a/src/constants/errorMessages.ts +++ b/src/constants/errorMessages.ts @@ -26,7 +26,7 @@ export enum ErrorsMessages { INVALID_EXPIRATION_DAYS = 'Valid days must be greater than zero', INVALID_JWK_TYPE = 'Invalid Jwk type', // eslint-disable-next-line max-len - UNSUPPORTED_JWK_CREATION_FOR_TYPE = 'The type of jwk for creation is not supported yet, valid type: "secp256k1"', + UNSUPPORTED_JWK_CREATION_FOR_TYPE = 'The type of jwk for creation is not supported yet, valid type: "secp256k1" or "secp256r1"', INVALID_VM_RELATION_TYPE = 'Invalid verification method relation type', UNSUPPORTED_ATTRIBUTE_ENCODING_METHOD = 'Unsupported attribute encoding', INVALID_DELEGATE_TYPE = 'Invalid delegate type', @@ -46,7 +46,11 @@ export enum ErrorsMessages { UNEXPECTED_RESPONSE_IN_SUCCESSFUL_TRANSACTION_ERROR = 'Transaction was successfully completed but received an unexpected response', UNSUPPORTED_CHAIN_ID_IN_DID = 'Unsupported chainId was found in DID', UNSUPPORTED_DID_TYPE = 'Unsupported DID type', - UNSUPPORTED_DID_VERSION = 'Unsupported DID version' + UNSUPPORTED_DID_VERSION = 'Unsupported DID version', + // eslint-disable-next-line max-len + PUBLIC_KEY_COMPRESSED_FORMAT_ERROR = 'Unexpected public key format, expected compressed format', + // eslint-disable-next-line max-len + PUBLIC_KEY_UNCOMPRESSED_FORMAT_ERROR = 'Unexpected public key format, expected uncompressed format' } export const Errors = { diff --git a/src/dto/did-lac/attributeDTO.ts b/src/dto/did-lac/attributeDTO.ts index 709d06f..61db4a3 100644 --- a/src/dto/did-lac/attributeDTO.ts +++ b/src/dto/did-lac/attributeDTO.ts @@ -78,7 +78,7 @@ export class NewAttributeDTO { export class NewJwkAttributeDTO extends NewAttributeDTO { @IsString() - jwkType!: 'secp256k1'; + jwkType!: 'secp256k1' | 'secp256r1'; } export class RevokeAttributeDTO { diff --git a/src/interfaces/did-lacchain/did-lacchain.interface.ts b/src/interfaces/did-lacchain/did-lacchain.interface.ts index 5697933..59a7bce 100644 --- a/src/interfaces/did-lacchain/did-lacchain.interface.ts +++ b/src/interfaces/did-lacchain/did-lacchain.interface.ts @@ -19,7 +19,7 @@ export interface INewAttribute { } export interface INewJwkAttribute extends INewAttribute { - jwkType: 'secp256k1'; + jwkType: 'secp256k1' | 'secp256r1'; } export interface IAccountIdAttribute { diff --git a/src/interfaces/key/key.interface.ts b/src/interfaces/key/key.interface.ts index 35ec34f..3c452e3 100644 --- a/src/interfaces/key/key.interface.ts +++ b/src/interfaces/key/key.interface.ts @@ -4,4 +4,6 @@ export interface IECKey { address: string; publicKey: string; type: string; + x?: string; + y?: string; } diff --git a/src/services/did-lac/did.service.ts b/src/services/did-lac/did.service.ts index 8aad60e..05e09c8 100644 --- a/src/services/did-lac/did.service.ts +++ b/src/services/did-lac/did.service.ts @@ -159,31 +159,82 @@ export abstract class DidService implements DidLacService { } async addNewJwkAttribute(attribute: INewJwkAttribute): Promise { if (attribute.jwkType == 'secp256k1') { - const key = await this.keyManagerService.createSecp256k1Key(); - const x = Buffer.from(key.publicKey.replace('0x', ''), 'hex').toString( - 'base64url' - ); - const ecJwk: EcJwk = { - kty: 'EC', - x, - crv: 'secp256k1' - }; - const ecJwkAttribute: IJwkEcAttribute = { - did: attribute.did, - ecJwk, - validDays: attribute.validDays, - relation: attribute.relation - }; - const r: INewJwkAttributeCreationResponse = { - ...(await this.addEcJwkAttribute(ecJwkAttribute)), - jwk: ecJwk - }; - return r; + return this.addNewSecp256k1JwkAttribute(attribute); + } else if (attribute.jwkType == 'secp256r1') { + return this.addNewSecp256r1JwkAttribute(attribute); } const message = ErrorsMessages.UNSUPPORTED_JWK_CREATION_FOR_TYPE; this.log.info(message); throw new BadRequestError(message); } + + async addNewSecp256k1JwkAttribute(attribute: INewJwkAttribute): Promise { + const key = await this.keyManagerService.createSecp256k1Key(); + // invariant verification + const pubKey = key.publicKey.replace('0x', ''); + if (pubKey.length !== 66 || !pubKey.startsWith('02')) { + throw new BadRequestError( + ErrorsMessages.PUBLIC_KEY_COMPRESSED_FORMAT_ERROR + ); + } + const x = Buffer.from(key.publicKey.replace('0x02', ''), 'hex').toString( + 'base64url' + ); + const ecJwk: EcJwk = { + kty: 'EC', + x, + crv: 'secp256k1' + }; + const ecJwkAttribute: IJwkEcAttribute = { + did: attribute.did, + ecJwk, + validDays: attribute.validDays, + relation: attribute.relation + }; + const r: INewJwkAttributeCreationResponse = { + ...(await this.addEcJwkAttribute(ecJwkAttribute)), + jwk: ecJwk + }; + return r; + } + + async addNewSecp256r1JwkAttribute(attribute: INewJwkAttribute): Promise { + const key = await this.keyManagerService.createP256Key(); + // invariant verification + const pubKey = key.publicKey.replace('0x', ''); + if (pubKey.length !== 130 || !pubKey.startsWith('04')) { + throw new BadRequestError( + ErrorsMessages.PUBLIC_KEY_UNCOMPRESSED_FORMAT_ERROR + ); + } + const pubKeyNoPrefix = key.publicKey.replace('04', ''); + const x = Buffer.from(pubKeyNoPrefix.substring(0, 64), 'hex').toString( + 'base64url' + ); + const y = Buffer.from(pubKeyNoPrefix.substring(64, 128), 'hex').toString( + 'base64url' + ); + + const ecJwk: EcJwk = { + kty: 'EC', + x, + y, + crv: 'P-256' + }; + console.log(ecJwk); + const ecJwkAttribute: IJwkEcAttribute = { + did: attribute.did, + ecJwk, + validDays: attribute.validDays, + relation: attribute.relation + }; + const r: INewJwkAttributeCreationResponse = { + ...(await this.addEcJwkAttribute(ecJwkAttribute)), + jwk: ecJwk + }; + return r; + } + async addRsaJwkAttribute(jwkRsaAttribute: IJwkRsaAttribute): Promise { // TODO: validate RSA params const { kty } = jwkRsaAttribute.rsaJwk; diff --git a/src/services/external/key-manager.service.ts b/src/services/external/key-manager.service.ts index bfa4866..2fbc202 100644 --- a/src/services/external/key-manager.service.ts +++ b/src/services/external/key-manager.service.ts @@ -7,7 +7,8 @@ import { SECP256K1_SIGN_ETHEREUM_TRANSACTION, SECP256K1_SIGN_LACCHAIN_TRANSACTION, log4TSProvider, - ED25519_CREATE_KEY + ED25519_CREATE_KEY, + P256_CREATE_KEY } from '../../config'; import { IEthereumTransaction, @@ -24,6 +25,7 @@ import { IECKey } from 'src/interfaces/key/key.interface'; export class KeyManagerService { public createSecp256k1Key: () => Promise; public createEd25519Key: () => Promise; + public createP256Key: () => Promise; public signEthereumTransaction: ( ethereumTransaction: IEthereumTransaction ) => Promise; @@ -35,6 +37,7 @@ export class KeyManagerService { // eslint-disable-next-line max-len private secp256k1SignLacchainTransactionService: Secp256k1SignLacchainTransactionService | null; private generic25519Service: ECService | null; + private p256KeyService: ECService | null; log = log4TSProvider.getLogger('KeyManagerService'); constructor() { if (LACCHAIN_IDENTITY_IS_DEPENDENT_SERVICE !== 'true') { @@ -56,6 +59,10 @@ export class KeyManagerService { this.createEd25519Key = this.createEd25519KeyByLib; const SS = require('lacchain-key-manager').Generic25519DbService; this.generic25519Service = new SS('ed25519'); + + this.createP256Key = this.createP256KeyByLib; + const TT = require('lacchain-key-manager').P256DbService; + this.p256KeyService = new TT(); } else { this.log.info('Configuring key-manager external service connection'); this.secp256k1Service = null; @@ -71,6 +78,9 @@ export class KeyManagerService { this.generic25519Service = null; this.createEd25519Key = this.createEd25519KeyByExternalService; + + this.p256KeyService = null; + this.createP256Key = this.createP256KeyByExternalService; } } async createSecp256k1KeyByLib(): Promise { @@ -110,6 +120,25 @@ export class KeyManagerService { return (await result.json()) as IECKey; } + async createP256KeyByLib(): Promise { + return (await this.p256KeyService?.createKey()) as IECKey; + } + + async createP256KeyByExternalService(): Promise { + const result = await fetch(`${KEY_MANAGER_BASE_URL}${P256_CREATE_KEY}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + console.log('status', result.status); + if (result.status !== 200) { + console.log(await result.text()); + throw new InternalServerError(ErrorsMessages.CREATE_KEY_ERROR); + } + return (await result.json()) as IECKey; + } + async signEthereumTransactionByLib( ethereumTransaction: IEthereumTransaction ): Promise { diff --git a/yarn.lock b/yarn.lock index ed45d97..fe54e30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5402,10 +5402,10 @@ koa@^2.8.2: type-is "^1.6.16" vary "^1.1.2" -lacchain-key-manager@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.2.tgz#e9996ad66bdbf52e4db3148390a09dff630413b7" - integrity sha512-8qDeQUYHgZylcPQDkKc3N6eprVUlp0vCH+4GsH+iznZ6HlWhhdghMgEgYwSq1jlmM9L/jBos+m7m1iWBDWoQbg== +lacchain-key-manager@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.6.tgz#2d4c3bd175ab11332a7f4657d70438ee47bf7f18" + integrity sha512-RRuazKwiLniOuGE/PmriZqjXbOjBYIe9a1vxCCHeJGnVs6mVp3dD9MnDArszUd+xiJLL4L9CLa0hzTOYZjXOhA== dependencies: "@lacchain/gas-model-provider" "^1.0.1" DIDComm-js "git+https://github.com/decentralized-identity/DIDComm-js.git"