diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-23 11:20:53 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-23 11:20:53 +0300 |
commit | 12fdf29fffaad285687bef9075214077654ebd74 (patch) | |
tree | 00591dfd9966bdf7a24c98257c407536dfae4ba8 | |
parent | cb394df2956b5a24ea3e179bdfa313ff959f407c (diff) |
GmCerts: Identity management
Loading and creating client certificates.
-rw-r--r-- | src/gmcerts.c | 250 | ||||
-rw-r--r-- | src/gmcerts.h | 26 |
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 | ||
33 | static const char *filename_GmCerts_ = "trusted.txt"; | 36 | static const char *filename_GmCerts_ = "trusted.txt"; |
37 | static const char *identsDir_GmCerts_ = "idents"; | ||
38 | static const char *identsFilename_GmCerts_ = "idents.bin"; | ||
34 | 39 | ||
35 | iDeclareClass(TrustEntry) | 40 | iDeclareClass(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 | ||
52 | iDefineObjectConstructionArgs(TrustEntry, (const iBlock *fingerprint, const iDate *until), fingerprint, until) | 57 | iDefineObjectConstructionArgs(TrustEntry, |
58 | (const iBlock *fingerprint, const iDate *until), | ||
59 | fingerprint, until) | ||
53 | iDefineClass(TrustEntry) | 60 | iDefineClass(TrustEntry) |
54 | 61 | ||
62 | /*----------------------------------------------------------------------------------------------*/ | ||
63 | |||
64 | static int cmpUrl_GmIdentity_(const void *a, const void *b) { | ||
65 | return cmpStringCase_String((const iString *) a, (const iString *) b); | ||
66 | } | ||
67 | |||
68 | void 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 | |||
78 | static 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 | |||
85 | void 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 | |||
94 | void 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 | |||
106 | void 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 | |||
121 | static iBool isValid_GmIdentity_(const iGmIdentity *d) { | ||
122 | return !isEmpty_TlsCertificate(d->cert); | ||
123 | } | ||
124 | |||
125 | static void setCertificate_GmIdentity_(iGmIdentity *d, iTlsCertificate *cert) { | ||
126 | delete_TlsCertificate(d->cert); | ||
127 | d->cert = cert; | ||
128 | } | ||
129 | |||
130 | static 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 | |||
140 | static 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 | |||
150 | iDefineTypeConstruction(GmIdentity) | ||
151 | |||
55 | /*-----------------------------------------------------------------------------------------------*/ | 152 | /*-----------------------------------------------------------------------------------------------*/ |
56 | 153 | ||
57 | struct Impl_GmCerts { | 154 | struct 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 | ||
161 | static const char *magicIdMeta_GmCerts_ = "lgL2"; | ||
162 | static const char *magicIdentity_GmCerts_ = "iden"; | ||
163 | |||
63 | iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir) | 164 | iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir) |
64 | 165 | ||
166 | static 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 | |||
65 | static void save_GmCerts_(const iGmCerts *d) { | 182 | static 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 | ||
204 | static 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 | |||
230 | static 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 | |||
240 | static 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 | |||
86 | static void load_GmCerts_(iGmCerts *d) { | 262 | static 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 | ||
113 | void init_GmCerts(iGmCerts *d, const char *saveDir) { | 310 | void 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 | ||
120 | void deinit_GmCerts(iGmCerts *d) { | 318 | void 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 | |||
370 | const 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 | |||
383 | iGmIdentity *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 | ||
27 | iDeclareType(GmIdentity) | ||
28 | iDeclareTypeConstruction(GmIdentity) | ||
29 | iDeclareTypeSerialization(GmIdentity) | ||
30 | |||
31 | enum iGmIdentityFlags { | ||
32 | temporary_GmIdentityFlag = 0x1, /* not saved persistently */ | ||
33 | }; | ||
34 | |||
35 | struct 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 | |||
27 | iDeclareType(GmCerts) | 45 | iDeclareType(GmCerts) |
28 | iDeclareTypeConstructionArgs(GmCerts, const char *saveDir) | 46 | iDeclareTypeConstructionArgs(GmCerts, const char *saveDir) |
29 | 47 | ||
30 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); | 48 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); |
49 | |||
50 | const iGmIdentity * identityForUrl_GmCerts (const iGmCerts *, const iString *url); | ||
51 | |||
52 | iGmIdentity * newIdentity_GmCerts (iGmCerts *, int flags, iDate validUntil, | ||
53 | const iString *commonName, const iString *userId, | ||
54 | const iString *org, const iString *country); | ||