1+ import { literal , quad , Statement } from 'rdflib' ;
2+ // import { PODCHAT, removeHashFromUrl } from './Constants';
3+ import { authn } from 'solid-logic'
4+ import * as UI from 'solid-ui'
5+ import * as $rdf from 'rdflib'
6+
7+ const ns = UI . ns
8+
9+ const removeHashFromUrl = ( url : string ) => {
10+ const newUrl = new URL ( url )
11+ newUrl . hash = ''
12+ return newUrl . href
13+ }
14+ const PODCHAT_NS = 'https://www.pod-chat.com/' ;
15+ const PODCHAT = {
16+ LongChatMessage : PODCHAT_NS + 'LongChatMessage' ,
17+ LongChatMessageReply : PODCHAT_NS + 'LongChatMessageReply' ,
18+ RSAPublicKey : PODCHAT_NS + 'RSAPublicKey' ,
19+ RSAPrivateKey : PODCHAT_NS + 'RSAPrivateKey' ,
20+ signature : PODCHAT_NS + 'signature'
21+ }
22+
23+ // import rdfStore, { extractObjectLastValue } from './RdfStore';
24+
25+ export type RdfStore = {
26+ cache : Store ,
27+ fetcher : Fetcher ,
28+ updateManager : UpdateManager
29+ }
30+ function extractObject ( { cache } : RdfStore , webid : string , resourceUrl : string , predicate : PredicateType ) : Array < Node > {
31+ return cache . each ( cache . sym ( webid ) , predicate , undefined , cache . sym ( resourceUrl ) ) ;
32+ }
33+ function extractObjectLastValue ( rdfStore : RdfStore , webid : string , resourceUrl : string , predicate : PredicateType ) : string | undefined {
34+ return extractObject ( rdfStore , webid , resourceUrl , predicate ) . map ( q => q . value ) . pop ( ) ;
35+ }
36+
37+
38+
39+ export const prepareRsaKeyPair = async ( profileId : string , rsaPrivateKeyResourceUrl : string ) : Promise < void > => {
40+
41+ const privKey = await getPrivateKey ( rsaPrivateKeyResourceUrl ) ;
42+ const pubKey = await getPublicKey ( profileId ) ;
43+
44+ if ( ! privKey || ! pubKey ) {
45+ await createKeyPair ( profileId , rsaPrivateKeyResourceUrl ) ;
46+ const privKeyNew = await getPrivateKey ( rsaPrivateKeyResourceUrl ) ;
47+ const pubKeyNew = await getPublicKey ( profileId ) ;
48+ if ( ! privKeyNew || ! pubKeyNew ) {
49+ throw new Error ( 'Unable to create RSA keypair.' ) ;
50+ }
51+ }
52+ }
53+
54+ export const signMessage = async ( rsaPrivateKeyResourceUrl : string , messageContent : string ) : Promise < string | undefined > => {
55+ const privateKey = await getPrivateKey ( rsaPrivateKeyResourceUrl ) ;
56+ if ( privateKey ) {
57+ const messageContentEnc = new TextEncoder ( ) . encode ( messageContent ) ;
58+ const signature = await window . crypto . subtle . sign (
59+ {
60+ name : "RSA-PSS" ,
61+ saltLength : 32 ,
62+ } ,
63+ privateKey ,
64+ messageContentEnc
65+ ) ;
66+ const exportedAsString = ab2str ( signature ) ;
67+ const exportedAsBase64 = window . btoa ( exportedAsString ) ;
68+ return exportedAsBase64 ;
69+ }
70+
71+ return undefined ;
72+ }
73+
74+ export const verifyMessage = async ( profileId : string , messageId : string , messageContent : string , signatureEncoded : string ) : Promise < { messageId : string , trusted : boolean } > => {
75+ let encoded = new TextEncoder ( ) . encode ( messageContent ) ;
76+ const publicKey = await getPublicKey ( profileId ) ;
77+ // base64 decode the string to get the binary data
78+ const binaryDerString = window . atob ( signatureEncoded ) ;
79+ // convert from a binary string to an ArrayBuffer
80+ const signature = str2ab ( binaryDerString ) ;
81+
82+ if ( publicKey ) {
83+ const trusted = await window . crypto . subtle . verify (
84+ {
85+ name : "RSA-PSS" ,
86+ saltLength : 32 ,
87+ } ,
88+ publicKey ,
89+ signature ,
90+ encoded
91+ ) ;
92+ return { messageId, trusted } ;
93+ }
94+
95+ return { messageId, trusted : false } ;
96+ }
97+
98+ const getPublicKey = async ( profileId : string ) : Promise < CryptoKey | undefined > => {
99+ const profileResourceUrl = removeHashFromUrl ( profileId ) ;
100+ await rdfStore . fetcher . load ( profileResourceUrl ) ;
101+ const pubKeyEncoded = extractObjectLastValue ( rdfStore , PODCHAT . RSAPublicKey , profileResourceUrl , rdfStore . cache . sym ( SIOC . content_encoded ) ) ;
102+ if ( pubKeyEncoded ) {
103+ return importPublicKey ( pubKeyEncoded ) ;
104+ }
105+
106+ return undefined ;
107+ }
108+
109+ const getPrivateKey = async ( rsaPrivateKeyResourceUrl : string ) : Promise < CryptoKey | undefined > => {
110+ await rdfStore . fetcher . load ( rsaPrivateKeyResourceUrl ) ;
111+ const privKeyEncoded = extractObjectLastValue ( rdfStore , PODCHAT . RSAPrivateKey , rsaPrivateKeyResourceUrl , rdfStore . cache . sym ( SIOC . content_encoded ) ) ;
112+ if ( privKeyEncoded ) {
113+ return importPrivateKey ( privKeyEncoded ) ;
114+ }
115+ return undefined ;
116+ }
117+
118+ /*
119+ Import a PEM encoded RSA private key, to use for RSA-PSS signing.
120+ Takes a string containing the PEM encoded key, and returns a Promise
121+ that will resolve to a CryptoKey representing the private key.
122+ */
123+ function importPrivateKey ( pem : string ) {
124+ // base64 decode the string to get the binary data
125+ const binaryDerString = window . atob ( pem ) ;
126+ // convert from a binary string to an ArrayBuffer
127+ const binaryDer = str2ab ( binaryDerString ) ;
128+
129+ return window . crypto . subtle . importKey (
130+ "pkcs8" ,
131+ binaryDer ,
132+ {
133+ name : "RSA-PSS" ,
134+ // Consider using a 4096-bit key for systems that require long-term security
135+ //modulusLength: 4096,
136+ //publicExponent: new Uint8Array([1, 0, 1]),
137+ hash : "SHA-256" ,
138+ } ,
139+ true ,
140+ [ "sign" ]
141+ ) ;
142+ }
143+
144+ /*
145+ Import a PEM encoded RSA public key, to use for RSA-OAEP encryption.
146+ Takes a string containing the PEM encoded key, and returns a Promise
147+ that will resolve to a CryptoKey representing the public key.
148+ */
149+ function importPublicKey ( pem : string ) {
150+ // base64 decode the string to get the binary data
151+ const binaryDerString = window . atob ( pem ) ;
152+ // convert from a binary string to an ArrayBuffer
153+ const binaryDer = str2ab ( binaryDerString ) ;
154+
155+ return window . crypto . subtle . importKey (
156+ "spki" ,
157+ binaryDer ,
158+ {
159+ name : "RSA-PSS" ,
160+ hash : "SHA-256"
161+ } ,
162+ true ,
163+ [ "verify" ]
164+ ) ;
165+ }
166+
167+ /* const createKeyPair = async (profileId: string, rsaPrivateKeyResourceUrl: string) => {
168+ const key = await window.crypto.subtle
169+ .generateKey(
170+ {
171+ name: "RSA-PSS",
172+ // Consider using a 4096-bit key for systems that require long-term security
173+ modulusLength: 4096,
174+ publicExponent: new Uint8Array([1, 0, 1]),
175+ hash: "SHA-256",
176+ },
177+ true,
178+ ["sign", "verify"]
179+ );
180+
181+ await privKeyPkcs8Pem(key.privateKey, profileId, rsaPrivateKeyResourceUrl);
182+ await pubKeySpkiPem(key.publicKey, profileId);
183+ } */
184+
185+ async function privKeyPkcs8Pem ( privKey : CryptoKey , profileId : string , rsaPrivateKeyResourceUrl : string ) {
186+ const exported = await window . crypto . subtle . exportKey ( "pkcs8" , privKey ) ;
187+ const exportedAsString = ab2str ( exported ) ;
188+ const exportedAsBase64 = window . btoa ( exportedAsString ) ;
189+ const del : Statement [ ] = [ ] ;
190+ const ins : Statement [ ] = [ ] ;
191+ del . push ( ...rdfStore . cache . statementsMatching (
192+ rdfStore . cache . sym ( PODCHAT . RSAPrivateKey ) ,
193+ rdfStore . cache . sym ( SIOC . content_encoded ) ,
194+ undefined ,
195+ rdfStore . cache . sym ( rsaPrivateKeyResourceUrl )
196+ ) ) ;
197+ ins . push ( quad (
198+ rdfStore . cache . sym ( PODCHAT . RSAPrivateKey ) ,
199+ rdfStore . cache . sym ( SIOC . content_encoded ) ,
200+ literal ( exportedAsBase64 ) , // alain
201+ rdfStore . cache . sym ( rsaPrivateKeyResourceUrl )
202+ ) ) ;
203+ await rdfStore . updateManager . update ( del , ins ) ;
204+ await aclForResource ( rsaPrivateKeyResourceUrl , profileId ) ;
205+ }
206+
207+ async function aclForResource ( resourceUrl : string , ownerId : string ) {
208+ const ins : Statement [ ] = [ ] ;
209+ const aclResourceUrl = resourceUrl + '.acl' ;
210+ const graph = rdfStore . cache . sym ( aclResourceUrl ) ;
211+ const aclId = rdfStore . cache . sym ( aclResourceUrl + '#ControlReadWrite' ) ;
212+ ins . push ( quad ( aclId , rdfStore . cache . sym ( RDF . type ) , rdfStore . cache . sym ( ACL . Authorization ) , graph ) ) ;
213+ ins . push ( quad ( aclId , rdfStore . cache . sym ( ACL . accessTo ) , rdfStore . cache . sym ( resourceUrl ) , graph ) ) ;
214+ [ ACL . Control , ACL . Write , ACL . Read ] . forEach ( mode => {
215+ ins . push ( quad ( aclId , rdfStore . cache . sym ( ACL . mode ) , rdfStore . cache . sym ( mode ) , graph ) ) ;
216+ } ) ;
217+ ins . push ( quad ( aclId , rdfStore . cache . sym ( ACL . agent ) , rdfStore . cache . sym ( ownerId ) , graph ) ) ;
218+ await rdfStore . updateManager . update ( [ ] , ins ) ;
219+ }
220+
221+ async function pubKeySpkiPem ( pubKey : CryptoKey , profileId : string ) {
222+ const exported = await window . crypto . subtle . exportKey ( "spki" , pubKey ) ;
223+ const exportedAsString = ab2str ( exported ) ;
224+ const exportedAsBase64 = window . btoa ( exportedAsString ) ;
225+ const profileResourceUrl = removeHashFromUrl ( profileId ) ;
226+ const del : Statement [ ] = [ ] ;
227+ const ins : Statement [ ] = [ ] ;
228+ del . push ( ...rdfStore . cache . statementsMatching (
229+ rdfStore . cache . sym ( PODCHAT . RSAPublicKey ) ,
230+ rdfStore . cache . sym ( SIOC . content_encoded ) ,
231+ undefined ,
232+ rdfStore . cache . sym ( profileResourceUrl )
233+ ) ) ;
234+ ins . push ( quad (
235+ rdfStore . cache . sym ( PODCHAT . RSAPublicKey ) ,
236+ rdfStore . cache . sym ( SIOC . content_encoded ) ,
237+ literal ( exportedAsBase64 ) , // alain
238+ rdfStore . cache . sym ( profileResourceUrl )
239+ ) ) ;
240+ await rdfStore . updateManager . update ( del , ins ) ;
241+ }
242+
243+ /*
244+ Convert an ArrayBuffer into a string
245+ */
246+ function ab2str ( buf : ArrayBuffer ) {
247+ return String . fromCharCode . apply ( null , new Uint8Array ( buf ) as unknown as number [ ] ) ;
248+ }
249+
250+ function str2ab ( str : string ) {
251+ const buf = new ArrayBuffer ( str . length ) ;
252+ const bufView = new Uint8Array ( buf ) ;
253+ for ( let i = 0 , strLen = str . length ; i < strLen ; i ++ ) {
254+ bufView [ i ] = str . charCodeAt ( i ) ;
255+ }
256+ return buf ;
257+ }
0 commit comments