crypto.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import { Injectable } from "@angular/core";
  2. import { TwitterApiProvider } from "../twitter-api/twitter-api";
  3. import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs";
  4. import { P2pDatabaseGunProvider } from "../../providers/p2p-database-gun/p2p-database-gun";
  5. import { Storage } from "@ionic/storage";
  6. import NodeRSA from "node-rsa";
  7. import * as openpgp from 'openpgp';
  8. declare var TextDecoder: any;
  9. declare var TextEncoder: any;
  10. @Injectable()
  11. export class CryptoProvider {
  12. ownUserId: string;
  13. IV_LENGTH = 12;
  14. HYBRID_OSN_AES_KEY = "Z1vxAULQnZdoWhJOvv+hWEvVpyUHzNjD/ichEE2c8i4=";
  15. email: string;
  16. constructor(
  17. private twitter: TwitterApiProvider,
  18. private ipfs: P2pStorageIpfsProvider,
  19. private storage: Storage,
  20. private gun: P2pDatabaseGunProvider
  21. ) {
  22. this.init();
  23. }
  24. private async init() {
  25. this.ownUserId = await this.storage.get("userId");
  26. this.email = await this.storage.get("email");
  27. }
  28. /**
  29. * Publishs the public key history with the latest key
  30. * @param key key to publish
  31. */
  32. public async publishPrivateKey(key: string) {
  33. let privateKeyHistory = await this.getKeyHistory(this.ownUserId);
  34. console.log("get publishPrivateKey crypto ", privateKeyHistory);
  35. // Todo: avoid publishing the same public key twice - check if new key equals newest key in history
  36. if ( key === privateKeyHistory[0])
  37. {
  38. return;
  39. }
  40. else
  41. {
  42. // Add new key to history
  43. const newKey = {
  44. key: key,
  45. validFrom: Date.now()
  46. };
  47. if (privateKeyHistory) {
  48. privateKeyHistory["keys"].push(newKey);
  49. } else {
  50. privateKeyHistory = {
  51. keys: [newKey]
  52. };
  53. }
  54. }
  55. // Ecnrypt key history
  56. const encryptedPrivateKeyHistory = JSON.stringify(privateKeyHistory);
  57. // Publish updated key history...
  58. const res = await this.ipfs.storePrivateKey(encryptedPrivateKeyHistory);
  59. // store ipfs link Of private tweet in gundb
  60. // let ipfsLink = "ipfs://" + res["Hash"];
  61. await this.gun.storePrivateKeyHistory(this.ownUserId, this.email, res["Hash"]);
  62. }
  63. public async getKeyHistory(userId: string) {
  64. //get private key history from gun
  65. let link = await this.gun.getPvtKeyHistory(userId);
  66. console.log("get link crypto ", link);
  67. // Fetch public key history
  68. if (link.length) {
  69. const encryptedKeyHistory = await this.ipfs.fetchJson(link);
  70. console.log("get link private key crypto ", encryptedKeyHistory);
  71. return JSON.parse(encryptedKeyHistory.toString());
  72. } else {
  73. return null;
  74. }
  75. }
  76. private extractTweetId(text: string): string {
  77. for (let word of text.split(" ")) {
  78. if (this.isTweetId(word)) {
  79. return word.substr(8);
  80. }
  81. }
  82. return "";
  83. }
  84. private extractLinkFromDescription(text: string): string {
  85. for (let word of text.split(" ")) {
  86. if (this.isIpfsLink(word)) {
  87. return word.substr(7);
  88. }
  89. }
  90. return "";
  91. }
  92. private isIpfsLink(word: string): boolean {
  93. return /ipfs:\/\/Qm[a-zA-Z0-9]+/.test(word);
  94. }
  95. private isTweetId(word: string): boolean {
  96. return /tweet:\/\/[0-9]+/.test(word);
  97. }
  98. /**
  99. * Generates a RSA key pair object
  100. */
  101. public async generateRsaKeys() {
  102. return await crypto.subtle.generateKey({
  103. name: "RSA-OAEP",
  104. modulusLength: 1024,
  105. publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
  106. hash: { name: "SHA-256" }
  107. },
  108. true,
  109. ["encrypt", "decrypt"]
  110. );
  111. }
  112. public async generatePgpKeys(email) {
  113. let options = {
  114. userIds: [{ name: 'Rohithosn', email: email }], // multiple user IDs
  115. curve: "ed25519", // ECC curve name
  116. passphrase: "This is phas" // protects the private key
  117. };
  118. let a = await openpgp.generateKey(options);
  119. return a;
  120. }
  121. /**
  122. * extracts the private key from the key object and transforms it to readable text
  123. * @param keys key object
  124. */
  125. public async extractPrivateKey(keys): Promise < string > {
  126. return btoa(
  127. String.fromCharCode.apply(
  128. null,
  129. new Uint8Array(await crypto.subtle.exportKey("pkcs8", keys.privateKey))
  130. )
  131. );
  132. }
  133. /**
  134. * extracts the public key from the key object and transforms it to readable text
  135. * @param keys key object
  136. */
  137. public async extractPublicKey(keys): Promise < string > {
  138. return btoa(
  139. String.fromCharCode.apply(
  140. null,
  141. new Uint8Array(await crypto.subtle.exportKey("spki", keys.publicKey))
  142. )
  143. );
  144. }
  145. /**
  146. * checks if the latest published key is the same as the one saved in app settings
  147. */
  148. public async isPublicKeyPublished(): Promise < boolean > {
  149. const publicKey = await this.storage.get("publicKey");
  150. return publicKey?true:false;
  151. }
  152. /**
  153. * checks if a private key is already set
  154. */
  155. public async isPrivateKeySet(): Promise < boolean > {
  156. const privateKey = await this.storage.get("privateKey");
  157. return privateKey?true:false;
  158. }
  159. /**
  160. * Encrypt text with RSA
  161. * @param plainText plain text
  162. * @param privateKey private key
  163. */
  164. public encrypt(plainText: string, privateKey: string) {
  165. const key = new NodeRSA(
  166. `-----BEGIN PRIVATE KEY-----${privateKey}-----END PRIVATE KEY-----`
  167. );
  168. return key.encryptPrivate(plainText, "base64");
  169. }
  170. /**
  171. * Decrypt secret with RSA
  172. * @param encryptedMessage encrypted message
  173. * @param publicKey public key
  174. */
  175. public decrypt(encryptedMessage: string, publicKey: string): string {
  176. const key = new NodeRSA(
  177. `-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----`
  178. );
  179. return key.decryptPublic(encryptedMessage).toString();
  180. }
  181. /**
  182. * Fetches the public key history for a given user id
  183. * @param userId user id
  184. */
  185. public async fetchPublicKeyHistoryForUser(userId: string): Promise < object[] > {
  186. const keyHistory = await this.getKeyHistory(userId);
  187. return keyHistory["keys"].reverse();
  188. }
  189. private async aesEncrypt(plainText: string) {
  190. const utf8BytesOfPlainText = new TextEncoder().encode(plainText);
  191. const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
  192. const keyBytes = this.base64ToBytes(this.HYBRID_OSN_AES_KEY);
  193. const algorithm = { name: "AES-GCM", iv, length: 256 };
  194. return crypto.subtle
  195. .importKey("raw", keyBytes, algorithm, false, ["encrypt"])
  196. .then((cryptoKey: CryptoKey) => {
  197. return crypto.subtle.encrypt(
  198. algorithm,
  199. cryptoKey,
  200. utf8BytesOfPlainText
  201. );
  202. })
  203. .then((encData: ArrayBuffer) =>
  204. this.mergeBytes(iv, this.bufferToBytes(encData))
  205. )
  206. .then(this.bytesToBase64);
  207. }
  208. private aesDecrypt(encryptedMessage: string) {
  209. try {
  210. const ivWithEncryptedBytes = this.base64ToBytes(encryptedMessage);
  211. const { encryptedBytes, ivBytes } = this.splitIvAndEncrypted(
  212. ivWithEncryptedBytes
  213. );
  214. const keyBytes = this.base64ToBytes(this.HYBRID_OSN_AES_KEY);
  215. const algorithm = { name: "AES-GCM", iv: ivBytes, length: 256 };
  216. return crypto.subtle
  217. .importKey("raw", keyBytes, algorithm, false, ["decrypt"])
  218. .then((cryptoKey: CryptoKey) => {
  219. return crypto.subtle.decrypt(algorithm, cryptoKey, encryptedBytes);
  220. })
  221. .then((decryptedData: ArrayBuffer) => {
  222. return new TextDecoder().decode(decryptedData);
  223. });
  224. } catch (e) {
  225. return Promise.reject(e);
  226. }
  227. }
  228. private bufferToBytes(arrayBuffer: ArrayBuffer) {
  229. return new Uint8Array(arrayBuffer);
  230. }
  231. private bytesToBase64(bytes: Uint8Array): string {
  232. let binary = "";
  233. const len = bytes.length;
  234. for (let i = 0; i < len; i++) {
  235. binary += String.fromCharCode(bytes[i]);
  236. }
  237. return btoa(binary);
  238. }
  239. private base64ToBytes(base64: string): Uint8Array {
  240. const binaryString = atob(base64);
  241. const len = binaryString.length;
  242. const bytes = new Uint8Array(len);
  243. for (let i = 0; i < len; i++) {
  244. bytes[i] = binaryString.charCodeAt(i);
  245. }
  246. return bytes;
  247. }
  248. private mergeBytes(a: Uint8Array, b: Uint8Array) {
  249. const aLen = a.length;
  250. const bLen = b.length;
  251. const res = new Uint8Array(aLen + bLen);
  252. for (let i = 0; i < aLen; i++) {
  253. res[i] = a[i];
  254. }
  255. for (let i = 0; i < bLen; i++) {
  256. res[i + aLen] = b[i];
  257. }
  258. return res;
  259. }
  260. private splitIvAndEncrypted(ivWithEncryptedBytes: Uint8Array) {
  261. const dataLen = ivWithEncryptedBytes.length;
  262. const ivBytes = new Uint8Array(this.IV_LENGTH);
  263. const encryptedBytes = new Uint8Array(dataLen - this.IV_LENGTH);
  264. for (let i = 0; i < this.IV_LENGTH; i++) {
  265. ivBytes[i] = ivWithEncryptedBytes[i];
  266. }
  267. for (let i = this.IV_LENGTH; i < dataLen; i++) {
  268. encryptedBytes[i - this.IV_LENGTH] = ivWithEncryptedBytes[i];
  269. }
  270. return { ivBytes, encryptedBytes };
  271. }
  272. }