-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for Ed25519 and X25519 algorithms
- Loading branch information
1 parent
965012d
commit a0cf289
Showing
10 changed files
with
504 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import crypto from "crypto"; | ||
import { AsnConvert } from "@peculiar/asn1-schema"; | ||
import { Convert } from "pvtsutils"; | ||
import * as core from "webcrypto-core"; | ||
import { Ed25519CryptoKey } from "./crypto_key"; | ||
import { Ed25519PrivateKey } from "./private_key"; | ||
import { Ed25519PublicKey } from "./public_key"; | ||
import { CryptoKey } from "../../keys"; | ||
|
||
export class Ed25519Crypto { | ||
public static privateKeyUsages: KeyUsage[] = ["sign", "deriveBits", "deriveKey"]; | ||
public static publicKeyUsages: KeyUsage[] = ["verify"]; | ||
|
||
public static async generateKey(algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> { | ||
const type = algorithm.name.toLowerCase() as "ed25519"; | ||
const keys = crypto.generateKeyPairSync(type, { | ||
publicKeyEncoding: { | ||
format: "pem", | ||
type: "spki", | ||
}, | ||
privateKeyEncoding: { | ||
format: "pem", | ||
type: "pkcs8", | ||
}, | ||
}); | ||
|
||
const keyAlg = { | ||
name: type === "ed25519" ? "Ed25519" : "X25519", | ||
}; | ||
const privateKeyUsages = keyUsages.filter((usage) => this.privateKeyUsages.includes(usage)); | ||
const publicKeyUsages = keyUsages.filter((usage) => this.publicKeyUsages.includes(usage)); | ||
return { | ||
privateKey: new Ed25519PrivateKey(keyAlg, extractable, privateKeyUsages, keys.privateKey), | ||
publicKey: new Ed25519PublicKey(keyAlg, true, publicKeyUsages, keys.publicKey), | ||
}; | ||
} | ||
|
||
public static async sign(algorithm: Algorithm, key: Ed25519PrivateKey, data: Uint8Array): Promise<ArrayBuffer> { | ||
const signature = crypto.sign(null, Buffer.from(data), key.data); | ||
|
||
return core.BufferSourceConverter.toArrayBuffer(signature); | ||
} | ||
|
||
public static async verify(algorithm: Algorithm, key: Ed25519PublicKey, signature: Uint8Array, data: Uint8Array): Promise<boolean> { | ||
return crypto.verify(null, Buffer.from(data), key.data, signature); | ||
} | ||
|
||
public static async exportKey(format: KeyFormat, key: Ed25519CryptoKey): Promise<JsonWebKey | ArrayBuffer> { | ||
switch (format) { | ||
case "jwk": | ||
return key.toJWK(); | ||
case "pkcs8": { | ||
return core.PemConverter.toArrayBuffer(key.data.toString()); | ||
} | ||
case "spki": { | ||
return core.PemConverter.toArrayBuffer(key.data.toString()); | ||
} | ||
case "raw": { | ||
const jwk = key.toJWK(); | ||
return Convert.FromBase64Url(jwk.x!); | ||
} | ||
default: | ||
return Promise.reject(new core.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'")); | ||
} | ||
} | ||
|
||
public static async importKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey> { | ||
switch (format) { | ||
case "jwk": { | ||
const jwk = keyData as JsonWebKey; | ||
if (jwk.d) { | ||
// private key | ||
const privateData = new core.asn1.EdPrivateKey(); | ||
privateData.value = core.BufferSourceConverter.toArrayBuffer(Buffer.from(jwk.d, "base64url")); | ||
const pkcs8 = new core.asn1.PrivateKeyInfo(); | ||
pkcs8.privateKeyAlgorithm.algorithm = algorithm.name.toLowerCase() === "ed25519" | ||
? core.asn1.idEd25519 | ||
: core.asn1.idX25519; | ||
pkcs8.privateKey = AsnConvert.serialize(privateData); | ||
const raw = AsnConvert.serialize(pkcs8); | ||
const pem = core.PemConverter.fromBufferSource(raw, "PRIVATE KEY"); | ||
return new Ed25519PrivateKey(algorithm, extractable, keyUsages, pem); | ||
} else if (jwk.x) { | ||
// public key | ||
const pubKey = crypto.createPublicKey({ | ||
format: "jwk", | ||
key: jwk as crypto.JsonWebKey, | ||
}); | ||
const pem = pubKey.export({ format: "pem", type: "spki" }) as string; | ||
return new Ed25519PublicKey(algorithm, extractable, keyUsages, pem); | ||
} else { | ||
throw new core.OperationError("keyData: Cannot import JWK. 'd' or 'x' must be presented"); | ||
} | ||
} | ||
case "pkcs8": { | ||
const pem = core.PemConverter.fromBufferSource(keyData as ArrayBuffer, "PRIVATE KEY"); | ||
return new Ed25519PrivateKey(algorithm, extractable, keyUsages, pem); | ||
} | ||
case "spki": { | ||
const pem = core.PemConverter.fromBufferSource(keyData as ArrayBuffer, "PUBLIC KEY"); | ||
return new Ed25519PublicKey(algorithm, extractable, keyUsages, pem); | ||
} | ||
case "raw": { | ||
const raw = keyData as ArrayBuffer; | ||
const key = crypto.createPublicKey({ | ||
format: "jwk", | ||
key: { | ||
kty: "OKP", | ||
crv: algorithm.name.toLowerCase() === "ed25519" ? "Ed25519" : "X25519", | ||
x: Convert.ToBase64Url(raw), | ||
}, | ||
}); | ||
const pem = key.export({ format: "pem", type: "spki" }) as string; | ||
return new Ed25519PublicKey(algorithm, extractable, keyUsages, pem); | ||
} | ||
default: | ||
return Promise.reject(new core.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'")); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { CryptoKey } from "../../keys"; | ||
|
||
export class Ed25519CryptoKey extends CryptoKey { | ||
|
||
constructor(algorithm: Algorithm, extractable: boolean, usages: KeyUsage[], data: string) { | ||
super(); | ||
this.algorithm = algorithm; | ||
this.extractable = extractable; | ||
this.usages = usages; | ||
this.data = Buffer.from(data); | ||
} | ||
|
||
public toJWK(): JsonWebKey { | ||
return { | ||
kty: "OKP", | ||
crv: this.algorithm.name, | ||
key_ops: this.usages, | ||
ext: this.extractable, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import * as core from "webcrypto-core"; | ||
import { Ed25519Crypto } from "./crypto"; | ||
import { Ed25519CryptoKey } from "./crypto_key"; | ||
import { Ed25519PrivateKey } from "./private_key"; | ||
import { Ed25519PublicKey } from "./public_key"; | ||
import { getCryptoKey, setCryptoKey } from "../storage"; | ||
|
||
export class Ed25519Provider extends core.Ed25519Provider { | ||
public override async onGenerateKey(algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> { | ||
const keys = await Ed25519Crypto.generateKey(algorithm, extractable, keyUsages); | ||
return { | ||
privateKey: setCryptoKey(keys.privateKey as Ed25519CryptoKey), | ||
publicKey: setCryptoKey(keys.publicKey as Ed25519CryptoKey), | ||
}; | ||
} | ||
|
||
override async onSign(algorithm: Algorithm, key: Ed25519PrivateKey, data: ArrayBuffer): Promise<ArrayBuffer> { | ||
const internalKey = getCryptoKey(key) as Ed25519PrivateKey; | ||
const signature = Ed25519Crypto.sign(algorithm, internalKey, new Uint8Array(data)); | ||
return signature; | ||
} | ||
|
||
override onVerify(algorithm: Algorithm, key: Ed25519PublicKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> { | ||
const internalKey = getCryptoKey(key) as Ed25519PublicKey; | ||
return Ed25519Crypto.verify(algorithm, internalKey, new Uint8Array(signature), new Uint8Array(data)); | ||
} | ||
|
||
override async onExportKey(format: KeyFormat, key: Ed25519CryptoKey): Promise<JsonWebKey | ArrayBuffer> { | ||
const internalKey = getCryptoKey(key) as Ed25519CryptoKey; | ||
return Ed25519Crypto.exportKey(format, internalKey); | ||
} | ||
|
||
override async onImportKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKey> { | ||
const internalKey = await Ed25519Crypto.importKey(format, keyData, algorithm, extractable, keyUsages); | ||
return setCryptoKey(internalKey); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from "./ed25519"; | ||
export * from "./x25519"; | ||
export * from "./private_key"; | ||
export * from "./public_key"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import crypto from "crypto"; | ||
import { AsnConvert } from "@peculiar/asn1-schema"; | ||
import * as core from "webcrypto-core"; | ||
import { Ed25519CryptoKey } from "./crypto_key"; | ||
|
||
export class Ed25519PrivateKey extends Ed25519CryptoKey { | ||
public override type = "private" as const; | ||
|
||
public override toJWK(): JsonWebKey { | ||
const pubJwk = crypto.createPublicKey({ | ||
key: this.data, | ||
format: "pem", | ||
}).export({ format: "jwk" }) as JsonWebKey; | ||
const raw = core.PemConverter.toUint8Array(this.data.toString()); | ||
const pkcs8 = AsnConvert.parse(raw, core.asn1.PrivateKeyInfo); | ||
const d = AsnConvert.parse(pkcs8.privateKey, core.asn1.EdPrivateKey).value; | ||
return { | ||
...super.toJWK(), | ||
...pubJwk, | ||
d: Buffer.from(new Uint8Array(d)).toString("base64url"), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import crypto from "crypto"; | ||
import { Ed25519CryptoKey } from "./crypto_key"; | ||
|
||
export class Ed25519PublicKey extends Ed25519CryptoKey { | ||
public override type = "public" as const; | ||
|
||
public override toJWK(): JsonWebKey { | ||
const jwk = crypto.createPublicKey({ | ||
key: this.data, | ||
format: "pem", | ||
}).export({ format: "jwk" }) as JsonWebKey; | ||
|
||
return { | ||
...super.toJWK(), | ||
...jwk, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import crypto from "crypto"; | ||
import * as core from "webcrypto-core"; | ||
import { Ed25519Crypto } from "./crypto"; | ||
import { Ed25519CryptoKey } from "./crypto_key"; | ||
import { CryptoKey } from "../../keys"; | ||
import { getCryptoKey, setCryptoKey } from "../storage"; | ||
|
||
export class X25519Provider extends core.X25519Provider { | ||
public override async onGenerateKey(algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> { | ||
const keys = await Ed25519Crypto.generateKey(algorithm, extractable, keyUsages); | ||
return { | ||
privateKey: setCryptoKey(keys.privateKey as Ed25519CryptoKey), | ||
publicKey: setCryptoKey(keys.publicKey as Ed25519CryptoKey), | ||
}; | ||
} | ||
|
||
public override async onDeriveBits(algorithm: EcdhKeyDeriveParams, baseKey: Ed25519CryptoKey, length: number): Promise<ArrayBuffer> { | ||
const internalBaseKey = getCryptoKey(baseKey); | ||
const internalPublicKey = getCryptoKey(algorithm.public); | ||
const publicKey = crypto.createPublicKey({ | ||
key: internalPublicKey.data.toString(), | ||
format: "pem", | ||
type: "spki", | ||
}); | ||
const privateKey = crypto.createPrivateKey({ | ||
key: internalBaseKey.data.toString(), | ||
format: "pem", | ||
type: "pkcs8", | ||
}); | ||
const bits = crypto.diffieHellman({ | ||
publicKey, | ||
privateKey, | ||
}); | ||
|
||
return new Uint8Array(bits).buffer.slice(0, length >> 3); | ||
} | ||
|
||
public override async onExportKey(format: KeyFormat, key: Ed25519CryptoKey): Promise<JsonWebKey | ArrayBuffer> { | ||
const internalKey = getCryptoKey(key); | ||
return Ed25519Crypto.exportKey(format, internalKey as Ed25519CryptoKey); | ||
} | ||
|
||
public override async onImportKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKey> { | ||
const key = await Ed25519Crypto.importKey(format, keyData, algorithm, extractable, keyUsages); | ||
return setCryptoKey(key); | ||
} | ||
|
||
override checkCryptoKey(key: CryptoKey, keyUsage?: KeyUsage | undefined): void { | ||
super.checkCryptoKey(key, keyUsage); | ||
if (!(getCryptoKey(key) instanceof Ed25519CryptoKey)) { | ||
throw new TypeError("key: Is not a Ed25519CryptoKey"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.