Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
41159 views
1
/*
2
* Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package sun.security.krb5;
27
28
import java.io.IOException;
29
import java.util.Arrays;
30
import javax.security.auth.kerberos.KeyTab;
31
import sun.security.jgss.krb5.Krb5Util;
32
import sun.security.krb5.internal.HostAddresses;
33
import sun.security.krb5.internal.KDCOptions;
34
import sun.security.krb5.internal.KRBError;
35
import sun.security.krb5.internal.KerberosTime;
36
import sun.security.krb5.internal.Krb5;
37
import sun.security.krb5.internal.PAData;
38
import sun.security.krb5.internal.crypto.EType;
39
40
/**
41
* A manager class for AS-REQ communications.
42
*
43
* This class does:
44
* 1. Gather information to create AS-REQ
45
* 2. Create and send AS-REQ
46
* 3. Receive AS-REP and KRB-ERROR (-KRB_ERR_RESPONSE_TOO_BIG) and parse them
47
* 4. Emit credentials and secret keys (for JAAS storeKey=true with password)
48
*
49
* This class does not:
50
* 1. Deal with real communications (KdcComm does it, and TGS-REQ)
51
* a. Name of KDCs for a realm
52
* b. Server availability, timeout, UDP or TCP
53
* d. KRB_ERR_RESPONSE_TOO_BIG
54
* 2. Stores its own copy of password, this means:
55
* a. Do not change/wipe it before Builder finish
56
* b. Builder will not wipe it for you
57
*
58
* With this class:
59
* 1. KrbAsReq has only one constructor
60
* 2. Krb5LoginModule and Kinit call a single builder
61
* 3. Better handling of sensitive info
62
*
63
* @since 1.7
64
*/
65
66
public final class KrbAsReqBuilder {
67
68
// Common data for AS-REQ fields
69
private KDCOptions options;
70
private PrincipalName cname;
71
private PrincipalName refCname; // May be changed by referrals
72
private PrincipalName sname;
73
private KerberosTime from;
74
private KerberosTime till;
75
private KerberosTime rtime;
76
private HostAddresses addresses;
77
78
// Secret source: can't be changed once assigned, only one (of the two
79
// sources) can be set to non-null
80
private final char[] password;
81
private final KeyTab ktab;
82
83
// Used to create a ENC-TIMESTAMP in the 2nd AS-REQ
84
private PAData[] paList; // PA-DATA from both KRB-ERROR and AS-REP.
85
// Used by getKeys() only.
86
// Only AS-REP should be enough per RFC,
87
// combined in case etypes are different.
88
89
// The generated and received:
90
private KrbAsReq req;
91
private KrbAsRep rep;
92
93
private static enum State {
94
INIT, // Initialized, can still add more initialization info
95
REQ_OK, // AS-REQ performed
96
DESTROYED, // Destroyed, not usable anymore
97
}
98
private State state;
99
100
// Called by other constructors
101
private void init(PrincipalName cname)
102
throws KrbException {
103
this.cname = cname;
104
this.refCname = cname;
105
state = State.INIT;
106
}
107
108
/**
109
* Creates a builder to be used by {@code cname} with existing keys.
110
*
111
* @param cname the client of the AS-REQ. Must not be null. Might have no
112
* realm, where default realm will be used. This realm will be the target
113
* realm for AS-REQ. I believe a client should only get initial TGT from
114
* its own realm.
115
* @param ktab must not be null. If empty, might be quite useless.
116
* This argument will neither be modified nor stored by the method.
117
* @throws KrbException
118
*/
119
public KrbAsReqBuilder(PrincipalName cname, KeyTab ktab)
120
throws KrbException {
121
init(cname);
122
this.ktab = ktab;
123
this.password = null;
124
}
125
126
/**
127
* Creates a builder to be used by {@code cname} with a known password.
128
*
129
* @param cname the client of the AS-REQ. Must not be null. Might have no
130
* realm, where default realm will be used. This realm will be the target
131
* realm for AS-REQ. I believe a client should only get initial TGT from
132
* its own realm.
133
* @param pass must not be null. This argument will neither be modified
134
* nor stored by the method.
135
* @throws KrbException
136
*/
137
public KrbAsReqBuilder(PrincipalName cname, char[] pass)
138
throws KrbException {
139
init(cname);
140
this.password = pass.clone();
141
this.ktab = null;
142
}
143
144
/**
145
* Retrieves an array of secret keys for the client. This is used when
146
* the client supplies password but need keys to act as an acceptor. For
147
* an initiator, it must be called after AS-REQ is performed (state is OK).
148
* For an acceptor, it can be called when this KrbAsReqBuilder object is
149
* constructed (state is INIT).
150
* @param isInitiator if the caller is an initiator
151
* @return generated keys from password. PA-DATA from server might be used.
152
* All "default_tkt_enctypes" keys will be generated, Never null.
153
* @throws IllegalStateException if not constructed from a password
154
* @throws KrbException
155
*/
156
public EncryptionKey[] getKeys(boolean isInitiator) throws KrbException {
157
checkState(isInitiator?State.REQ_OK:State.INIT, "Cannot get keys");
158
if (password != null) {
159
int[] eTypes = EType.getDefaults("default_tkt_enctypes");
160
EncryptionKey[] result = new EncryptionKey[eTypes.length];
161
162
/*
163
* Returns an array of keys. Before KrbAsReqBuilder, all etypes
164
* use the same salt which is either the default one or a new salt
165
* coming from PA-DATA. After KrbAsReqBuilder, each etype uses its
166
* own new salt from PA-DATA. For an etype with no PA-DATA new salt
167
* at all, what salt should it use?
168
*
169
* Commonly, the stored keys are only to be used by an acceptor to
170
* decrypt service ticket in AP-REQ. Most impls only allow keys
171
* from a keytab on acceptor, but unfortunately (?) Java supports
172
* acceptor using password. In this case, if the service ticket is
173
* encrypted using an etype which we don't have PA-DATA new salt,
174
* using the default salt might be wrong (say, case-insensitive
175
* user name). Instead, we would use the new salt of another etype.
176
*/
177
178
String salt = null; // the saved new salt
179
try {
180
for (int i=0; i<eTypes.length; i++) {
181
// First round, only calculate those have a PA entry
182
PAData.SaltAndParams snp =
183
PAData.getSaltAndParams(eTypes[i], paList);
184
if (snp != null) {
185
// Never uses a salt for rc4-hmac, it does not use
186
// a salt at all
187
if (eTypes[i] != EncryptedData.ETYPE_ARCFOUR_HMAC &&
188
snp.salt != null) {
189
salt = snp.salt;
190
}
191
result[i] = EncryptionKey.acquireSecretKey(cname,
192
password,
193
eTypes[i],
194
snp);
195
}
196
}
197
// No new salt from PA, maybe empty, maybe only rc4-hmac
198
if (salt == null) salt = cname.getSalt();
199
for (int i=0; i<eTypes.length; i++) {
200
// Second round, calculate those with no PA entry
201
if (result[i] == null) {
202
result[i] = EncryptionKey.acquireSecretKey(password,
203
salt,
204
eTypes[i],
205
null);
206
}
207
}
208
} catch (IOException ioe) {
209
KrbException ke = new KrbException(Krb5.ASN1_PARSE_ERROR);
210
ke.initCause(ioe);
211
throw ke;
212
}
213
return result;
214
} else {
215
throw new IllegalStateException("Required password not provided");
216
}
217
}
218
219
/**
220
* Sets or clears options. If cleared, default options will be used
221
* at creation time.
222
* @param options
223
*/
224
public void setOptions(KDCOptions options) {
225
checkState(State.INIT, "Cannot specify options");
226
this.options = options;
227
}
228
229
public void setTill(KerberosTime till) {
230
checkState(State.INIT, "Cannot specify till");
231
this.till = till;
232
}
233
234
public void setRTime(KerberosTime rtime) {
235
checkState(State.INIT, "Cannot specify rtime");
236
this.rtime = rtime;
237
}
238
239
/**
240
* Sets or clears target. If cleared, KrbAsReq might choose krbtgt
241
* for cname realm
242
* @param sname
243
*/
244
public void setTarget(PrincipalName sname) {
245
checkState(State.INIT, "Cannot specify target");
246
this.sname = sname;
247
}
248
249
/**
250
* Adds or clears addresses. KrbAsReq might add some if empty
251
* field not allowed
252
* @param addresses
253
*/
254
public void setAddresses(HostAddresses addresses) {
255
checkState(State.INIT, "Cannot specify addresses");
256
this.addresses = addresses;
257
}
258
259
/**
260
* Build a KrbAsReq object from all info fed above. Normally this method
261
* will be called twice: initial AS-REQ and second with pakey
262
* @param key null (initial AS-REQ) or pakey (with preauth)
263
* @return the KrbAsReq object
264
* @throws KrbException
265
* @throws IOException
266
*/
267
private KrbAsReq build(EncryptionKey key, ReferralsState referralsState)
268
throws KrbException, IOException {
269
PAData[] extraPAs = null;
270
int[] eTypes;
271
if (password != null) {
272
eTypes = EType.getDefaults("default_tkt_enctypes");
273
} else {
274
EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
275
eTypes = EType.getDefaults("default_tkt_enctypes",
276
ks);
277
for (EncryptionKey k: ks) k.destroy();
278
}
279
options = (options == null) ? new KDCOptions() : options;
280
if (referralsState.isEnabled()) {
281
if (referralsState.sendCanonicalize()) {
282
options.set(KDCOptions.CANONICALIZE, true);
283
}
284
extraPAs = new PAData[]{ new PAData(Krb5.PA_REQ_ENC_PA_REP,
285
new byte[]{}) };
286
} else {
287
options.set(KDCOptions.CANONICALIZE, false);
288
}
289
return new KrbAsReq(key,
290
options,
291
refCname,
292
sname,
293
from,
294
till,
295
rtime,
296
eTypes,
297
addresses,
298
extraPAs);
299
}
300
301
/**
302
* Parses AS-REP, decrypts enc-part, retrieves ticket and session key
303
* @throws KrbException
304
* @throws Asn1Exception
305
* @throws IOException
306
*/
307
private KrbAsReqBuilder resolve()
308
throws KrbException, Asn1Exception, IOException {
309
if (ktab != null) {
310
rep.decryptUsingKeyTab(ktab, req, cname);
311
} else {
312
rep.decryptUsingPassword(password, req, cname);
313
}
314
if (rep.getPA() != null) {
315
if (paList == null || paList.length == 0) {
316
paList = rep.getPA();
317
} else {
318
int extraLen = rep.getPA().length;
319
if (extraLen > 0) {
320
int oldLen = paList.length;
321
paList = Arrays.copyOf(paList, paList.length + extraLen);
322
System.arraycopy(rep.getPA(), 0, paList, oldLen, extraLen);
323
}
324
}
325
}
326
return this;
327
}
328
329
/**
330
* Communication until AS-REP or non preauth-related KRB-ERROR received
331
* @throws KrbException
332
* @throws IOException
333
*/
334
private KrbAsReqBuilder send() throws KrbException, IOException {
335
boolean preAuthFailedOnce = false;
336
KdcComm comm = null;
337
EncryptionKey pakey = null;
338
ReferralsState referralsState = new ReferralsState(this);
339
while (true) {
340
if (referralsState.refreshComm()) {
341
comm = new KdcComm(refCname.getRealmAsString());
342
}
343
try {
344
req = build(pakey, referralsState);
345
rep = new KrbAsRep(comm.send(req.encoding()));
346
return this;
347
} catch (KrbException ke) {
348
if (!preAuthFailedOnce && (
349
ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED ||
350
ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
351
if (Krb5.DEBUG) {
352
System.out.println("KrbAsReqBuilder: " +
353
"PREAUTH FAILED/REQ, re-send AS-REQ");
354
}
355
preAuthFailedOnce = true;
356
KRBError kerr = ke.getError();
357
int paEType = PAData.getPreferredEType(kerr.getPA(),
358
EType.getDefaults("default_tkt_enctypes")[0]);
359
if (password == null) {
360
EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
361
pakey = EncryptionKey.findKey(paEType, ks);
362
if (pakey != null) pakey = (EncryptionKey)pakey.clone();
363
for (EncryptionKey k: ks) k.destroy();
364
} else {
365
pakey = EncryptionKey.acquireSecretKey(cname,
366
password,
367
paEType,
368
PAData.getSaltAndParams(
369
paEType, kerr.getPA()));
370
}
371
paList = kerr.getPA(); // Update current paList
372
} else {
373
if (referralsState.handleError(ke)) {
374
pakey = null;
375
preAuthFailedOnce = false;
376
continue;
377
}
378
throw ke;
379
}
380
}
381
}
382
}
383
384
static final class ReferralsState {
385
private static boolean canonicalizeConfig;
386
private boolean enabled;
387
private boolean sendCanonicalize;
388
private boolean isEnterpriseCname;
389
private int count;
390
private boolean refreshComm;
391
private KrbAsReqBuilder reqBuilder;
392
393
static {
394
initStatic();
395
}
396
397
// Config may be refreshed while running so the setting
398
// value may need to be updated. See Config::refresh.
399
static void initStatic() {
400
canonicalizeConfig = false;
401
try {
402
canonicalizeConfig = Config.getInstance()
403
.getBooleanObject("libdefaults", "canonicalize") ==
404
Boolean.TRUE;
405
} catch (KrbException e) {
406
if (Krb5.DEBUG) {
407
System.out.println("Exception in getting canonicalize," +
408
" using default value " +
409
Boolean.valueOf(canonicalizeConfig) + ": " +
410
e.getMessage());
411
}
412
}
413
}
414
415
ReferralsState(KrbAsReqBuilder reqBuilder) throws KrbException {
416
this.reqBuilder = reqBuilder;
417
sendCanonicalize = canonicalizeConfig;
418
isEnterpriseCname = reqBuilder.refCname.getNameType() ==
419
PrincipalName.KRB_NT_ENTERPRISE;
420
updateStatus();
421
if (!enabled && isEnterpriseCname) {
422
throw new KrbException("NT-ENTERPRISE principals only" +
423
" allowed when referrals are enabled.");
424
}
425
refreshComm = true;
426
}
427
428
private void updateStatus() {
429
enabled = !Config.DISABLE_REFERRALS &&
430
(isEnterpriseCname || sendCanonicalize);
431
}
432
433
boolean handleError(KrbException ke) throws RealmException {
434
if (enabled) {
435
if (ke.returnCode() == Krb5.KRB_ERR_WRONG_REALM) {
436
Realm referredRealm = ke.getError().getClientRealm();
437
if (referredRealm != null &&
438
!referredRealm.toString().isEmpty() &&
439
count < Config.MAX_REFERRALS) {
440
// A valid referral was received while referrals
441
// were enabled. Change the cname realm to the referred
442
// realm and set refreshComm to send a new request.
443
reqBuilder.refCname = new PrincipalName(
444
reqBuilder.refCname.getNameType(),
445
reqBuilder.refCname.getNameStrings(),
446
referredRealm);
447
refreshComm = true;
448
count++;
449
return true;
450
}
451
}
452
if (count < Config.MAX_REFERRALS && sendCanonicalize) {
453
if (Krb5.DEBUG) {
454
System.out.println("KrbAsReqBuilder: AS-REQ failed." +
455
" Retrying with CANONICALIZE false.");
456
}
457
458
// Server returned an unexpected error with
459
// CANONICALIZE true. Retry with false.
460
sendCanonicalize = false;
461
462
// Setting CANONICALIZE to false may imply that referrals
463
// are now disabled (if cname is not of NT-ENTERPRISE type).
464
updateStatus();
465
466
return true;
467
}
468
}
469
return false;
470
}
471
472
boolean refreshComm() {
473
boolean retRefreshComm = refreshComm;
474
refreshComm = false;
475
return retRefreshComm;
476
}
477
478
boolean isEnabled() {
479
return enabled;
480
}
481
482
boolean sendCanonicalize() {
483
return sendCanonicalize;
484
}
485
}
486
487
/**
488
* Performs AS-REQ send and AS-REP receive.
489
* Maybe a state is needed here, to divide prepare process and getCreds.
490
* @throws KrbException
491
* @throws Asn1Exception
492
* @throws IOException
493
*/
494
public KrbAsReqBuilder action()
495
throws KrbException, Asn1Exception, IOException {
496
checkState(State.INIT, "Cannot call action");
497
state = State.REQ_OK;
498
return send().resolve();
499
}
500
501
/**
502
* Gets Credentials object after action
503
*/
504
public Credentials getCreds() {
505
checkState(State.REQ_OK, "Cannot retrieve creds");
506
return rep.getCreds();
507
}
508
509
/**
510
* Gets another type of Credentials after action
511
*/
512
public sun.security.krb5.internal.ccache.Credentials getCCreds() {
513
checkState(State.REQ_OK, "Cannot retrieve CCreds");
514
return rep.getCCreds();
515
}
516
517
/**
518
* Destroys the object and clears keys and password info.
519
*/
520
public void destroy() {
521
state = State.DESTROYED;
522
if (password != null) {
523
Arrays.fill(password, (char)0);
524
}
525
}
526
527
/**
528
* Checks if the current state is the specified one.
529
* @param st the expected state
530
* @param msg error message if state is not correct
531
* @throws IllegalStateException if state is not correct
532
*/
533
private void checkState(State st, String msg) {
534
if (state != st) {
535
throw new IllegalStateException(msg + " at " + st + " state");
536
}
537
}
538
}
539
540