]>
Commit | Line | Data |
---|---|---|
552cc11b | 1 | /* |
5955db5b | 2 | * Copyright (C) 2017-2018 Tobias Brunner |
7033a70f | 3 | * Copyright (C) 2005-2009 Martin Willi |
552cc11b | 4 | * Copyright (C) 2005 Jan Hutter |
552cc11b MW |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | * for more details. | |
552cc11b | 15 | */ |
7daf5226 | 16 | |
552cc11b MW |
17 | #include <gmp.h> |
18 | #include <sys/stat.h> | |
19 | #include <unistd.h> | |
20 | #include <stdio.h> | |
21 | #include <string.h> | |
22 | ||
23 | #include "gmp_rsa_public_key.h" | |
24 | ||
f05b4272 | 25 | #include <utils/debug.h> |
d3d7e46b | 26 | #include <asn1/oid.h> |
552cc11b | 27 | #include <asn1/asn1.h> |
d3d7e46b | 28 | #include <asn1/asn1_parser.h> |
d3d7e46b | 29 | #include <crypto/hashers/hasher.h> |
7d6b8164 | 30 | #include <credentials/keys/signature_params.h> |
552cc11b | 31 | |
3e35a6e7 MW |
32 | #ifdef HAVE_MPZ_POWM_SEC |
33 | # undef mpz_powm | |
34 | # define mpz_powm mpz_powm_sec | |
35 | #endif | |
36 | ||
552cc11b MW |
37 | typedef struct private_gmp_rsa_public_key_t private_gmp_rsa_public_key_t; |
38 | ||
39 | /** | |
40 | * Private data structure with signing context. | |
41 | */ | |
42 | struct private_gmp_rsa_public_key_t { | |
43 | /** | |
44 | * Public interface for this signer. | |
45 | */ | |
46 | gmp_rsa_public_key_t public; | |
7daf5226 | 47 | |
552cc11b MW |
48 | /** |
49 | * Public modulus. | |
50 | */ | |
51 | mpz_t n; | |
7daf5226 | 52 | |
552cc11b MW |
53 | /** |
54 | * Public exponent. | |
55 | */ | |
56 | mpz_t e; | |
7daf5226 | 57 | |
552cc11b MW |
58 | /** |
59 | * Keysize in bytes. | |
60 | */ | |
61 | size_t k; | |
7daf5226 | 62 | |
552cc11b MW |
63 | /** |
64 | * reference counter | |
65 | */ | |
66 | refcount_t ref; | |
67 | }; | |
68 | ||
8b799d55 AS |
69 | /** |
70 | * Shared functions defined in gmp_rsa_private_key.c | |
71 | */ | |
5955db5b TB |
72 | chunk_t gmp_mpz_to_chunk(const mpz_t value); |
73 | bool gmp_emsa_pkcs1_signature_data(hash_algorithm_t hash_algorithm, | |
74 | chunk_t data, size_t keylen, chunk_t *em); | |
8b799d55 | 75 | |
552cc11b MW |
76 | /** |
77 | * RSAEP algorithm specified in PKCS#1. | |
78 | */ | |
79 | static chunk_t rsaep(private_gmp_rsa_public_key_t *this, chunk_t data) | |
80 | { | |
81 | mpz_t m, c; | |
82 | chunk_t encrypted; | |
7daf5226 | 83 | |
552cc11b | 84 | mpz_init(m); |
552cc11b | 85 | mpz_import(m, data.len, 1, 1, 1, 0, data.ptr); |
7daf5226 | 86 | |
ef5c37fc TB |
87 | if (mpz_cmp_ui(m, 0) <= 0 || mpz_cmp(m, this->n) >= 0) |
88 | { /* m must be <= n-1, and while 0 is technically a valid value, it | |
89 | * doesn't really make sense here, so we filter that too */ | |
90 | mpz_clear(m); | |
91 | return chunk_empty; | |
92 | } | |
93 | ||
94 | mpz_init(c); | |
552cc11b MW |
95 | mpz_powm(c, m, this->e, this->n); |
96 | ||
73f6886a MW |
97 | encrypted.len = this->k; |
98 | encrypted.ptr = mpz_export(NULL, NULL, 1, encrypted.len, 1, 0, c); | |
99 | if (encrypted.ptr == NULL) | |
100 | { | |
101 | encrypted.len = 0; | |
102 | } | |
7daf5226 | 103 | |
552cc11b | 104 | mpz_clear(c); |
73f6886a | 105 | mpz_clear(m); |
7daf5226 | 106 | |
552cc11b MW |
107 | return encrypted; |
108 | } | |
109 | ||
110 | /** | |
111 | * RSAVP1 algorithm specified in PKCS#1. | |
112 | */ | |
113 | static chunk_t rsavp1(private_gmp_rsa_public_key_t *this, chunk_t data) | |
114 | { | |
115 | return rsaep(this, data); | |
116 | } | |
117 | ||
118 | /** | |
7d6b8164 | 119 | * Verification of an EMSA PKCS1 signature described in PKCS#1 |
552cc11b MW |
120 | */ |
121 | static bool verify_emsa_pkcs1_signature(private_gmp_rsa_public_key_t *this, | |
122 | hash_algorithm_t algorithm, | |
123 | chunk_t data, chunk_t signature) | |
124 | { | |
5955db5b | 125 | chunk_t em_expected, em; |
d3d7e46b | 126 | bool success = FALSE; |
7daf5226 | 127 | |
552cc11b MW |
128 | /* remove any preceding 0-bytes from signature */ |
129 | while (signature.len && *(signature.ptr) == 0x00) | |
130 | { | |
e7227f0b | 131 | signature = chunk_skip(signature, 1); |
552cc11b | 132 | } |
7daf5226 | 133 | |
e7227f0b | 134 | if (signature.len == 0 || signature.len > this->k) |
552cc11b | 135 | { |
060b508e | 136 | return FALSE; |
552cc11b | 137 | } |
7daf5226 | 138 | |
5955db5b TB |
139 | /* generate expected signature value */ |
140 | if (!gmp_emsa_pkcs1_signature_data(algorithm, data, this->k, &em_expected)) | |
552cc11b | 141 | { |
5955db5b | 142 | return FALSE; |
8b799d55 | 143 | } |
552cc11b | 144 | |
5955db5b TB |
145 | /* unpack signature */ |
146 | em = rsavp1(this, signature); | |
d3d7e46b | 147 | |
5955db5b | 148 | success = chunk_equals_const(em_expected, em); |
552cc11b | 149 | |
5955db5b TB |
150 | chunk_free(&em_expected); |
151 | chunk_free(&em); | |
d3d7e46b | 152 | return success; |
552cc11b MW |
153 | } |
154 | ||
7d6b8164 TB |
155 | /** |
156 | * Verification of an EMSA PSS signature described in PKCS#1 | |
157 | */ | |
158 | static bool verify_emsa_pss_signature(private_gmp_rsa_public_key_t *this, | |
159 | rsa_pss_params_t *params, chunk_t data, | |
160 | chunk_t signature) | |
161 | { | |
162 | ext_out_function_t xof; | |
163 | hasher_t *hasher = NULL; | |
164 | xof_t *mgf = NULL; | |
165 | chunk_t em, hash, salt, db, h, dbmask, m; | |
166 | size_t embits, maskbits; | |
167 | int i; | |
168 | bool success = FALSE; | |
169 | ||
234302a1 | 170 | if (!params || params->salt_len < 0) |
7d6b8164 TB |
171 | { |
172 | return FALSE; | |
173 | } | |
126fd8af TB |
174 | xof = xof_mgf1_from_hash_algorithm(params->mgf1_hash); |
175 | if (xof == XOF_UNDEFINED) | |
7d6b8164 | 176 | { |
126fd8af TB |
177 | DBG1(DBG_LIB, "%N is not supported for MGF1", hash_algorithm_names, |
178 | params->mgf1_hash); | |
179 | return FALSE; | |
7d6b8164 TB |
180 | } |
181 | chunk_skip_zero(signature); | |
182 | if (signature.len == 0 || signature.len > this->k) | |
183 | { | |
184 | return FALSE; | |
185 | } | |
186 | /* EM = RSAVP1((n, e), S) */ | |
187 | em = rsavp1(this, signature); | |
188 | if (!em.len) | |
189 | { | |
190 | goto error; | |
191 | } | |
192 | /* emBits = modBits - 1 */ | |
193 | embits = mpz_sizeinbase(this->n, 2) - 1; | |
194 | /* mHash = Hash(M) */ | |
195 | hasher = lib->crypto->create_hasher(lib->crypto, params->hash); | |
196 | if (!hasher) | |
197 | { | |
198 | DBG1(DBG_LIB, "hash algorithm %N not supported", | |
199 | hash_algorithm_names, params->hash); | |
200 | goto error; | |
201 | } | |
202 | hash = chunk_alloca(hasher->get_hash_size(hasher)); | |
203 | if (!hasher->get_hash(hasher, data, hash.ptr)) | |
204 | { | |
205 | goto error; | |
206 | } | |
ecfe6755 | 207 | salt.len = params->salt_len; |
7d6b8164 TB |
208 | /* verify general structure of EM */ |
209 | maskbits = (8 * em.len) - embits; | |
210 | if (em.len < (hash.len + salt.len + 2) || em.ptr[em.len-1] != 0xbc || | |
211 | (em.ptr[0] & (0xff << (8-maskbits)))) | |
212 | { /* inconsistent */ | |
213 | goto error; | |
214 | } | |
215 | /* split EM in maskedDB and H */ | |
216 | db = chunk_create(em.ptr, em.len - hash.len - 1); | |
217 | h = chunk_create(em.ptr + db.len, hash.len); | |
218 | /* dbMask = MGF(H, emLen - hLen - 1) */ | |
219 | mgf = lib->crypto->create_xof(lib->crypto, xof); | |
220 | if (!mgf) | |
221 | { | |
222 | DBG1(DBG_LIB, "%N not supported", ext_out_function_names, xof); | |
223 | goto error; | |
224 | } | |
225 | dbmask = chunk_alloca(db.len); | |
226 | if (!mgf->set_seed(mgf, h) || | |
227 | !mgf->get_bytes(mgf, dbmask.len, dbmask.ptr)) | |
228 | { | |
229 | DBG1(DBG_LIB, "%N not supported or failed", ext_out_function_names, xof); | |
230 | goto error; | |
231 | } | |
232 | /* DB = maskedDB xor dbMask */ | |
233 | memxor(db.ptr, dbmask.ptr, db.len); | |
234 | if (maskbits) | |
235 | { | |
236 | db.ptr[0] &= (0xff >> maskbits); | |
237 | } | |
238 | /* check DB = PS | 0x01 | salt */ | |
239 | for (i = 0; i < (db.len - salt.len - 1); i++) | |
240 | { | |
241 | if (db.ptr[i]) | |
242 | { /* padding not 0 */ | |
243 | goto error; | |
244 | } | |
245 | } | |
246 | if (db.ptr[i++] != 0x01) | |
247 | { /* 0x01 not found */ | |
248 | goto error; | |
249 | } | |
250 | salt.ptr = &db.ptr[i]; | |
251 | /* M' = 0x0000000000000000 | mHash | salt */ | |
252 | m = chunk_cata("ccc", | |
253 | chunk_from_chars(0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00), | |
254 | hash, salt); | |
255 | if (!hasher->get_hash(hasher, m, hash.ptr)) | |
256 | { | |
257 | goto error; | |
258 | } | |
259 | success = memeq_const(h.ptr, hash.ptr, hash.len); | |
260 | ||
261 | error: | |
262 | DESTROY_IF(hasher); | |
263 | DESTROY_IF(mgf); | |
264 | free(em.ptr); | |
265 | return success; | |
266 | } | |
267 | ||
876b61e1 MW |
268 | METHOD(public_key_t, get_type, key_type_t, |
269 | private_gmp_rsa_public_key_t *this) | |
552cc11b MW |
270 | { |
271 | return KEY_RSA; | |
272 | } | |
273 | ||
876b61e1 | 274 | METHOD(public_key_t, verify, bool, |
a413571f | 275 | private_gmp_rsa_public_key_t *this, signature_scheme_t scheme, void *params, |
876b61e1 | 276 | chunk_t data, chunk_t signature) |
552cc11b MW |
277 | { |
278 | switch (scheme) | |
279 | { | |
8b799d55 | 280 | case SIGN_RSA_EMSA_PKCS1_NULL: |
552cc11b | 281 | return verify_emsa_pkcs1_signature(this, HASH_UNKNOWN, data, signature); |
40f2589a | 282 | case SIGN_RSA_EMSA_PKCS1_SHA2_224: |
b6f739c1 | 283 | return verify_emsa_pkcs1_signature(this, HASH_SHA224, data, signature); |
40f2589a | 284 | case SIGN_RSA_EMSA_PKCS1_SHA2_256: |
552cc11b | 285 | return verify_emsa_pkcs1_signature(this, HASH_SHA256, data, signature); |
40f2589a | 286 | case SIGN_RSA_EMSA_PKCS1_SHA2_384: |
552cc11b | 287 | return verify_emsa_pkcs1_signature(this, HASH_SHA384, data, signature); |
40f2589a | 288 | case SIGN_RSA_EMSA_PKCS1_SHA2_512: |
552cc11b | 289 | return verify_emsa_pkcs1_signature(this, HASH_SHA512, data, signature); |
40f2589a AS |
290 | case SIGN_RSA_EMSA_PKCS1_SHA3_224: |
291 | return verify_emsa_pkcs1_signature(this, HASH_SHA3_224, data, signature); | |
292 | case SIGN_RSA_EMSA_PKCS1_SHA3_256: | |
293 | return verify_emsa_pkcs1_signature(this, HASH_SHA3_256, data, signature); | |
294 | case SIGN_RSA_EMSA_PKCS1_SHA3_384: | |
295 | return verify_emsa_pkcs1_signature(this, HASH_SHA3_384, data, signature); | |
296 | case SIGN_RSA_EMSA_PKCS1_SHA3_512: | |
297 | return verify_emsa_pkcs1_signature(this, HASH_SHA3_512, data, signature); | |
298 | case SIGN_RSA_EMSA_PKCS1_SHA1: | |
299 | return verify_emsa_pkcs1_signature(this, HASH_SHA1, data, signature); | |
300 | case SIGN_RSA_EMSA_PKCS1_MD5: | |
301 | return verify_emsa_pkcs1_signature(this, HASH_MD5, data, signature); | |
7d6b8164 TB |
302 | case SIGN_RSA_EMSA_PSS: |
303 | return verify_emsa_pss_signature(this, params, data, signature); | |
552cc11b | 304 | default: |
8b0e0910 | 305 | DBG1(DBG_LIB, "signature scheme %N not supported in RSA", |
552cc11b MW |
306 | signature_scheme_names, scheme); |
307 | return FALSE; | |
308 | } | |
309 | } | |
310 | ||
741680d1 | 311 | #define MIN_PS_PADDING 8 |
c50ff68d | 312 | |
876b61e1 | 313 | METHOD(public_key_t, encrypt_, bool, |
33ddaaab | 314 | private_gmp_rsa_public_key_t *this, encryption_scheme_t scheme, |
4abb29f6 | 315 | void *params, chunk_t plain, chunk_t *crypto) |
552cc11b | 316 | { |
c50ff68d AS |
317 | chunk_t em; |
318 | u_char *pos; | |
5025135f | 319 | int padding; |
7b3814f7 | 320 | rng_t *rng; |
c50ff68d | 321 | |
33ddaaab | 322 | if (scheme != ENCRYPT_RSA_PKCS1) |
c50ff68d | 323 | { |
33ddaaab MW |
324 | DBG1(DBG_LIB, "encryption scheme %N not supported", |
325 | encryption_scheme_names, scheme); | |
c50ff68d AS |
326 | return FALSE; |
327 | } | |
c50ff68d AS |
328 | /* number of pseudo-random padding octets */ |
329 | padding = this->k - plain.len - 3; | |
7b3814f7 | 330 | if (padding < MIN_PS_PADDING) |
c50ff68d | 331 | { |
8b0e0910 TB |
332 | DBG1(DBG_LIB, "pseudo-random padding must be at least %d octets", |
333 | MIN_PS_PADDING); | |
c50ff68d AS |
334 | return FALSE; |
335 | } | |
33ddaaab MW |
336 | rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); |
337 | if (rng == NULL) | |
338 | { | |
339 | DBG1(DBG_LIB, "no random generator available"); | |
340 | return FALSE; | |
341 | } | |
c50ff68d AS |
342 | |
343 | /* padding according to PKCS#1 7.2.1 (RSAES-PKCS1-v1.5-ENCRYPT) */ | |
8b0e0910 TB |
344 | DBG2(DBG_LIB, "padding %u bytes of data to the rsa modulus size of" |
345 | " %u bytes", plain.len, this->k); | |
c50ff68d | 346 | em.len = this->k; |
7daf5226 | 347 | em.ptr = malloc(em.len); |
c50ff68d AS |
348 | pos = em.ptr; |
349 | *pos++ = 0x00; | |
350 | *pos++ = 0x02; | |
351 | ||
352 | /* fill with pseudo random octets */ | |
5025135f | 353 | if (!rng_get_bytes_not_zero(rng, padding, pos, TRUE)) |
c50ff68d | 354 | { |
5025135f TB |
355 | DBG1(DBG_LIB, "failed to allocate padding"); |
356 | chunk_clear(&em); | |
357 | rng->destroy(rng); | |
358 | return FALSE; | |
c50ff68d AS |
359 | } |
360 | rng->destroy(rng); | |
361 | ||
828cefc3 MW |
362 | pos += padding; |
363 | ||
c50ff68d AS |
364 | /* append the padding terminator */ |
365 | *pos++ = 0x00; | |
366 | ||
367 | /* now add the data */ | |
368 | memcpy(pos, plain.ptr, plain.len); | |
8b0e0910 | 369 | DBG3(DBG_LIB, "padded data before rsa encryption: %B", &em); |
7daf5226 | 370 | |
d615ffdc | 371 | /* rsa encryption using PKCS#1 RSAEP */ |
c50ff68d | 372 | *crypto = rsaep(this, em); |
8b0e0910 | 373 | DBG3(DBG_LIB, "rsa encrypted data: %B", crypto); |
c50ff68d AS |
374 | chunk_clear(&em); |
375 | return TRUE; | |
552cc11b MW |
376 | } |
377 | ||
a944d209 | 378 | METHOD(public_key_t, get_keysize, int, |
876b61e1 | 379 | private_gmp_rsa_public_key_t *this) |
552cc11b | 380 | { |
a944d209 | 381 | return mpz_sizeinbase(this->n, 2); |
552cc11b MW |
382 | } |
383 | ||
876b61e1 MW |
384 | METHOD(public_key_t, get_encoding, bool, |
385 | private_gmp_rsa_public_key_t *this, cred_encoding_type_t type, | |
386 | chunk_t *encoding) | |
8b799d55 | 387 | { |
741680d1 | 388 | chunk_t n, e; |
dd04a68f | 389 | bool success; |
7daf5226 | 390 | |
741680d1 MW |
391 | n = gmp_mpz_to_chunk(this->n); |
392 | e = gmp_mpz_to_chunk(this->e); | |
7daf5226 MW |
393 | |
394 | success = lib->encoding->encode(lib->encoding, type, NULL, encoding, | |
da9724e6 | 395 | CRED_PART_RSA_MODULUS, n, CRED_PART_RSA_PUB_EXP, e, CRED_PART_END); |
741680d1 MW |
396 | chunk_free(&n); |
397 | chunk_free(&e); | |
7daf5226 | 398 | |
741680d1 | 399 | return success; |
8b799d55 AS |
400 | } |
401 | ||
876b61e1 MW |
402 | METHOD(public_key_t, get_fingerprint, bool, |
403 | private_gmp_rsa_public_key_t *this, cred_encoding_type_t type, chunk_t *fp) | |
552cc11b | 404 | { |
741680d1 MW |
405 | chunk_t n, e; |
406 | bool success; | |
7daf5226 | 407 | |
741680d1 | 408 | if (lib->encoding->get_cache(lib->encoding, type, this, fp)) |
552cc11b | 409 | { |
741680d1 | 410 | return TRUE; |
552cc11b | 411 | } |
741680d1 MW |
412 | n = gmp_mpz_to_chunk(this->n); |
413 | e = gmp_mpz_to_chunk(this->e); | |
7daf5226 | 414 | |
741680d1 | 415 | success = lib->encoding->encode(lib->encoding, type, this, fp, |
da9724e6 | 416 | CRED_PART_RSA_MODULUS, n, CRED_PART_RSA_PUB_EXP, e, CRED_PART_END); |
741680d1 MW |
417 | chunk_free(&n); |
418 | chunk_free(&e); | |
7daf5226 | 419 | |
741680d1 | 420 | return success; |
552cc11b MW |
421 | } |
422 | ||
876b61e1 MW |
423 | METHOD(public_key_t, get_ref, public_key_t*, |
424 | private_gmp_rsa_public_key_t *this) | |
552cc11b MW |
425 | { |
426 | ref_get(&this->ref); | |
876b61e1 | 427 | return &this->public.key; |
552cc11b MW |
428 | } |
429 | ||
876b61e1 MW |
430 | METHOD(public_key_t, destroy, void, |
431 | private_gmp_rsa_public_key_t *this) | |
552cc11b MW |
432 | { |
433 | if (ref_put(&this->ref)) | |
434 | { | |
435 | mpz_clear(this->n); | |
436 | mpz_clear(this->e); | |
741680d1 | 437 | lib->encoding->clear_cache(lib->encoding, this); |
552cc11b MW |
438 | free(this); |
439 | } | |
440 | } | |
441 | ||
442 | /** | |
1086d00e | 443 | * See header. |
552cc11b | 444 | */ |
1086d00e | 445 | gmp_rsa_public_key_t *gmp_rsa_public_key_load(key_type_t type, va_list args) |
552cc11b | 446 | { |
1086d00e MW |
447 | private_gmp_rsa_public_key_t *this; |
448 | chunk_t n, e; | |
449 | ||
450 | n = e = chunk_empty; | |
451 | while (TRUE) | |
452 | { | |
453 | switch (va_arg(args, builder_part_t)) | |
454 | { | |
455 | case BUILD_RSA_MODULUS: | |
456 | n = va_arg(args, chunk_t); | |
457 | continue; | |
458 | case BUILD_RSA_PUB_EXP: | |
459 | e = va_arg(args, chunk_t); | |
460 | continue; | |
461 | case BUILD_END: | |
462 | break; | |
463 | default: | |
464 | return NULL; | |
465 | } | |
466 | break; | |
467 | } | |
6681d98d | 468 | if (!e.len || !n.len || (n.ptr[n.len-1] & 0x01) == 0) |
1086d00e MW |
469 | { |
470 | return NULL; | |
471 | } | |
472 | ||
876b61e1 | 473 | INIT(this, |
ba31fe1f MW |
474 | .public = { |
475 | .key = { | |
476 | .get_type = _get_type, | |
477 | .verify = _verify, | |
478 | .encrypt = _encrypt_, | |
479 | .equals = public_key_equals, | |
480 | .get_keysize = _get_keysize, | |
481 | .get_fingerprint = _get_fingerprint, | |
482 | .has_fingerprint = public_key_has_fingerprint, | |
483 | .get_encoding = _get_encoding, | |
484 | .get_ref = _get_ref, | |
485 | .destroy = _destroy, | |
486 | }, | |
876b61e1 MW |
487 | }, |
488 | .ref = 1, | |
489 | ); | |
7daf5226 | 490 | |
8b799d55 AS |
491 | mpz_init(this->n); |
492 | mpz_init(this->e); | |
7daf5226 | 493 | |
7033a70f MW |
494 | mpz_import(this->n, n.len, 1, 1, 1, 0, n.ptr); |
495 | mpz_import(this->e, e.len, 1, 1, 1, 0, e.ptr); | |
7daf5226 | 496 | |
741680d1 | 497 | this->k = (mpz_sizeinbase(this->n, 2) + 7) / BITS_PER_BYTE; |
7daf5226 | 498 | |
6681d98d TB |
499 | if (!mpz_sgn(this->e)) |
500 | { | |
501 | destroy(this); | |
502 | return NULL; | |
503 | } | |
552cc11b MW |
504 | return &this->public; |
505 | } |