-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: credo callback crypto implementation
Signed-off-by: Berend Sliedrecht <[email protected]>
- Loading branch information
Berend Sliedrecht
committed
Jun 26, 2024
1 parent
e71679b
commit a396d36
Showing
13 changed files
with
11,592 additions
and
2,517 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
Large diffs are not rendered by default.
Oops, something went wrong.
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,14 @@ | ||
import * as core from 'webcrypto-core' | ||
import type { KeyAlgorithm, KeyType, KeyUsage } from './types' | ||
|
||
export class CallbackCryptoKey<T> extends core.CryptoKey { | ||
public constructor( | ||
public key: T, | ||
public override algorithm: KeyAlgorithm, | ||
public override extractable: boolean, | ||
public override type: KeyType, | ||
public override usages: Array<KeyUsage> | ||
) { | ||
super() | ||
} | ||
} |
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,115 @@ | ||
import { type AgentContext, Buffer, type JwkJson, Key, KeyType, getJwkFromJson, getJwkFromKey } from '@credo-ts/core' | ||
import { AsnConvert, AsnParser } from '@peculiar/asn1-schema' | ||
import { type AlgorithmIdentifier, SubjectPublicKeyInfo } from '@peculiar/asn1-x509' | ||
import { CallbackCryptoKey } from './CallbackKey' | ||
import type { CryptoCallback } from './CryptoCallback' | ||
import type { | ||
JsonWebKey, | ||
KeyAlgorithm, | ||
KeyFormat, | ||
KeyImportParams, | ||
KeySignParams, | ||
KeyUsage, | ||
KeyVerifyParams, | ||
} from './types' | ||
import { ecdsaWithSHA256 } from '@peculiar/asn1-ecc' | ||
|
||
export class CredoCryptoCallback implements CryptoCallback<Key> { | ||
public constructor(private agentContext: AgentContext) {} | ||
|
||
public async sign(key: CallbackCryptoKey<Key>, message: Uint8Array, _algorithm: KeySignParams): Promise<Uint8Array> { | ||
const signature = await this.agentContext.wallet.sign({ | ||
key: key.key, | ||
data: Buffer.from(message), | ||
}) | ||
|
||
return signature | ||
} | ||
|
||
public async verify( | ||
key: CallbackCryptoKey<Key>, | ||
_algorithm: KeyVerifyParams, | ||
message: Uint8Array, | ||
signature: Uint8Array | ||
): Promise<boolean> { | ||
const isValidSignature = await this.agentContext.wallet.verify({ | ||
key: key.key, | ||
signature: Buffer.from(signature), | ||
data: Buffer.from(message), | ||
}) | ||
|
||
return isValidSignature | ||
} | ||
|
||
public async generate(algorithm: KeyAlgorithm): Promise<Key> { | ||
const keyType = cryptoKeyAlgorithmToCredoKeyType(algorithm) | ||
|
||
const key = await this.agentContext.wallet.createKey({ | ||
keyType, | ||
}) | ||
|
||
return key | ||
} | ||
|
||
public async importKey( | ||
format: KeyFormat, | ||
keyData: Uint8Array | JsonWebKey, | ||
algorithm: KeyImportParams, | ||
extractable: boolean, | ||
keyUsages: Array<KeyUsage> | ||
): Promise<CallbackCryptoKey<Key>> { | ||
if (format === 'jwk' && keyData instanceof Uint8Array) { | ||
throw new Error('JWK format is only allowed with a jwk as key data') | ||
} | ||
|
||
if (format !== 'jwk' && !(keyData instanceof Uint8Array)) { | ||
throw new Error('non-jwk formats are only allowed with a uint8array as key data') | ||
} | ||
|
||
switch (format.toLowerCase()) { | ||
case 'jwk': { | ||
const jwk = getJwkFromJson(keyData as unknown as JwkJson) | ||
const publicKey = Key.fromPublicKey(jwk.publicKey, jwk.keyType) | ||
return new CallbackCryptoKey(publicKey, algorithm, extractable, 'public', keyUsages) | ||
} | ||
case 'spki': { | ||
const subjectPublicKey = AsnParser.parse(keyData as Uint8Array, SubjectPublicKeyInfo) | ||
|
||
const key = new Uint8Array(subjectPublicKey.subjectPublicKey) | ||
|
||
const keyType = spkiAlgorithmIntoCredoKeyType(subjectPublicKey.algorithm) | ||
|
||
return new CallbackCryptoKey(Key.fromPublicKey(key, keyType), algorithm, extractable, 'public', keyUsages) | ||
} | ||
default: | ||
throw new Error(`Unsupported export format: ${format}`) | ||
} | ||
} | ||
|
||
public async exportKey(format: KeyFormat, key: CallbackCryptoKey<Key>): Promise<Uint8Array | JsonWebKey> { | ||
switch (format.toLowerCase()) { | ||
case 'jwk': { | ||
const jwk = getJwkFromKey(key.key) | ||
return jwk.toJson() as unknown as JsonWebKey | ||
} | ||
case 'spki': { | ||
const publicKeyInfo = new SubjectPublicKeyInfo({ | ||
algorithm: ecdsaWithSHA256, | ||
subjectPublicKey: key.key.publicKey.buffer, | ||
}) | ||
|
||
const derEncoded = AsnConvert.serialize(publicKeyInfo) | ||
return new Uint8Array(derEncoded) | ||
} | ||
|
||
default: | ||
throw new Error(`Unsupported export format: ${format}`) | ||
} | ||
} | ||
} | ||
|
||
// TODO: proper conversion | ||
const cryptoKeyAlgorithmToCredoKeyType = (_algorithm: KeyAlgorithm): KeyType => KeyType.P256 | ||
|
||
// TODO: proper conversion | ||
const spkiAlgorithmIntoCredoKeyType = (_algorithm: AlgorithmIdentifier): KeyType => KeyType.P256 |
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,31 @@ | ||
import * as core from 'webcrypto-core' | ||
import { askarGetRandomValues } from './askar' | ||
import { EcdsaCallbackProvider } from './EcdsaCallbackProvider' | ||
import type { CryptoCallback } from './CryptoCallback' | ||
|
||
class Subtle<T> extends core.SubtleCrypto { | ||
public constructor(callbacks: CryptoCallback<T>) { | ||
super() | ||
|
||
this.providers.set(new EcdsaCallbackProvider(callbacks)) | ||
} | ||
} | ||
|
||
export class Crypto<T> extends core.Crypto { | ||
public subtle: Subtle<T> | ||
|
||
public getRandomValues<T extends ArrayBufferView | null>(array: T): T { | ||
if (!ArrayBuffer.isView(array)) { | ||
throw new TypeError('Input is not an array buffer view') | ||
} | ||
const buffer = new Uint8Array(array.buffer, array.byteOffset, array.byteLength) | ||
askarGetRandomValues(buffer) | ||
return array | ||
} | ||
|
||
public constructor(callbacks: CryptoCallback<T>) { | ||
super() | ||
|
||
this.subtle = new Subtle(callbacks) | ||
} | ||
} |
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 type { CallbackCryptoKey } from './CallbackKey' | ||
import type { JsonWebKey, KeyAlgorithm, KeyFormat, KeyImportParams, KeySignParams, KeyUsage } from './types' | ||
|
||
export interface CryptoCallback<T> { | ||
sign: (key: CallbackCryptoKey<T>, message: Uint8Array, algorithm: KeySignParams) => Promise<Uint8Array> | ||
generate: (algorithm: KeyAlgorithm) => Promise<T> | ||
importKey: ( | ||
format: KeyFormat, | ||
keyData: Uint8Array | JsonWebKey, | ||
algorithm: KeyImportParams, | ||
extractable: boolean, | ||
keyUsages: Array<KeyUsage> | ||
) => Promise<CallbackCryptoKey<T>> | ||
exportKey: (format: KeyFormat, key: CallbackCryptoKey<T>) => Promise<JsonWebKey | Uint8Array> | ||
verify: ( | ||
key: CallbackCryptoKey<T>, | ||
algorithm: KeySignParams, | ||
message: Uint8Array, | ||
signature: Uint8Array | ||
) => Promise<boolean> | ||
} |
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,64 @@ | ||
import * as core from 'webcrypto-core' | ||
import { CallbackCryptoKey } from './CallbackKey' | ||
import type { CryptoCallback } from './CryptoCallback' | ||
import type { | ||
CallbackCryptoKeyPair, | ||
EcKeyGenParams, | ||
EcKeyImportParams, | ||
EcdsaParams, | ||
JsonWebKey, | ||
KeyFormat, | ||
KeyUsage, | ||
} from './types' | ||
|
||
export class EcdsaCallbackProvider<T> extends core.EcdsaProvider { | ||
public constructor(private callbacks: CryptoCallback<T>) { | ||
super() | ||
} | ||
|
||
public async onSign(algorithm: EcdsaParams, key: CallbackCryptoKey<T>, data: ArrayBuffer): Promise<ArrayBuffer> { | ||
return this.callbacks.sign(key, new Uint8Array(data), algorithm) | ||
} | ||
|
||
public async onVerify( | ||
algorithm: EcdsaParams, | ||
key: CallbackCryptoKey<T>, | ||
signature: ArrayBuffer, | ||
data: ArrayBuffer | ||
): Promise<boolean> { | ||
return this.callbacks.verify(key, algorithm, new Uint8Array(data), new Uint8Array(signature)) | ||
} | ||
|
||
public async onGenerateKey( | ||
algorithm: EcKeyGenParams, | ||
extractable: boolean, | ||
keyUsages: KeyUsage[] | ||
): Promise<CallbackCryptoKeyPair<T>> { | ||
const key: T = await this.callbacks.generate(algorithm) | ||
|
||
return { | ||
publicKey: new CallbackCryptoKey(key, algorithm, extractable, 'public', keyUsages), | ||
privateKey: new CallbackCryptoKey(key, algorithm, extractable, 'private', keyUsages), | ||
} | ||
} | ||
|
||
public async onExportKey(format: KeyFormat, key: CallbackCryptoKey<T>): Promise<JsonWebKey | ArrayBuffer> { | ||
return this.callbacks.exportKey(format, key) | ||
} | ||
|
||
public async onImportKey( | ||
format: KeyFormat, | ||
keyData: JsonWebKey | ArrayBuffer, | ||
algorithm: EcKeyImportParams, | ||
extractable: boolean, | ||
keyUsages: KeyUsage[] | ||
): Promise<CallbackCryptoKey<T>> { | ||
return this.callbacks.importKey( | ||
format, | ||
ArrayBuffer.isView(keyData) ? new Uint8Array(keyData as ArrayBuffer) : (keyData as JsonWebKey), | ||
algorithm, | ||
extractable, | ||
keyUsages | ||
) | ||
} | ||
} |
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 |
---|---|---|
@@ -1,30 +1,3 @@ | ||
import * as core from 'webcrypto-core' | ||
import { Ed25519Provider } from './Ed25519Provider' | ||
import { EcdsaProvider } from './EcdsaProvider' | ||
import { askarGetRandomValues } from './askar' | ||
import { Sha1Provider } from './Sha1Provider' | ||
|
||
class Subtle extends core.SubtleCrypto { | ||
public constructor() { | ||
super() | ||
|
||
this.providers.set(new EcdsaProvider()) | ||
this.providers.set(new Ed25519Provider()) | ||
this.providers.set(new Sha1Provider()) | ||
} | ||
} | ||
|
||
export class Crypto extends core.Crypto { | ||
public subtle = new Subtle() | ||
|
||
public getRandomValues<T extends ArrayBufferView | null>(array: T): T { | ||
if (!ArrayBuffer.isView(array)) { | ||
throw new TypeError('Input is not an array buffer view') | ||
} | ||
const buffer = new Uint8Array(array.buffer, array.byteOffset, array.byteLength) | ||
askarGetRandomValues(buffer) | ||
return array | ||
} | ||
} | ||
|
||
export * from './Crypto' | ||
export * from './types' | ||
export * from './CredoCryptoCallback' |
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.