summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-23 11:20:53 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-23 11:20:53 +0300
commit12fdf29fffaad285687bef9075214077654ebd74 (patch)
tree00591dfd9966bdf7a24c98257c407536dfae4ba8
parentcb394df2956b5a24ea3e179bdfa313ff959f407c (diff)
GmCerts: Identity management
Loading and creating client certificates.
-rw-r--r--src/gmcerts.c250
-rw-r--r--src/gmcerts.h26
2 files changed, 273 insertions, 3 deletions
diff --git a/src/gmcerts.c b/src/gmcerts.c
index 57c8d0eb..58ddfd0c 100644
--- a/src/gmcerts.c
+++ b/src/gmcerts.c
@@ -23,14 +23,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#include "gmcerts.h" 23#include "gmcerts.h"
24 24
25#include <the_Foundation/file.h> 25#include <the_Foundation/file.h>
26#include <the_Foundation/fileinfo.h>
26#include <the_Foundation/mutex.h> 27#include <the_Foundation/mutex.h>
27#include <the_Foundation/path.h> 28#include <the_Foundation/path.h>
28#include <the_Foundation/regexp.h> 29#include <the_Foundation/regexp.h>
30#include <the_Foundation/stringarray.h>
29#include <the_Foundation/stringhash.h> 31#include <the_Foundation/stringhash.h>
32#include <the_Foundation/stringlist.h>
30#include <the_Foundation/time.h> 33#include <the_Foundation/time.h>
31#include <ctype.h> 34#include <ctype.h>
32 35
33static const char *filename_GmCerts_ = "trusted.txt"; 36static const char *filename_GmCerts_ = "trusted.txt";
37static const char *identsDir_GmCerts_ = "idents";
38static const char *identsFilename_GmCerts_ = "idents.bin";
34 39
35iDeclareClass(TrustEntry) 40iDeclareClass(TrustEntry)
36 41
@@ -49,19 +54,131 @@ void deinit_TrustEntry(iTrustEntry *d) {
49 deinit_Block(&d->fingerprint); 54 deinit_Block(&d->fingerprint);
50} 55}
51 56
52iDefineObjectConstructionArgs(TrustEntry, (const iBlock *fingerprint, const iDate *until), fingerprint, until) 57iDefineObjectConstructionArgs(TrustEntry,
58 (const iBlock *fingerprint, const iDate *until),
59 fingerprint, until)
53iDefineClass(TrustEntry) 60iDefineClass(TrustEntry)
54 61
62/*----------------------------------------------------------------------------------------------*/
63
64static int cmpUrl_GmIdentity_(const void *a, const void *b) {
65 return cmpStringCase_String((const iString *) a, (const iString *) b);
66}
67
68void init_GmIdentity(iGmIdentity *d) {
69 d->icon = 0x1f511; /* key */
70 d->flags = 0;
71 d->cert = new_TlsCertificate();
72// init_String(&d->fileName);
73 init_Block(&d->fingerprint, 0);
74 init_SortedArray(&d->useUrls, sizeof(iString), cmpUrl_GmIdentity_);
75 init_String(&d->notes);
76}
77
78static void clear_GmIdentity_(iGmIdentity *d) {
79 iForEach(Array, i, &d->useUrls.values) {
80 deinit_String(i.value);
81 }
82 clear_SortedArray(&d->useUrls);
83}
84
85void deinit_GmIdentity(iGmIdentity *d) {
86 clear_GmIdentity_(d);
87 deinit_String(&d->notes);
88 deinit_SortedArray(&d->useUrls);
89 delete_TlsCertificate(d->cert);
90 deinit_Block(&d->fingerprint);
91// deinit_String(&d->fileName);
92}
93
94void serialize_GmIdentity(const iGmIdentity *d, iStream *outs) {
95// serialize_String(&d->fileName, outs);
96 serialize_Block(&d->fingerprint, outs);
97 writeU32_Stream(outs, d->icon);
98 serialize_String(&d->notes, outs);
99 write32_Stream(outs, d->flags);
100 writeU32_Stream(outs, size_SortedArray(&d->useUrls));
101 iConstForEach(Array, i, &d->useUrls.values) {
102 serialize_String(i.value, outs);
103 }
104}
105
106void deserialize_GmIdentity(iGmIdentity *d, iStream *ins) {
107// deserialize_String(&d->fileName, ins);
108 deserialize_Block(&d->fingerprint, ins);
109 d->icon = readU32_Stream(ins);
110 deserialize_String(&d->notes, ins);
111 d->flags = read32_Stream(ins);
112 size_t n = readU32_Stream(ins);
113 while (n-- && !atEnd_Stream(ins)) {
114 iString url;
115 init_String(&url);
116 deserialize_String(&url, ins);
117 insert_SortedArray(&d->useUrls, &url);
118 }
119}
120
121static iBool isValid_GmIdentity_(const iGmIdentity *d) {
122 return !isEmpty_TlsCertificate(d->cert);
123}
124
125static void setCertificate_GmIdentity_(iGmIdentity *d, iTlsCertificate *cert) {
126 delete_TlsCertificate(d->cert);
127 d->cert = cert;
128}
129
130static const iString *readFile_(const iString *path) {
131 iString *str = NULL;
132 iFile *f = new_File(path);
133 if (open_File(f, readOnly_FileMode | text_FileMode)) {
134 str = readString_File(f);
135 }
136 iRelease(f);
137 return str ? collect_String(str) : collectNew_String();
138}
139
140static iBool writeTextFile_(const iString *path, const iString *content) {
141 iFile *f = iClob(new_File(path));
142 if (open_File(f, writeOnly_FileMode | text_FileMode)) {
143 write_File(f, &content->chars);
144 close_File(f);
145 return iTrue;
146 }
147 return iFalse;
148}
149
150iDefineTypeConstruction(GmIdentity)
151
55/*-----------------------------------------------------------------------------------------------*/ 152/*-----------------------------------------------------------------------------------------------*/
56 153
57struct Impl_GmCerts { 154struct Impl_GmCerts {
58 iMutex mtx; 155 iMutex mtx;
59 iString saveDir; 156 iString saveDir;
60 iStringHash *trusted; 157 iStringHash *trusted;
158 iPtrArray idents;
61}; 159};
62 160
161static const char *magicIdMeta_GmCerts_ = "lgL2";
162static const char *magicIdentity_GmCerts_ = "iden";
163
63iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir) 164iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir)
64 165
166static void saveIdentities_GmCerts_(const iGmCerts *d) {
167 iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, identsFilename_GmCerts_)));
168 if (open_File(f, writeOnly_FileMode)) {
169 writeData_File(f, magicIdMeta_GmCerts_, 4);
170 writeU32_File(f, 0); /* version */
171 iConstForEach(PtrArray, i, &d->idents) {
172 const iGmIdentity *ident = i.ptr;
173 if (~ident->flags & temporary_GmIdentityFlag) {
174 writeData_File(f, magicIdentity_GmCerts_, 4);
175 serialize_GmIdentity(ident, stream_File(f));
176 }
177 }
178 }
179 iRelease(f);
180}
181
65static void save_GmCerts_(const iGmCerts *d) { 182static void save_GmCerts_(const iGmCerts *d) {
66 iBeginCollect(); 183 iBeginCollect();
67 iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, filename_GmCerts_))); 184 iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, filename_GmCerts_)));
@@ -80,9 +197,68 @@ static void save_GmCerts_(const iGmCerts *d) {
80 deinit_String(&line); 197 deinit_String(&line);
81 } 198 }
82 iRelease(f); 199 iRelease(f);
200 saveIdentities_GmCerts_(d);
83 iEndCollect(); 201 iEndCollect();
84} 202}
85 203
204static void loadIdentities_GmCerts_(iGmCerts *d) {
205 iFile *f =
206 iClob(new_File(collect_String(concatCStr_Path(&d->saveDir, identsFilename_GmCerts_))));
207 if (open_File(f, readOnly_FileMode)) {
208 char magic[4];
209 readData_File(f, sizeof(magic), magic);
210 if (memcmp(magic, magicIdMeta_GmCerts_, sizeof(magic))) {
211 printf("%s: format not recognized\n", cstr_String(path_File(f)));
212 return;
213 }
214 setVersion_Stream(stream_File(f), readU32_File(f));
215 while (!atEnd_File(f)) {
216 readData_File(f, sizeof(magic), magic);
217 if (!memcmp(magic, magicIdentity_GmCerts_, sizeof(magic))) {
218 iGmIdentity *id = new_GmIdentity();
219 deserialize_GmIdentity(id, stream_File(f));
220 pushBack_PtrArray(&d->idents, id);
221 }
222 else {
223 printf("%s: invalid file contents\n", cstr_String(path_File(f)));
224 break;
225 }
226 }
227 }
228}
229
230static iGmIdentity *findIdentity_GmCerts_(iGmCerts *d, const iBlock *fingerprint) {
231 iForEach(PtrArray, i, &d->idents) {
232 iGmIdentity *ident = i.ptr;
233 if (cmp_Block(fingerprint, &ident->fingerprint) == 0) { /* TODO: could use a hash */
234 return ident;
235 }
236 }
237 return NULL;
238}
239
240static void loadIdentityFromCertificate_GmCerts_(iGmCerts *d, const iString *crtPath) {
241 iAssert(fileExists_FileInfo(crtPath));
242 iString *keyPath = collect_String(copy_String(crtPath));
243 truncate_Block(&keyPath->chars, size_String(keyPath) - 3);
244 appendCStr_String(keyPath, "key");
245 if (!fileExists_FileInfo(keyPath)) {
246 return;
247 }
248 iTlsCertificate *cert = newPemKey_TlsCertificate(readFile_(crtPath), readFile_(keyPath));
249 iBlock *finger = fingerprint_TlsCertificate(cert);
250 iGmIdentity *ident = findIdentity_GmCerts_(d, finger);
251 if (!ident) {
252 ident = new_GmIdentity();
253 set_Block(&ident->fingerprint, finger);
254 iDate today;
255 initCurrent_Date(&today);
256 set_String(&ident->notes, collect_String(format_Date(&today, "Imported on %b %d, %Y")));
257 }
258 setCertificate_GmIdentity_(ident, cert);
259 delete_Block(finger);
260}
261
86static void load_GmCerts_(iGmCerts *d) { 262static void load_GmCerts_(iGmCerts *d) {
87 iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, filename_GmCerts_))); 263 iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, filename_GmCerts_)));
88 if (open_File(f, readOnly_FileMode | text_FileMode)) { 264 if (open_File(f, readOnly_FileMode | text_FileMode)) {
@@ -108,17 +284,43 @@ static void load_GmCerts_(iGmCerts *d) {
108 iRelease(pattern); 284 iRelease(pattern);
109 } 285 }
110 iRelease(f); 286 iRelease(f);
287 /* Load all identity certificates. */ {
288 loadIdentities_GmCerts_(d);
289 const iString *idDir = collect_String(concatCStr_Path(&d->saveDir, identsDir_GmCerts_));
290 if (!fileExists_FileInfo(idDir)) {
291 mkdir_Path(idDir);
292 }
293 iForEach(DirFileInfo, i, iClob(directoryContents_FileInfo(iClob(new_FileInfo(idDir))))) {
294 const iFileInfo *entry = i.value;
295 if (endsWithCase_String(path_FileInfo(entry), ".crt")) {
296 loadIdentityFromCertificate_GmCerts_(d, path_FileInfo(entry));
297 }
298 }
299 /* Remove certificates whose crt/key files were missing. */
300 iForEach(PtrArray, j, &d->idents) {
301 iGmIdentity *ident = j.ptr;
302 if (!isValid_GmIdentity_(ident)) {
303 delete_GmIdentity(ident);
304 remove_PtrArrayIterator(&j);
305 }
306 }
307 }
111} 308}
112 309
113void init_GmCerts(iGmCerts *d, const char *saveDir) { 310void init_GmCerts(iGmCerts *d, const char *saveDir) {
114 init_Mutex(&d->mtx); 311 init_Mutex(&d->mtx);
115 initCStr_String(&d->saveDir, saveDir); 312 initCStr_String(&d->saveDir, saveDir);
116 d->trusted = new_StringHash(); 313 d->trusted = new_StringHash();
314 init_PtrArray(&d->idents);
117 load_GmCerts_(d); 315 load_GmCerts_(d);
118} 316}
119 317
120void deinit_GmCerts(iGmCerts *d) { 318void deinit_GmCerts(iGmCerts *d) {
121 iGuardMutex(&d->mtx, { 319 iGuardMutex(&d->mtx, {
320 iForEach(PtrArray, i, &d->idents) {
321 delete_GmIdentity(i.ptr);
322 }
323 deinit_PtrArray(&d->idents);
122 iRelease(d->trusted); 324 iRelease(d->trusted);
123 deinit_String(&d->saveDir); 325 deinit_String(&d->saveDir);
124 }); 326 });
@@ -164,3 +366,47 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, const iTlsCertificate *ce
164 unlock_Mutex(&d->mtx); 366 unlock_Mutex(&d->mtx);
165 return iTrue; 367 return iTrue;
166} 368}
369
370const iGmIdentity *identityForUrl_GmCerts(const iGmCerts *d, const iString *url) {
371 iConstForEach(PtrArray, i, &d->idents) {
372 const iGmIdentity *ident = i.ptr;
373 iConstForEach(Array, j, &ident->useUrls.values) {
374 const iString *used = j.value;
375 if (startsWithCase_String(url, cstr_String(used))) {
376 return ident;
377 }
378 }
379 }
380 return NULL;
381}
382
383iGmIdentity *newIdentity_GmCerts(iGmCerts *d, int flags, iDate validUntil, const iString *commonName,
384 const iString *userId, const iString *org,
385 const iString *country) {
386 const iTlsCertificateName names[] = {
387 { subjectCommonName_TlsCertificateNameType, commonName },
388 { subjectUserId_TlsCertificateNameType, userId },
389 { subjectOrganization_TlsCertificateNameType, org },
390 { subjectCountry_TlsCertificateNameType, country },
391 { 0, NULL }
392 };
393 iGmIdentity *id = new_GmIdentity();
394 setCertificate_GmIdentity_(id, newSelfSignedRSA_TlsCertificate(2048, validUntil, names));
395 /* Save the certificate and private key as PEM files. */
396 if (~flags & temporary_GmIdentityFlag) {
397 const char *finger = cstrCollect_String(hexEncode_Block(&id->fingerprint));
398 if (!writeTextFile_(
399 collect_String(concatCStr_Path(&d->saveDir, format_CStr("%s.crt", finger))),
400 collect_String(pem_TlsCertificate(id->cert)))) {
401 delete_GmIdentity(id);
402 return NULL;
403 }
404 if (!writeTextFile_(
405 collect_String(concatCStr_Path(&d->saveDir, format_CStr("%s.key", finger))),
406 collect_String(privateKeyPem_TlsCertificate(id->cert)))) {
407 delete_GmIdentity(id);
408 return NULL;
409 }
410 }
411 return id;
412}
diff --git a/src/gmcerts.h b/src/gmcerts.h
index 7f8bb3f5..6f34e5dd 100644
--- a/src/gmcerts.h
+++ b/src/gmcerts.h
@@ -24,7 +24,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
24 24
25#include <the_Foundation/tlsrequest.h> 25#include <the_Foundation/tlsrequest.h>
26 26
27iDeclareType(GmIdentity)
28iDeclareTypeConstruction(GmIdentity)
29iDeclareTypeSerialization(GmIdentity)
30
31enum iGmIdentityFlags {
32 temporary_GmIdentityFlag = 0x1, /* not saved persistently */
33};
34
35struct Impl_GmIdentity {
36// iString fileName;
37 iBlock fingerprint;
38 iTlsCertificate *cert;
39 iSortedArray useUrls; /* Strings */
40 iChar icon;
41 iString notes; /* private, local usage notes */
42 int flags;
43};
44
27iDeclareType(GmCerts) 45iDeclareType(GmCerts)
28iDeclareTypeConstructionArgs(GmCerts, const char *saveDir) 46iDeclareTypeConstructionArgs(GmCerts, const char *saveDir)
29 47
30iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); 48iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert);
49
50const iGmIdentity * identityForUrl_GmCerts (const iGmCerts *, const iString *url);
51
52iGmIdentity * newIdentity_GmCerts (iGmCerts *, int flags, iDate validUntil,
53 const iString *commonName, const iString *userId,
54 const iString *org, const iString *country);