Skip to content

Commit c3a97ff

Browse files
authored
chore: refactor cert authn project to support one-step authentication/enrollment (#13184)
* feat: add flows to support enrollment/authn scenarios #13183 Signed-off-by: jgomer2001 <bonustrack310@gmail.com> * feat: add supporting UI templates #13183 Signed-off-by: jgomer2001 <bonustrack310@gmail.com> * feat: implement cert handling logic #13183 Signed-off-by: jgomer2001 <bonustrack310@gmail.com> * fix: fix compilation problems #13183 Signed-off-by: jgomer2001 <bonustrack310@gmail.com> * chore: make rabbit happy #13183 Signed-off-by: jgomer2001 <bonustrack310@gmail.com> * chore: fix compilation problem #13183 Signed-off-by: jgomer2001 <bonustrack310@gmail.com> --------- Signed-off-by: jgomer2001 <bonustrack310@gmail.com>
1 parent 7ccd3c8 commit c3a97ff

16 files changed

+867
-39
lines changed
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
Flow io.jans.casa.authn.cert
22
Basepath ""
3-
Configs conf
4-
Inputs userData
3+
Inputs userData withEscape
4+
5+
obj = Trigger io.jans.casa.cert.promptAndValidate
6+
When obj.success is true
7+
uid = Call io.jans.casa.certauthn.CertUtil#findOwner obj.data.cert.fingerPrint
58

6-
cah = Call io.jans.casa.certauthn.CertAuthnHelper#new userData.inum conf.certPickupUrl conf.roundTripMaxTime
7-
url = Call cah buildRedirectUrl
8-
//See template index.zul and class io.jans.casa.plugins.certauthn.vm.CertAuthnVM in casa plugin
9-
RFAC url
9+
When uid is userData.uid
10+
Finish uid
1011

11-
outcome = Call cah retrieveOutcome
12-
When outcome is ""
13-
Finish true
12+
When uid is null
13+
obj = { error: "NOT_RECOGNIZED" }
14+
Otherwise
15+
obj = { error: "CERT_ENROLLED_OTHER_USER" }
1416

15-
When outcome is null //the cache entry expired
16-
Log "Cert Authentication failed."
17-
Otherwise
18-
Log "Cert Authentication failed. Reason:" outcome
17+
RRF "authn-error.ftlh" obj
1918

19+
Log "Cert Authentication failed"
2020
Finish false
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Flow io.jans.casa.cert.enroll
2+
Basepath ""
3+
Inputs uidRef
4+
5+
uid = Call io.jans.casa.certauthn.CertAuthnHelper#decryptValue uidRef
6+
obj = Trigger io.jans.casa.cert.promptAndValidate
7+
validated = obj.success
8+
9+
When validated is true
10+
c = obj.data.cert
11+
status = Call io.jans.casa.certauthn.CertUtil#enroll uid c.x509 c.fingerPrint
12+
13+
obj = { status: status }
14+
//Show feedback to user before returning to Casa
15+
RRF "enroll-feedback.ftlh" obj
16+
17+
//Always terminate successfully (there was session already in Casa)
18+
Finish uid
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Flow io.jans.casa.cert.oneStepAuthn
2+
Basepath ""
3+
Configs conf
4+
5+
When conf.useAcctLinking is true
6+
obj = Trigger io.jans.casa.authn.acctlinking
7+
Override templates "blb4x/acctlinking.ftlh" "login-acctlinking.ftlh"
8+
9+
When obj.aborted is null //User took usual Casa authentication, terminate as such
10+
Finish obj
11+
12+
When obj.data."_abort".length is 0
13+
pid = obj.data.providerId
14+
Log "User selected identity provider" pid
15+
16+
//Launch the flow again, this time preselecting the given identity provider
17+
obj = Trigger io.jans.casa.authn.acctlinking pid null
18+
Finish obj
19+
20+
Otherwise
21+
obj = Trigger io.jans.casa.authn.main
22+
Override templates "kz1vc3/main.ftlh" "login.ftlh"
23+
24+
When obj.aborted is null //User took usual Casa authentication, terminate as such
25+
Finish obj
26+
27+
Log "Smart card/User cert option selected"
28+
obj = Trigger io.jans.casa.cert.promptAndValidate
29+
30+
When obj.success is true
31+
fingerPrint = obj.data.cert.fingerPrint
32+
x509 = obj.data.cert.x509
33+
uid = Call io.jans.casa.certauthn.CertUtil#findOwner fingerPrint
34+
35+
When uid is not null
36+
Finish uid
37+
38+
uid = Call io.jans.casa.certauthn.CertUtil#register x509 conf.mappingClassField
39+
status = Call io.jans.casa.certauthn.CertUtil#enroll uid x509 fingerPrint
40+
status = Call status toString
41+
42+
When status is "SUCCESS"
43+
Finish uid
44+
45+
obj = { success: false }
46+
47+
Finish obj
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Flow io.jans.casa.cert.promptAndValidate
2+
Basepath ""
3+
Configs conf
4+
5+
cah = Call io.jans.casa.certauthn.CertAuthnHelper#new conf.certPickupUrl conf.roundTripMaxTime
6+
url = Call cah buildRedirectUrl
7+
//See template index.zul and class io.jans.casa.plugins.certauthn.vm.CertAuthnVM in casa plugin
8+
RFAC url
9+
10+
validationResult = Call io.jans.casa.certauthn.CertUtil#validate cah.certPEM conf.certChainPEM
11+
status = Call validationResult.first toString
12+
13+
When status is "VALID"
14+
x509Cert = validationResult.second
15+
fp = Call io.jans.casa.certauthn.CertUtil#getFingerPrint x509Cert
16+
obj = { success: true, data: { cert: { x509: x509Cert, fingerPrint: fp } } }
17+
Finish obj
18+
19+
obj = { error: status }
20+
RRF "validation-error.ftlh" obj
21+
Finish false
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.jans.casa.certauthn;
2+
3+
import java.util.function.UnaryOperator;
4+
import java.util.*;
5+
6+
/**
7+
* Fields of this class can be referenced in the config properties of flow
8+
* io.jans.casa.cert.oneStepAuthn, see mappingClassField
9+
*/
10+
public final class AttributeMappings {
11+
12+
//Maps some "incoming attributes" in the certificate to build a user profile
13+
public static final UnaryOperator<Map<String, String>> STRAIGHT =
14+
15+
attributes -> {
16+
String[] attrNames = new String[] {
17+
"o", //Organization Name
18+
"cn", //Common Name
19+
"l", //City
20+
"mail" //e-mail
21+
};
22+
String attrForUid = "mail";
23+
24+
Map<String, String> profile = new HashMap<>();
25+
profile.put("uid", attributes.get(attrForUid));
26+
27+
//Straight (one-to-one) copy of attributes from source to destination map
28+
List.of(attrNames).forEach(attr ->
29+
profile.put(attr, attributes.get(attr)));
30+
31+
return profile;
32+
};
33+
34+
private AttributeMappings() { }
35+
36+
}

jans-casa/plugins/cert-authn/agama/project/lib/io/jans/casa/certauthn/CertAuthnHelper.java

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@
44
import io.jans.service.cdi.util.CdiUtil;
55
import io.jans.util.security.StringEncrypter;
66

7-
import org.json.JSONObject;
8-
97
import java.net.URLEncoder;
108
import java.security.SecureRandom;
119
import java.util.Optional;
1210

11+
import org.slf4j.*;
12+
1313
import static java.nio.charset.StandardCharsets.UTF_8;
1414

1515
public class CertAuthnHelper {
1616

1717
private static final String RND_KEY = "ref";
18-
private static final SecureRandom RND = new SecureRandom();
18+
private static final int DEFAULT_ROUND_TRIP_MAX_TIME = 45;
1919

20+
private static SecureRandom RND = new SecureRandom();
21+
private static Logger logger = LoggerFactory.getLogger(CertAuthnHelper.class);
2022
private static CacheService cs = CdiUtil.bean(CacheService.class);
21-
private String userId;
23+
private static StringEncrypter encrypter = CdiUtil.bean(StringEncrypter.class);
24+
2225
private String certPickupUrl;
2326
private String key;
2427

@@ -28,39 +31,47 @@ public class CertAuthnHelper {
2831

2932
public CertAuthnHelper() {}
3033

31-
public CertAuthnHelper(String inum, String certPickupUrl, int roundTripMaxTime) {
34+
public CertAuthnHelper(String certPickupUrl, Integer rtmt) {
35+
36+
if (rtmt == null || rtmt < 10) {
37+
logger.warn("roundTripMaxTime provided is of no practical use. Setting a default value...");
38+
this.roundTripMaxTime = DEFAULT_ROUND_TRIP_MAX_TIME;
39+
} else {
40+
this.roundTripMaxTime = rtmt;
41+
}
3242

33-
this.userId = inum;
3443
this.certPickupUrl = certPickupUrl;
35-
this.roundTripMaxTime = roundTripMaxTime;
3644
this.key = ("" + RND.nextDouble()).substring(2);
3745

3846
}
3947

4048
public String buildRedirectUrl() throws StringEncrypter.EncryptionException {
41-
42-
String encKey = CdiUtil.bean(StringEncrypter.class).encrypt(key);
49+
50+
String encKey = encrypter.encrypt(key);
4351
encKey = URLEncoder.encode(encKey, UTF_8);
44-
45-
//See plugin's class io.jans.casa.plugins.certauthn.model.Reference
46-
JSONObject job = new JSONObject();
47-
job.put("userId", userId);
48-
job.put("enroll", false);
49-
job.put("expiresAt", System.currentTimeMillis() + 1000L*roundTripMaxTime);
5052

51-
cs.put(roundTripMaxTime, key, job.toString());
53+
cs.put(roundTripMaxTime, key, System.currentTimeMillis() + 1000L*roundTripMaxTime);
5254
return certPickupUrl + "?" + RND_KEY + "=" + encKey;
5355

5456
}
55-
56-
public String retrieveOutcome() {
57-
58-
String val = Optional.ofNullable(cs.get(key)).map(Object::toString).orElse(null);
59-
if (val != null) {
57+
58+
public String getCertPEM() {
59+
60+
//See class io.jans.casa.plugins.certauthn.vm.CertAuthnVM
61+
String cert = Optional.ofNullable(cs.get(key)).map(Object::toString).orElse(null);
62+
if (cert != null) {
6063
cs.remove(key);
64+
65+
if (cert.equals("(null)")) { //Apache server may send '(null)'
66+
cert = null;
67+
}
6168
}
62-
return val;
69+
return cert;
6370

6471
}
6572

73+
public static String decryptValue(String val) throws StringEncrypter.EncryptionException {
74+
return encrypter.decrypt(val);
75+
}
76+
6677
}

0 commit comments

Comments
 (0)