|
| 1 | +A Basic Introduction to GSSAPI |
| 2 | +============================== |
| 3 | + |
| 4 | +GSSAPI (which stands for "Generic Security Service API") is an |
| 5 | +standard layer for interfacing with security services. While it |
| 6 | +supports multiple different mechanisms, it is most commonly used |
| 7 | +with Kerberos 5 ("krb5" for short). |
| 8 | + |
| 9 | +This tutorial will provide a basic introduction to interacting with |
| 10 | +GSSAPI through Python. |
| 11 | + |
| 12 | +*Note*: This file is designed to be runnable using |
| 13 | +[YALPT](https://github.com/directxman12/yalpt). You can also just |
| 14 | +read it normally. |
| 15 | + |
| 16 | +To start out, we'll import python-gssapi, and save the current FQDN |
| 17 | +for later: |
| 18 | + |
| 19 | + >>> import gssapi, socket |
| 20 | + >>> FQDN = socket.getfqdn() |
| 21 | + >>> |
| 22 | + |
| 23 | +Note that this assumes you have a KRB5 realm set up, and some relevant |
| 24 | +functions available in the `REALM` object (see gssapi-console.py, or |
| 25 | +try `$ run-lit -e gssapi basic-tutorial.md` when you have both |
| 26 | +gssapi-console and yalpt installed). Any actions performed using the |
| 27 | +`REALM` object are not part of the GSSAPI library; the `REALM` object |
| 28 | +simply contians wrappers to krb5 commands generally run separately from |
| 29 | +the application using GSSAPI. |
| 30 | + |
| 31 | +Names and Credentials |
| 32 | +--------------------- |
| 33 | + |
| 34 | +Two important concepts in GSSAPI are *names* and *credentials*. |
| 35 | + |
| 36 | +*Names*, as the name suggests, identify different entities, be they |
| 37 | +users or services. GSSAPI has the concept of different *name types*. |
| 38 | +These represent different types of names and corresponding sytaxes |
| 39 | +for representing names as strings. |
| 40 | + |
| 41 | +Suppose we wanted to refer to an HTTP server on the current host. |
| 42 | +We could refer to it as a *host-based service*, or in the default |
| 43 | +mechanism form (in this case, for krb5): |
| 44 | + |
| 45 | + >>> server_hostbased_name = gssapi.Name('http@' + FQDN, name_type=gssapi.NameType.hostbased_service) |
| 46 | + >>> server_hostbased_name |
| 47 | + Name(b'http@sross', <OID 1.2.840.113554.1.2.1.4>) |
| 48 | + >>> server_name = gssapi.Name('http/sross@') |
| 49 | + >>> server_name |
| 50 | + Name(b'http/sross@', None) |
| 51 | + >>> |
| 52 | + |
| 53 | +These are both effectively the same, but if we *canonicalize* both |
| 54 | +names with respect to krb5, we'll see that GSSAPI knows they're the |
| 55 | +same: |
| 56 | + |
| 57 | + >>> server_name == server_hostbased_name |
| 58 | + False |
| 59 | + >>> server_canon_name = server_name.canonicalize(gssapi.MechType.kerberos) |
| 60 | + >>> server_hostbased_canon_name = server_hostbased_name.canonicalize(gssapi.MechType.kerberos) |
| 61 | + >>> server_canon_name == server_hostbased_canon_name |
| 62 | + True |
| 63 | + >>> |
| 64 | + |
| 65 | +To compare two names of different name types, you should canonicalize |
| 66 | +them first. |
| 67 | + |
| 68 | +*Credentials* represent identification for a user or service. In |
| 69 | +order to establish secure communication with other entities, a user |
| 70 | +or service first needs credentials. For the krb5 mechanism, |
| 71 | +credentials generally represent a handle to the TGT. |
| 72 | + |
| 73 | +Credentials may be acquired for a particular name, or the default set |
| 74 | +of credentials may be acquired. |
| 75 | + |
| 76 | +For instance, suppose that we are writing a server, and wish to |
| 77 | +communicate accept connections as the 'http' service. We would need |
| 78 | +to acquire credentials as such: |
| 79 | + |
| 80 | + >>> REALM.addprinc('http/%s@%s' % (FQDN, REALM.realm)) |
| 81 | + >>> REALM.extract_keytab('http/%s@%s' % (FQDN, REALM.realm), REALM.keytab) |
| 82 | + >>> server_creds = gssapi.Credentials(usage='accept', name=server_name) |
| 83 | + >>> |
| 84 | + |
| 85 | +Note that for the krb5 mechanism, in order to acquire credentials with |
| 86 | +the GSSAPI, the system must already have a way to access those credentials. |
| 87 | +For users, this generally means that they have already performed a `kinit` |
| 88 | +(i.e. have cached a TGT), while for services (like above), having a keytab |
| 89 | +is sufficient. This process is generally performed outside the application |
| 90 | +using the GSSAPI. |
| 91 | + |
| 92 | +Credentials have a *usage*: 'accept' for accepting security contexts, |
| 93 | +'initiate' for initiating security contexts, or 'both' for |
| 94 | +credentials used for both initiating and accepting security contexts. |
| 95 | + |
| 96 | +Credentials also have an associated *name*, *lifetime* (which may |
| 97 | +be `None` for indefinite), and set of *mechansims* with which the |
| 98 | +credentials are usable: |
| 99 | + |
| 100 | + >>> server_creds.usage |
| 101 | + 'accept' |
| 102 | + >>> server_creds.name == server_name |
| 103 | + True |
| 104 | + >>> server_creds.lifetime is None |
| 105 | + True |
| 106 | + >>> gssapi.MechType.kerberos in server_creds.mechs |
| 107 | + True |
| 108 | + >>> gssapi.MechType.kerberos in server_creds.mechs |
| 109 | + True |
| 110 | + >>> |
| 111 | + |
| 112 | +Each of these settings is setable from the constructor as `usage`, |
| 113 | +`name`, `lifetime`, and `mechs`. |
| 114 | + |
| 115 | +Security Contexts |
| 116 | +----------------- |
| 117 | + |
| 118 | +*Security contexts* represent active sessions between two different |
| 119 | +entities. Security contexts are used to verify identities, as well |
| 120 | +as ensure *integrity* (message signing), *confidentiality* (message |
| 121 | +encryption), or both for messages exchanged between the two parties. |
| 122 | + |
| 123 | +When establishing a security context, the default credentials are |
| 124 | +used unless otherwise specified. This allows applications to use |
| 125 | +the user's already acquired credentials: |
| 126 | + |
| 127 | + >>> client_ctx = gssapi.SecurityContext(name=server_name, usage='initiate') |
| 128 | + >>> initial_client_token = client_ctx.step() |
| 129 | + >>> client_ctx.complete |
| 130 | + False |
| 131 | + >>> |
| 132 | + |
| 133 | +Just like credentials, security contexts are either initiating |
| 134 | +contexts, or accepting contexts (they cannot be both). Initating |
| 135 | +contexts must specify at least a target name. In this case, |
| 136 | +we indicate that we wish to establish a context with the HTTP server |
| 137 | +from above. The http server can then accept that context: |
| 138 | + |
| 139 | + >>> server_ctx = gssapi.SecurityContext(creds=server_creds, usage='accept') |
| 140 | + >>> initial_server_token = server_ctx.step(initial_client_token) |
| 141 | + >>> |
| 142 | + |
| 143 | +As you can see, creating an accepting security context is similar. |
| 144 | +Here, we specify a set of accepting credentials to use, although |
| 145 | +this is optional (the defaults will be used if no credentials are |
| 146 | +specified). |
| 147 | + |
| 148 | +Let's finish up the exchange: |
| 149 | + |
| 150 | + >>> server_tok = initial_server_token |
| 151 | + >>> |
| 152 | + >>> while not (client_ctx.complete and server_ctx.complete): |
| 153 | + ... client_tok = client_ctx.step(server_tok) |
| 154 | + ... if not client_tok: |
| 155 | + ... break |
| 156 | + ... server_tok = server_ctx.step(client_tok) |
| 157 | + ... |
| 158 | + >>> client_ctx.complete and server_ctx.complete |
| 159 | + True |
| 160 | + >>> |
| 161 | + |
| 162 | +We can now wrap and unwrap messages, using the `wrap` and `unwrap` methods |
| 163 | +on `SecurityContext`: |
| 164 | + |
| 165 | + >>> message = b'some message here' |
| 166 | + >>> wrapped_message, msg_encrypted = client_ctx.wrap(message, True) |
| 167 | + >>> message not in wrapped_message |
| 168 | + True |
| 169 | + >>> msg_encrypted |
| 170 | + True |
| 171 | + >>> server_ctx.unwrap(wrapped_message) |
| 172 | + UnwrapResult(message=b'some message here', encrypted=True, qop=0) |
| 173 | + >>> |
| 174 | + |
| 175 | +We can use the second parameter to control whether or not we encrypt the |
| 176 | +messages, or just sign them: |
| 177 | + |
| 178 | + >>> signed_message, msg_encrypted = client_ctx.wrap(message, False) |
| 179 | + >>> msg_encrypted |
| 180 | + False |
| 181 | + >>> message in signed_message |
| 182 | + True |
| 183 | + >>> server_ctx.unwrap(signed_message) |
| 184 | + UnwrapResult(message=b'some message here', encrypted=False, qop=0) |
| 185 | + >>> |
| 186 | + |
| 187 | +Manually passing in a second parameter and checking whether or not encryption |
| 188 | +was used can get tedious, so python-gssapi provides two convinience methods |
| 189 | +to help with this: `encrypt` and `decrypt`. If the context is set up to use |
| 190 | +encryption, they will call `wrap` with encryption. If not, they will |
| 191 | +call `wrap` without encryption. |
| 192 | + |
| 193 | + >>> encrypted_message = client_ctx.encrypt(message) |
| 194 | + >>> encrypted_message != message |
| 195 | + True |
| 196 | + >>> server_ctx.decrypt(encrypted_message) |
| 197 | + b'some message here' |
| 198 | + >>> |
| 199 | + |
| 200 | +Notice that if we try to use `decrypt` a signed message, and exception will be raised, |
| 201 | +since the context was set up to use encryption (the default): |
| 202 | + |
| 203 | + >>> signed_message, _ = client_ctx.wrap(message, False) |
| 204 | + >>> server_ctx.decrypt(signed_message) |
| 205 | + Traceback (most recent call last): |
| 206 | + File "<stdin>", line 1, in <module> |
| 207 | + File "<string>", line 2, in decrypt |
| 208 | + File "/home/directxman12/dev/gssapi/gssapi-console/.venv/lib/python3.4/site-packages/gssapi/_utils.py", line 167, in check_last_err |
| 209 | + return func(self, *args, **kwargs) |
| 210 | + File "/home/directxman12/dev/gssapi/gssapi-console/.venv/lib/python3.4/site-packages/gssapi/sec_contexts.py", line 295, in decrypt |
| 211 | + unwrapped_message=res.message) |
| 212 | + gssapi.exceptions.EncryptionNotUsed: Confidentiality was requested, but not used: The context was established with encryption, but unwrapped message was not encrypted. |
| 213 | + >>> |
| 214 | + |
| 215 | +There you have it: the basics of GSSAPI. You can use the `help` function |
| 216 | +at the interpreter, or check the [docs](http://pythonhosted.org/gssapi/) |
| 217 | +for more information. |
0 commit comments