123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import { Injectable } from "@angular/core";
- import { TwitterApiProvider } from "../twitter-api/twitter-api";
- import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs";
- import { Storage } from "@ionic/storage";
- import NodeRSA from "node-rsa";
- declare var TextDecoder: any;
- declare var TextEncoder: any;
- @Injectable()
- export class CryptoProvider {
- ownUserId: string;
- IV_LENGTH = 12;
- HYBRID_OSN_AES_KEY = "Z1vxAULQnZdoWhJOvv+hWEvVpyUHzNjD/ichEE2c8i4=";
- constructor(
- private twitter: TwitterApiProvider,
- private ipfs: P2pStorageIpfsProvider,
- private storage: Storage
- ) {
- this.init();
- }
- private async init() {
- this.ownUserId = await this.storage.get("userId");
- }
- public async publishPublicKey(key: string) {
- let publicKeyHistory = await this.getKeyHistory(this.ownUserId);
- // Todo: avoid publishing the same public key twice - check if new key equals newest key in history
- // Add new key to history
- const newKey = {
- key: key,
- validFrom: Date.now()
- };
- if (publicKeyHistory) {
- publicKeyHistory["keys"].push(newKey);
- } else {
- publicKeyHistory = {
- keys: [newKey]
- };
- }
- // Ecnrypt key history
- const encryptedPublicKeyHistory = await this.aesEncrypt(
- JSON.stringify(publicKeyHistory)
- );
- // Publish updated key history...
- const res = await this.ipfs.storePublicKey(encryptedPublicKeyHistory);
- // tweet ipfs link
- const tweetResponse = await this.twitter.tweet(
- "ipfs://" + res["Hash"] + " #hybridOSN"
- );
- // ... and update description in user profile with tweet id
- this.twitter.updateProfileDescription(
- "tweet://" + tweetResponse["data"]["id_str"] + " #hybridOSN"
- );
- }
- private async getKeyHistory(userId: string) {
- // Get user description
- const userData = await this.twitter.fetchUser(userId);
- const profileDescription = userData["description"];
- // Get tweet with link to key history
- const tweetId = this.extractTweetId(profileDescription);
- if (tweetId.length === 0) {
- return null;
- }
- const tweetWithKeyHistoryLink = await this.twitter.fetchTweet(tweetId);
- // Extract link to public key
- const link = this.extractLinkFromDescription(
- tweetWithKeyHistoryLink["data"]["full_text"]
- );
- // Fetch public key history
- if (link.length) {
- const encryptedKeyHistory = await this.ipfs.fetchJson(link);
- // Decrypt key history
- const keyHistory = await this.aesDecrypt(encryptedKeyHistory.toString());
- return JSON.parse(keyHistory);
- } else {
- return null;
- }
- }
- private extractTweetId(text: string): string {
- for (let word of text.split(" ")) {
- if (this.isTweetId(word)) {
- return word.substr(8);
- }
- }
- return "";
- }
- private extractLinkFromDescription(text: string): string {
- for (let word of text.split(" ")) {
- if (this.isIpfsLink(word)) {
- return word.substr(7);
- }
- }
- return "";
- }
- private isIpfsLink(word: string): boolean {
- return /ipfs:\/\/Qm[a-zA-Z0-9]+/.test(word);
- }
- private isTweetId(word: string): boolean {
- return /tweet:\/\/[0-9]+/.test(word);
- }
- public async generateRsaKeys() {
- return await crypto.subtle.generateKey(
- {
- name: "RSA-OAEP",
- modulusLength: 1024,
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
- hash: { name: "SHA-256" }
- },
- true,
- ["encrypt", "decrypt"]
- );
- }
- public async extractPrivateKey(keys): Promise<string> {
- return btoa(
- String.fromCharCode.apply(
- null,
- new Uint8Array(await crypto.subtle.exportKey("pkcs8", keys.privateKey))
- )
- );
- }
- public async extractPublicKey(keys): Promise<string> {
- return btoa(
- String.fromCharCode.apply(
- null,
- new Uint8Array(await crypto.subtle.exportKey("spki", keys.publicKey))
- )
- );
- }
- public async isPublicKeyPublished(): Promise<boolean> {
- const publicKey = await this.storage.get("publicKey");
- const keyHistory = await this.getKeyHistory(this.ownUserId);
- if (keyHistory && publicKey.length) {
- const newestPublicKey = keyHistory["keys"].reverse()[0]["key"];
- return newestPublicKey === publicKey;
- } else {
- return false;
- }
- }
- public async isPrivateKeySet(): Promise<boolean> {
- const privateKey = await this.storage.get("privateKey");
- return privateKey;
- }
- public encrypt(plainText: string, privateKey: string) {
- const key = new NodeRSA(
- `-----BEGIN PRIVATE KEY-----${privateKey}-----END PRIVATE KEY-----`
- );
- return key.encryptPrivate(plainText, "base64");
- }
- public decrypt(encryptedMessage: string, publicKey: string): string {
- const key = new NodeRSA(
- `-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----`
- );
- return key.decryptPublic(encryptedMessage).toString();
- }
- public async fetchPublicKeyHistoryForUser(userId: string): Promise<object[]> {
- const keyHistory = await this.getKeyHistory(userId);
- return keyHistory["keys"].reverse();
- }
- private async aesEncrypt(plainText: string) {
- const utf8BytesOfPlainText = new TextEncoder().encode(plainText);
- const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
- const keyBytes = this.base64ToBytes(this.HYBRID_OSN_AES_KEY);
- const algorithm = { name: "AES-GCM", iv, length: 256 };
- return crypto.subtle
- .importKey("raw", keyBytes, algorithm, false, ["encrypt"])
- .then((cryptoKey: CryptoKey) => {
- return crypto.subtle.encrypt(
- algorithm,
- cryptoKey,
- utf8BytesOfPlainText
- );
- })
- .then((encData: ArrayBuffer) =>
- this.mergeBytes(iv, this.bufferToBytes(encData))
- )
- .then(this.bytesToBase64);
- }
- private aesDecrypt(encryptedMessage: string) {
- try {
- const ivWithEncryptedBytes = this.base64ToBytes(encryptedMessage);
- const { encryptedBytes, ivBytes } = this.splitIvAndEncrypted(
- ivWithEncryptedBytes
- );
- const keyBytes = this.base64ToBytes(this.HYBRID_OSN_AES_KEY);
- const algorithm = { name: "AES-GCM", iv: ivBytes, length: 256 };
- return crypto.subtle
- .importKey("raw", keyBytes, algorithm, false, ["decrypt"])
- .then((cryptoKey: CryptoKey) => {
- return crypto.subtle.decrypt(algorithm, cryptoKey, encryptedBytes);
- })
- .then((decryptedData: ArrayBuffer) => {
- return new TextDecoder().decode(decryptedData);
- });
- } catch (e) {
- return Promise.reject(e);
- }
- }
- private bufferToBytes(arrayBuffer: ArrayBuffer) {
- return new Uint8Array(arrayBuffer);
- }
- private bytesToBase64(bytes: Uint8Array): string {
- let binary = "";
- const len = bytes.length;
- for (let i = 0; i < len; i++) {
- binary += String.fromCharCode(bytes[i]);
- }
- return btoa(binary);
- }
- private base64ToBytes(base64: string): Uint8Array {
- const binaryString = atob(base64);
- const len = binaryString.length;
- const bytes = new Uint8Array(len);
- for (let i = 0; i < len; i++) {
- bytes[i] = binaryString.charCodeAt(i);
- }
- return bytes;
- }
- private mergeBytes(a: Uint8Array, b: Uint8Array) {
- const aLen = a.length;
- const bLen = b.length;
- const res = new Uint8Array(aLen + bLen);
- for (let i = 0; i < aLen; i++) {
- res[i] = a[i];
- }
- for (let i = 0; i < bLen; i++) {
- res[i + aLen] = b[i];
- }
- return res;
- }
- private splitIvAndEncrypted(ivWithEncryptedBytes: Uint8Array) {
- const dataLen = ivWithEncryptedBytes.length;
- const ivBytes = new Uint8Array(this.IV_LENGTH);
- const encryptedBytes = new Uint8Array(dataLen - this.IV_LENGTH);
- for (let i = 0; i < this.IV_LENGTH; i++) {
- ivBytes[i] = ivWithEncryptedBytes[i];
- }
- for (let i = this.IV_LENGTH; i < dataLen; i++) {
- encryptedBytes[i - this.IV_LENGTH] = ivWithEncryptedBytes[i];
- }
- return { ivBytes, encryptedBytes };
- }
- }
|