Skip to content

Commit

Permalink
add(): list the key-pairs
Browse files Browse the repository at this point in the history
  • Loading branch information
ikosta committed Jul 15, 2024
1 parent 0b9a282 commit ffa9d15
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 4 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ npx cap sync

<docgen-index>

* [`list()`](#list)
* [`generateKey(...)`](#generatekey)
* [`loadKey(...)`](#loadkey)
* [`deleteKey(...)`](#deletekey)
Expand All @@ -53,6 +54,19 @@ npx cap sync
<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->

### list()

```typescript
list() => Promise<ListResponse>
```

Returns all key-pair tags that are available in the Secure Enclave (iOS) or StrongBox/TEE (Android).

**Returns:** <code>Promise&lt;<a href="#listresponse">ListResponse</a>&gt;</code>

--------------------


### generateKey(...)

```typescript
Expand Down Expand Up @@ -156,6 +170,13 @@ Only ECDSA is supported.
### Interfaces


#### ListResponse

| Prop | Type | Description |
| ---------- | --------------------- | ------------------ |
| **`list`** | <code>string[]</code> | The key-pair tags. |


#### GenerateKeyResponse

| Prop | Type | Description |
Expand Down
37 changes: 37 additions & 0 deletions android/src/main/java/de/perfood/plugins/cryptoapi/CryptoApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,46 @@
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CryptoApi {

public List<String> list() {
Log.i("CryptoApi.list", "null");

try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

ArrayList<String> list = new ArrayList();

for (String tag : Collections.list(keyStore.aliases())) {
if (keyStore.entryInstanceOf(tag, KeyStore.PrivateKeyEntry.class)) {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(tag, null);
if (privateKeyEntry.getPrivateKey().getAlgorithm() == KeyProperties.KEY_ALGORITHM_EC) {
list.add(tag);
}
}
}

return list;
} catch (Error e) {
return Collections.emptyList();
} catch (CertificateException e) {
return Collections.emptyList();
} catch (KeyStoreException e) {
return Collections.emptyList();
} catch (IOException e) {
return Collections.emptyList();
} catch (NoSuchAlgorithmException e) {
return Collections.emptyList();
} catch (UnrecoverableEntryException e) {
return Collections.emptyList();
}
}

public String generateKey(String tag) {
Log.i("CryptoApi.generateKey", tag);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
package de.perfood.plugins.cryptoapi;

import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.util.List;

@CapacitorPlugin(name = "CryptoApi")
public class CryptoApiPlugin extends Plugin {

private CryptoApi implementation = new CryptoApi();

@PluginMethod
public void list(PluginCall call) {
List<String> list = implementation.list();

JSObject ret = new JSObject();
if (list != null) {
ret.put("list", new JSArray(list));
}
call.resolve(ret);
}

@PluginMethod
public void generateKey(PluginCall call) {
String tag = call.getString("tag");
Expand Down
4 changes: 4 additions & 0 deletions example/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ <h1>capacitor-crypto-api example</h1>
private-key.
</p>

<h2>key-pairs</h2>
<button onclick="list()">load key-pairs</button>
<code id="list"></code>

<h2>key-pair</h2>

<h3>tag</h3>
Expand Down
9 changes: 9 additions & 0 deletions example/src/js/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import { CryptoApi } from '@perfood/capacitor-crypto-api';

const API_URL = 'https://localhost:3001';

/**
* Loads the key-pair tags.
*/
window.list = async () => {
console.log('list');
const list = await CryptoApi.list();
document.getElementById('list').textContent = JSON.stringify(list.list);
};

/**
* Creates a key pair for the tag.
*/
Expand Down
31 changes: 31 additions & 0 deletions ios/Sources/CryptoApiPlugin/CryptoApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@ import CryptoKit
])
}

@objc public func list() -> [String] {
print("CryptoApi.list")

var list = [String]()

let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecReturnAttributes as String: true,
kSecMatchLimit as String: kSecMatchLimitAll
]

var result: AnyObject?

let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}

if lastResultCode == noErr {
let array = result as? [[String: Any]]

for item in array! {
if let atag = item[kSecAttrApplicationTag as String] as? Data,
let tag = String(data: atag, encoding: .utf8) {
list.append(tag)
}
}
}

return list
}

@objc public func generateKey(_ tag: String) -> String? {
print("CryptoApi.generateKey", tag)

Expand Down
9 changes: 9 additions & 0 deletions ios/Sources/CryptoApiPlugin/CryptoApiPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class CryptoApiPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "CryptoApiPlugin"
public let jsName = "CryptoApi"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "list", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "generateKey", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "loadKey", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "deleteKey", returnType: CAPPluginReturnPromise),
Expand All @@ -18,6 +19,14 @@ public class CryptoApiPlugin: CAPPlugin, CAPBridgedPlugin {
]
private let implementation = CryptoApi()

@objc func list(_ call: CAPPluginCall) {
let list = implementation.list()

call.resolve([
"list": list
])
}

@objc func generateKey(_ call: CAPPluginCall) {
let tag = call.getString("tag") ?? ""

Expand Down
12 changes: 12 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export const CRYPTO_API_ECDSA_SIGN_ALGORITHM = {
hash: { name: 'SHA-256' },
};

export interface ListResponse {
/**
* The key-pair tags.
*/
list: string[];
}

export interface GenerateKeyOptions {
/**
* The key-pair tag.
Expand Down Expand Up @@ -88,6 +95,11 @@ export interface VerifyResponse {
}

export interface CryptoApiPlugin {
/**
* Returns all key-pair tags that are available in the Secure Enclave (iOS) or StrongBox/TEE (Android).
*/
list(): Promise<ListResponse>;

/**
* Generates a key-pair in the Secure Enclave (iOS) or StrongBox/TEE (Android),
* tags it for alter referencing and returns the public-key only,
Expand Down
24 changes: 20 additions & 4 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
DeleteKeyOptions,
GenerateKeyOptions,
GenerateKeyResponse,
ListResponse,
LoadKeyOptions,
LoadKeyResponse,
SignOptions,
Expand All @@ -23,7 +24,19 @@ import {
p1363ToDer,
} from './utils';

const STORAGE_PREFIX = 'CryptoApiWeb:';

export class CryptoApiWeb extends WebPlugin implements CryptoApiPlugin {
async list(): Promise<ListResponse> {
console.log('CryptoApi.list');

return {
list: Object.keys(localStorage)
.filter(key => key.startsWith(STORAGE_PREFIX))
.map(key => key.replace(STORAGE_PREFIX, '')),
};
}

async generateKey(options: GenerateKeyOptions): Promise<GenerateKeyResponse> {
console.log('CryptoApi.generateKey', options);

Expand Down Expand Up @@ -62,7 +75,10 @@ export class CryptoApiWeb extends WebPlugin implements CryptoApiPlugin {
publicKey: publicKeyBase64,
};

localStorage.setItem(options.tag, JSON.stringify(keyPair));
localStorage.setItem(
`${STORAGE_PREFIX}${options.tag}`,
JSON.stringify(keyPair),
);

return {
publicKey: keyPair.publicKey,
Expand All @@ -72,7 +88,7 @@ export class CryptoApiWeb extends WebPlugin implements CryptoApiPlugin {
async loadKey(options: LoadKeyOptions): Promise<LoadKeyResponse> {
console.log('CryptoApi.loadKey', options);

const item = localStorage.getItem(options.tag);
const item = localStorage.getItem(`${STORAGE_PREFIX}${options.tag}`);
if (!item) {
return {};
}
Expand All @@ -90,7 +106,7 @@ export class CryptoApiWeb extends WebPlugin implements CryptoApiPlugin {
async deleteKey(options: DeleteKeyOptions): Promise<void> {
console.log('CryptoApi.deleteKey', options);

localStorage.removeItem(options.tag);
localStorage.removeItem(`${STORAGE_PREFIX}${options.tag}`);
}

async sign(options: SignOptions): Promise<SignResponse> {
Expand All @@ -102,7 +118,7 @@ export class CryptoApiWeb extends WebPlugin implements CryptoApiPlugin {
);
}

const item = localStorage.getItem(options.tag);
const item = localStorage.getItem(`${STORAGE_PREFIX}${options.tag}`);
if (!item) {
throw new Error('Key not found');
}
Expand Down

0 comments on commit ffa9d15

Please sign in to comment.