From 4e1d0fa078e88a56e56426b225d2f6179b26ab5b Mon Sep 17 00:00:00 2001 From: Yaroslav Grishajev Date: Mon, 13 May 2024 21:26:45 +0200 Subject: [PATCH] feat(web): integrate cert management from akashjs refs akash-network/akashjs#76 --- deploy-web/package-lock.json | 16 +-- deploy-web/package.json | 3 +- .../new-deployment/ManifestEdit.tsx | 15 +-- .../src/components/sdl/RentGpusForm.tsx | 9 +- .../CertificateProviderContext.tsx | 16 +-- deploy-web/src/utils/certificateUtils.ts | 103 ------------------ 6 files changed, 30 insertions(+), 132 deletions(-) delete mode 100644 deploy-web/src/utils/certificateUtils.ts diff --git a/deploy-web/package-lock.json b/deploy-web/package-lock.json index 4b734375c..255ffa539 100644 --- a/deploy-web/package-lock.json +++ b/deploy-web/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", - "@akashnetwork/akashjs": "^0.8.1", + "@akashnetwork/akashjs": "^0.9.0", "@auth0/nextjs-auth0": "^3.5.0", "@cosmjs/encoding": "^0.29.5", "@cosmjs/stargate": "^0.29.5", @@ -76,7 +76,6 @@ "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", "json2csv": "^5.0.7", - "jsrsasign": "^10.6.1", "lodash": "^4.17.21", "long": "^5.2.3", "lucide-react": "^0.292.0", @@ -179,9 +178,9 @@ } }, "node_modules/@akashnetwork/akashjs": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@akashnetwork/akashjs/-/akashjs-0.8.1.tgz", - "integrity": "sha512-zIIPt+GDY4xHsmQSaoJvyyAqNgDB/I4CyxEsn7vNUMSCGdxMaDOqJgKX0VnXYvrEWFfG5QM+T6O7UQ4Dr7ZqbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@akashnetwork/akashjs/-/akashjs-0.9.1.tgz", + "integrity": "sha512-zgm56eF9ADZmOp5MIET5JOhfR5edKAD2K7wcYXnzlxD8jGTJPZMwn9TisPXTz6INVQjr0b2tvU49KnG64pv4fg==", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", "@cosmjs/launchpad": "^0.27.0", @@ -195,6 +194,7 @@ "cosmwasm": "^1.1.1", "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", + "jsrsasign": "^11.1.0", "keytar": "^7.7.0", "node-fetch": "2", "pkijs": "^3.0.0", @@ -19523,9 +19523,9 @@ } }, "node_modules/jsrsasign": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-10.6.1.tgz", - "integrity": "sha512-emiQ05haY9CRj1Ho/LiuCqr/+8RgJuWdiHYNglIg2Qjfz0n+pnUq9I2QHplXuOMO2EnAW1oCGC1++aU5VoWSlw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-11.1.0.tgz", + "integrity": "sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==", "funding": { "url": "https://github.com/kjur/jsrsasign#donations" } diff --git a/deploy-web/package.json b/deploy-web/package.json index 001f21dad..d55f514c3 100644 --- a/deploy-web/package.json +++ b/deploy-web/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@akashnetwork/akash-api": "^1.3.0", - "@akashnetwork/akashjs": "^0.8.1", + "@akashnetwork/akashjs": "^0.9.0", "@auth0/nextjs-auth0": "^3.5.0", "@cosmjs/encoding": "^0.29.5", "@cosmjs/stargate": "^0.29.5", @@ -81,7 +81,6 @@ "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", "json2csv": "^5.0.7", - "jsrsasign": "^10.6.1", "lodash": "^4.17.21", "long": "^5.2.3", "lucide-react": "^0.292.0", diff --git a/deploy-web/src/components/new-deployment/ManifestEdit.tsx b/deploy-web/src/components/new-deployment/ManifestEdit.tsx index 2aa48f707..be446db1d 100644 --- a/deploy-web/src/components/new-deployment/ManifestEdit.tsx +++ b/deploy-web/src/components/new-deployment/ManifestEdit.tsx @@ -1,8 +1,14 @@ "use client"; import { useState, useEffect, Dispatch, useRef } from "react"; +import { useRouter } from "next/navigation"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme as useMuiTheme } from "@mui/material/styles"; +import { useAtom } from "jotai"; +import { event } from "nextjs-google-analytics"; +import { certificateManager } from "@akashnetwork/akashjs/build/certificates/certificate-manager"; + import { useSettings } from "../../context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; -import { useRouter } from "next/navigation"; import { Timer } from "@src/utils/timer"; import { defaultInitialDeposit, RouteStepKeys } from "@src/utils/constants"; import { deploymentData } from "@src/utils/deploymentData"; @@ -12,19 +18,15 @@ import ViewPanel from "../shared/ViewPanel"; import { TransactionMessageData } from "@src/utils/TransactionMessageData"; import { saveDeploymentManifestAndName } from "@src/utils/deploymentLocalDataUtils"; import { UrlService, domainName, handleDocClick } from "@src/utils/urlUtils"; -import { event } from "nextjs-google-analytics"; import { AnalyticsEvents } from "@src/utils/analytics"; import { PrerequisiteList } from "../shared/PrerequisiteList"; import { TemplateCreation } from "@src/types"; import { useCertificate } from "@src/context/CertificateProvider"; -import { generateCertificate } from "@src/utils/certificateUtils"; import { updateWallet } from "@src/utils/walletUtils"; import sdlStore from "@src/store/sdlStore"; -import { useAtom } from "jotai"; import { SdlBuilder, SdlBuilderRefType } from "./SdlBuilder"; import { validateDeploymentData } from "@src/utils/deploymentUtils"; import { useChainParam } from "@src/context/ChainParamProvider"; -import { useTheme as useMuiTheme } from "@mui/material/styles"; import { EncodeObject } from "@cosmjs/proto-signing"; import { Alert } from "../ui/alert"; import { Button } from "../ui/button"; @@ -35,7 +37,6 @@ import { InputWithIcon } from "../ui/input"; import { DeploymentDepositModal } from "../deployments/DeploymentDepositModal"; import { CustomNextSeo } from "../shared/CustomNextSeo"; import { cn } from "@src/utils/styleUtils"; -import useMediaQuery from "@mui/material/useMediaQuery"; type Props = { selectedTemplate: TemplateCreation; @@ -157,7 +158,7 @@ export const ManifestEdit: React.FunctionComponent = ({ editedManifest, s // Create a cert if the user doesn't have one if (!hasValidCert) { - const { crtpem, pubpem, encryptedKey } = generateCertificate(address); + const { cert: crtpem, publicKey: pubpem, privateKey: encryptedKey } = certificateManager.generatePEM(address); _crtpem = crtpem; _encryptedKey = encryptedKey; messages.push(TransactionMessageData.getCreateCertificateMsg(address, crtpem, pubpem)); diff --git a/deploy-web/src/components/sdl/RentGpusForm.tsx b/deploy-web/src/components/sdl/RentGpusForm.tsx index ffaaea976..ed233b7ed 100644 --- a/deploy-web/src/components/sdl/RentGpusForm.tsx +++ b/deploy-web/src/components/sdl/RentGpusForm.tsx @@ -4,8 +4,11 @@ import { useEffect, useRef, useState } from "react"; import { ApiTemplate, ProfileGpuModel, RentGpusFormValues, Service } from "@src/types"; import { defaultAnyRegion, defaultRentGpuService } from "@src/utils/sdl/data"; import { useRouter, useSearchParams } from "next/navigation"; -import sdlStore from "@src/store/sdlStore"; import { useAtom } from "jotai"; +import { EncodeObject } from "@cosmjs/proto-signing"; +import { certificateManager } from "@akashnetwork/akashjs/build/certificates/certificate-manager"; + +import sdlStore from "@src/store/sdlStore"; import { RegionSelect } from "./RegionSelect"; import { AdvancedConfig } from "./AdvancedConfig"; import { GpuFormControl } from "./GpuFormControl"; @@ -21,7 +24,6 @@ import { useCertificate } from "@src/context/CertificateProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; import { validateDeploymentData } from "@src/utils/deploymentUtils"; -import { generateCertificate } from "@src/utils/certificateUtils"; import { TransactionMessageData } from "@src/utils/TransactionMessageData"; import { updateWallet } from "@src/utils/walletUtils"; import { saveDeploymentManifestAndName } from "@src/utils/deploymentLocalDataUtils"; @@ -35,7 +37,6 @@ import { event } from "nextjs-google-analytics"; import { AnalyticsEvents } from "@src/utils/analytics"; import { useChainParam } from "@src/context/ChainParamProvider"; import { useGpuModels } from "@src/queries/useGpuQuery"; -import { EncodeObject } from "@cosmjs/proto-signing"; import { Alert } from "../ui/alert"; import { FormPaper } from "./FormPaper"; import { Button } from "../ui/button"; @@ -211,7 +212,7 @@ export const RentGpusForm: React.FunctionComponent = ({}) => { // Create a cert if the user doesn't have one if (!hasValidCert) { - const { crtpem, pubpem, encryptedKey } = generateCertificate(address); + const { cert: crtpem, publicKey: pubpem, privateKey: encryptedKey } = certificateManager.generatePEM(address); _crtpem = crtpem; _encryptedKey = encryptedKey; messages.push(TransactionMessageData.getCreateCertificateMsg(address, crtpem, pubpem)); diff --git a/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx b/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx index dae8a02af..4a0d4eb51 100644 --- a/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx +++ b/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx @@ -1,17 +1,18 @@ "use client"; import React, { useState, useCallback, useEffect } from "react"; import axios from "axios"; +import { useSnackbar } from "notistack"; +import { certificateManager } from "@akashnetwork/akashjs/build/certificates/certificate-manager"; + import { useSettings } from "../SettingsProvider"; import { networkVersion } from "@src/utils/constants"; -import { generateCertificate, getCertPem, openCert } from "@src/utils/certificateUtils"; +import { Snackbar } from "@src/components/shared/Snackbar"; import { getSelectedStorageWallet, getStorageWallets, updateWallet } from "@src/utils/walletUtils"; import { useWallet } from "../WalletProvider"; import { TransactionMessageData } from "@src/utils/TransactionMessageData"; import { event } from "nextjs-google-analytics"; import { AnalyticsEvents } from "@src/utils/analytics"; import { RestApiCertificatesResponseType } from "@src/types/certificate"; -import { useSnackbar } from "notistack"; -import { Snackbar } from "@src/components/shared/Snackbar"; export type LocalCert = { certPem: string; @@ -83,7 +84,7 @@ export const CertificateProvider = ({ children }) => { ); const certs = (response.data.certificates || []).map(cert => { const parsed = atob(cert.certificate.cert); - const pem = getCertPem(parsed); + const pem = certificateManager.parsePem(parsed); return { ...cert, @@ -159,8 +160,7 @@ export const CertificateProvider = ({ children }) => { for (let i = 0; i < wallets.length; i++) { const _wallet = wallets[i]; - const cert = await openCert(_wallet.cert as string, _wallet.certKey as string); - const _cert = { ...cert, address: _wallet.address }; + const _cert = { certPem: _wallet.cert, keyPem: _wallet.certKey, address: _wallet.address }; certs.push(_cert as LocalCert); @@ -178,7 +178,7 @@ export const CertificateProvider = ({ children }) => { async function createCertificate() { setIsCreatingCert(true); - const { crtpem, pubpem, encryptedKey } = generateCertificate(address); + const { cert: crtpem, publicKey: pubpem, privateKey: encryptedKey } = certificateManager.generatePEM(address); try { const message = TransactionMessageData.getCreateCertificateMsg(address, crtpem, pubpem); @@ -215,7 +215,7 @@ export const CertificateProvider = ({ children }) => { */ async function regenerateCertificate() { setIsCreatingCert(true); - const { crtpem, pubpem, encryptedKey } = generateCertificate(address); + const { cert: crtpem, publicKey: pubpem, privateKey: encryptedKey } = certificateManager.generatePEM(address); try { const revokeCertMsg = TransactionMessageData.getRevokeCertificateMsg(address, selectedCertificate?.serial as string); diff --git a/deploy-web/src/utils/certificateUtils.ts b/deploy-web/src/utils/certificateUtils.ts deleted file mode 100644 index 5b4b45db7..000000000 --- a/deploy-web/src/utils/certificateUtils.ts +++ /dev/null @@ -1,103 +0,0 @@ -import rs from "jsrsasign"; - -export async function openCert(certPem: string, encryptedKeyPem: string) { - if (!certPem || !encryptedKeyPem) return null; - - const key = rs.KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(encryptedKeyPem); - - return { - certPem, - keyPem: rs.KEYUTIL.getPEM(key, "PKCS8PRV") - }; -} - -export const getCertPem = certKey => { - var c = new rs.X509(); - c.readCertPEM(certKey); - var hSerial = c.getSerialNumberHex(); // '009e755e" hexadecimal string - var sIssuer = c.getIssuerString(); // '/C=US/O=z2' - var sSubject = c.getSubjectString(); // '/C=US/O=z2' - var sNotBefore = c.getNotBefore(); // '100513235959Z' - var sNotAfter = c.getNotAfter(); // '200513235959Z' - - return { - hSerial, - sIssuer, - sSubject, - sNotBefore, - sNotAfter, - issuedOn: strToDate(sNotBefore), - expiresOn: strToDate(sNotAfter) - }; -}; - -export const generateCertificate = (address: string) => { - const notBefore = new Date(); - let notAfter = new Date(); - notAfter.setFullYear(notBefore.getFullYear() + 1); - - const notBeforeStr = dateToStr(notBefore); - const notAfterStr = dateToStr(notAfter); - - // STEP1. generate a key pair - const kp = rs.KEYUTIL.generateKeypair("EC", "secp256r1"); - const prv = kp.prvKeyObj; - const pub = kp.pubKeyObj; - - const encryptedKey = rs.KEYUTIL.getPEM(prv, "PKCS8PRV") as string; - - const pubpem = rs.KEYUTIL.getPEM(pub, "PKCS8PUB").replaceAll("PUBLIC KEY", "EC PUBLIC KEY") as string; - - // STEP2. specify certificate parameters - const cert = new rs.KJUR.asn1.x509.Certificate({ - version: 3, - serial: { int: Math.floor(new Date().getTime() * 1000) }, - issuer: { str: "/CN=" + address }, - notbefore: notBeforeStr, - notafter: notAfterStr, - subject: { str: "/CN=" + address }, - //subjectAltName: {array: [{oid: "2.23.133.2.6", value: "v0.0.1"}]}, - sbjpubkey: pub, // can specify public key object or PEM string - ext: [ - { extname: "keyUsage", critical: true, names: ["keyEncipherment", "dataEncipherment"] }, - { - extname: "extKeyUsage", - array: [{ name: "clientAuth" }] - }, - { extname: "basicConstraints", cA: true, critical: true } - ], - sigalg: "SHA256withECDSA", - cakey: prv // can specify private key object or PEM string - }); - - const crtpem = cert.getPEM() as string; - - return { cert, crtpem, pubpem, encryptedKey }; -}; - -function dateToStr(date) { - const year = date.getUTCFullYear().toString().substring(2).padStart(2, "0"); - const month = (date.getUTCMonth() + 1).toString().padStart(2, "0"); - const day = date.getUTCDate().toString().padStart(2, "0"); - const hours = date.getUTCHours().toString().padStart(2, "0"); - const minutes = date.getUTCMinutes().toString().padStart(2, "0"); - const secs = date.getUTCSeconds().toString().padStart(2, "0"); - - return `${year}${month}${day}${hours}${minutes}${secs}Z`; -} - -/** - * 230518223318Z into Date - * @param {*} str - * @returns Date - */ -function strToDate(str) { - const year = parseInt(`20${str.substring(0, 2)}`); - const month = parseInt(str.substring(2, 4)) - 1; - const day = parseInt(str.substring(4, 6)); - const hours = parseInt(str.substring(6, 8)); - const minutes = parseInt(str.substring(8, 10)); - const secs = parseInt(str.substring(10, 12)); - - return new Date(Date.UTC(year, month, day, hours, minutes, secs)); -}