Skip to content

Commit

Permalink
Support DGWS 1.0 and legacy SecurityTokenService (for test usage) + c…
Browse files Browse the repository at this point in the history
…hange IdCard API from LocalDateTime to ZonedDateTime
  • Loading branch information
jsotrifork committed Oct 24, 2024
1 parent 517fbcc commit 7659da9
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 36 deletions.
61 changes: 46 additions & 15 deletions src/main/java/com/trifork/unsealed/IdCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

Expand All @@ -29,6 +31,7 @@

public abstract class IdCard {
public static final String DEFAULT_SIGN_IDCARD_ENDPOINT = "/sts/services/NewSecurityTokenService";
public static final String LEGACY_SIGN_IDCARD_ENDPOINT = "/sts/services/SecurityTokenService";
public static final String DEFAULT_IDCARD_TO_TOKEN_ENDPOINT = "/sts/services/Sosi2OIOSaml";

protected static final String SOSI_IDCARD_TYPE = "sosi:IDCardType";
Expand All @@ -55,6 +58,7 @@ public abstract class IdCard {
protected String careProviderId;
protected String careProviderIdNameFormat;
protected String careProviderName;
protected boolean useLegacyDGWS_1_0;

protected Element signedIdCard;

Expand All @@ -65,8 +69,9 @@ public abstract class IdCard {
private String dgwsVersion;
private String idCardId;

protected IdCard(NSPEnv env, X509Certificate certificate, Key privateKey, String systemName) {
protected IdCard(NSPEnv env, boolean useLegacyDGWS_1_0, X509Certificate certificate, Key privateKey, String systemName) {
this.env = env;
this.useLegacyDGWS_1_0 = useLegacyDGWS_1_0;
this.certificate = certificate;
this.privateKey = privateKey;
this.systemName = systemName;
Expand Down Expand Up @@ -185,11 +190,26 @@ protected String getSamlAttributeNameFormat(XPathContext xpathContext, Element r
protected abstract void extractKeystoreOwnerInfo(X509Certificate cert);

/**
* Sign this IDCard, i.e., send at request to SOSI STS requesting a signed IDCard.
* Sign this IDCard, i.e., send at request to SOSI STS requesting a signed DGWS 1.0.1 IDCard.
*
* @throws Exception
*/
public void sign() throws Exception {
sign(false);
}

/**
* <p>Sign this IDCard, i.e., send at request to SOSI STS requesting a signed IDCard using the deprecated
* SecurityTokenService rather than the recommended NewSecurityTokenService</p>
* <p>Included for test usage - NOT RECOMMENDED FOR PRODUCTION!</p>
*
* @throws Exception
*/
public void signUsingLegacySTSService() throws Exception {
sign(true);
}

protected void sign(boolean useLegacySTSService) throws Exception {

Instant now = Instant.now();

Expand All @@ -209,16 +229,17 @@ public void sign() throws Exception {

SignatureUtil.sign(idcard, null, new String[] { "#IDCard" }, "OCESSignature", certificate, privateKey, true);

String endpoint = useLegacySTSService ? LEGACY_SIGN_IDCARD_ENDPOINT : DEFAULT_SIGN_IDCARD_ENDPOINT;

Element response = WSHelper.post(docBuilder, requestBody,
env.getStsBaseUrl() + DEFAULT_SIGN_IDCARD_ENDPOINT, "Issue");
env.getStsBaseUrl() + endpoint, "Issue");

XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
signedIdCard = (Element) xpath.evaluate("//*[@id='IDCard']", response.getOwnerDocument(), XPathConstants.NODE);
signedIdCard.setIdAttribute("id", true);

extractSamlAttributes(signedIdCard);

}

/**
Expand Down Expand Up @@ -372,7 +393,7 @@ private Element createUnsignedIdCard(Document doc, Certificate certificate, Inst

SamlUtil.addSamlAttribute(idCardData, "sosi:IDCardID", UUID.randomUUID().toString());

SamlUtil.addSamlAttribute(idCardData, "sosi:IDCardVersion", "1.0.1");
SamlUtil.addSamlAttribute(idCardData, "sosi:IDCardVersion", useLegacyDGWS_1_0 ? "1.0" : "1.0.1");

SamlUtil.addSamlAttribute(idCardData, "sosi:OCESCertHash", SignatureUtil.getDigestOfCertificate(certificate));

Expand Down Expand Up @@ -445,20 +466,20 @@ public Element serialize2DOMDocument(Document doc) {

/**
* Get the NotBefore condition (valid from time) of this IDCard
* @return The NotBefore condition as a {@link java.time.LocalDateTime}
* @return The NotBefore condition as a {@link java.time.ZonedDateTime}
*/
public LocalDateTime getNotBefore() {
public ZonedDateTime getNotBefore() {
Element cond = getChild(signedIdCard, NsPrefixes.saml, "Conditions");
return LocalDateTime.parse(cond.getAttribute("NotBefore"), ISO_WITHOUT_MILLIS_FORMATTER);
return parseDate(cond.getAttribute("NotBefore"));
}

/**
* Get the NotOnOrAfter condition (expiration time) of this IDCard
* @return The NotOnOrAftter condition as a {@link java.time.LocalDateTime}
* @return The NotOnOrAftter condition as a {@link java.time.ZonedDateTime}
*/
public LocalDateTime getNotOnOrAfter() {
public ZonedDateTime getNotOnOrAfter() {
Element cond = getChild(signedIdCard, NsPrefixes.saml, "Conditions");
return LocalDateTime.parse(cond.getAttribute("NotOnOrAfter"), ISO_WITHOUT_MILLIS_FORMATTER);
return parseDate(cond.getAttribute("NotOnOrAfter"));
}

/**
Expand All @@ -478,6 +499,16 @@ public Element getAssertion() {
return signedIdCard != null ? signedIdCard : assertion;
}

private ZonedDateTime parseDate(String dateTime) {
if ("1.0.1".equals(dgwsVersion)) {
return ZonedDateTime.parse(dateTime, ISO_WITHOUT_MILLIS_FORMATTER);
} else if ("1.0".equals(dgwsVersion)) {
return LocalDateTime.parse(dateTime).atZone(ZoneId.systemDefault());
} else {
throw new IllegalStateException("Unexpected DGWS version \"" + dgwsVersion + "\"");
}
}

private void validateSignature() throws ValidationException {
try {
// STS signs SOSI IdCards rsa-sha1 which is considered unsafe by Java 17..
Expand All @@ -487,17 +518,17 @@ private void validateSignature() throws ValidationException {
}
}

private void validateTimes(LocalDateTime now) throws ValidationException {
private void validateTimes(ZonedDateTime now) throws ValidationException {
if (now == null) {
now = LocalDateTime.now();
now = ZonedDateTime.now();
}

LocalDateTime notBefore = getNotBefore();
ZonedDateTime notBefore = getNotBefore();
if (now.isBefore(notBefore)) {
throw new ValidationException("NotBefore condition not met, NotBefore=" + notBefore + ", now=" + now);
}

LocalDateTime notOnOrAfter = getNotOnOrAfter();
ZonedDateTime notOnOrAfter = getNotOnOrAfter();
if (now.isEqual(notOnOrAfter) || now.isAfter(notOnOrAfter)) {
throw new ValidationException("NotOnOrAfter condition not met, NotOnOrAfter=" + notOnOrAfter + ", now=" + now);
}
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/com/trifork/unsealed/IdCardBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ public IdCardBuilder fromXml(String xml) {
return new IdCardBuilder(params);
}

/**
* If parameter is true, issue a legacy DGWS 1.0 IdCard (default is DGWS 1.0.1). Ignored if used in combination
* with {@link IdCardBuilder#fromXml} or {@link IdCardBuilder#assertion}
*
* @param useLegacyDGWS_1_0 If true use DGWS 1.0, otherwise DGWS 1.0.1
* @return A new immutable builder instance that encapsulates the supplied parameter
*/
public IdCardBuilder uselegacyDGWS_1_0(boolean useLegacyDGWS_1_0) {
IdCardBuilderParams params = this.params.copy();
params.useLegacyDGWS_1_0 = useLegacyDGWS_1_0;
return new IdCardBuilder(params);
}

/**
* Build IDCard from supplied parameters. If the builder is initialized from an assertion element or XML String, the type of IDCard will be autodetected (user
* or system).
Expand Down Expand Up @@ -249,7 +262,7 @@ public UserIdCard buildUserIdCard() throws IOException, KeyStoreException, NoSuc
fixIdAttribute(assertion);
idCard = new UserIdCard(params.env, assertion);
} else {
idCard = new UserIdCard(params.env, params.cpr, params.certAndKey.certificate,
idCard = new UserIdCard(params.env, params.useLegacyDGWS_1_0, params.cpr, params.certAndKey.certificate,
params.certAndKey.privateKey, params.email, params.role, params.occupation,
params.authorizationCode,
params.systemName);
Expand Down Expand Up @@ -287,7 +300,7 @@ public SystemIdCard buildSystemIdCard() throws IOException, KeyStoreException, N
idCard = new SystemIdCard(params.env, assertion);

} else {
idCard = new SystemIdCard(params.env, params.certAndKey.certificate, params.certAndKey.privateKey, params.systemName);
idCard = new SystemIdCard(params.env, params.useLegacyDGWS_1_0, params.certAndKey.certificate, params.certAndKey.privateKey, params.systemName);
}

return idCard;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class IdCardBuilderParams extends AbstractBuilderParams {
String systemName;
Element assertion;
String xml;
boolean useLegacyDGWS_1_0;
CertAndKey certAndKey;

IdCardBuilderParams copy() {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/trifork/unsealed/SystemIdCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class SystemIdCard extends IdCard {
private static final String ORG_ID = "OID.2.5.4.97";
private static final Pattern OI_PATTERN = Pattern.compile("NTRDK-(?<Cvr>[0-9]{1,8})");

protected SystemIdCard(NSPEnv env, X509Certificate certificate, Key privateKey, String systemName) {
super(env, certificate, privateKey, systemName);
protected SystemIdCard(NSPEnv env, boolean useLegacyDGWS_1_0, X509Certificate certificate, Key privateKey, String systemName) {
super(env, useLegacyDGWS_1_0, certificate, privateKey, systemName);
}

protected SystemIdCard(NSPEnv env, Element signedIdCard) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/trifork/unsealed/UserIdCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public class UserIdCard extends IdCard {
private String firstName;
private String lastName;

protected UserIdCard(NSPEnv env, String cpr, X509Certificate certificate, Key privateKey, String email, String role,
protected UserIdCard(NSPEnv env, boolean useLegacyDGWS_1_0, String cpr, X509Certificate certificate, Key privateKey, String email, String role,
String occupation, String authorizationCode, String systemName) {
super(env, certificate, privateKey, systemName);
super(env, useLegacyDGWS_1_0, certificate, privateKey, systemName);

this.cpr = cpr;
this.role = role;
Expand Down
10 changes: 4 additions & 6 deletions src/test/java/com/trifork/unsealed/BootstrapTokenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Base64;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
Expand Down Expand Up @@ -202,8 +200,8 @@ void canExchangeBootstrapTokenToIdCard() throws Exception {

UserIdCard userIdCard = bst.exchangeToUserIdCard("https://fmk", null, null, "J0184", "FMK-online");

assertTrue(userIdCard.getNotBefore().isBefore(LocalDateTime.now()));
assertTrue(userIdCard.getNotOnOrAfter().isAfter(LocalDateTime.now()));
assertTrue(userIdCard.getNotBefore().isBefore(ZonedDateTime.now()));
assertTrue(userIdCard.getNotOnOrAfter().isAfter(ZonedDateTime.now()));
assertEquals("Lars", userIdCard.getAttribute("medcom:UserGivenName"));
}

Expand All @@ -224,8 +222,8 @@ void canExchangeBootstrapTokenViaXmlToIdCard() throws Exception {

UserIdCard userIdCard = bst2.exchangeToUserIdCard("https://fmk", null, null, "MJP84", "FMK-online");

assertTrue(userIdCard.getNotBefore().isBefore(LocalDateTime.now()));
assertTrue(userIdCard.getNotOnOrAfter().isAfter(LocalDateTime.now()));
assertTrue(userIdCard.getNotBefore().isBefore(ZonedDateTime.now()));
assertTrue(userIdCard.getNotOnOrAfter().isAfter(ZonedDateTime.now()));
}

private String readFromClasspath(String path) throws IOException {
Expand Down
55 changes: 46 additions & 9 deletions src/test/java/com/trifork/unsealed/IDCardTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.LocalDateTime;
import java.time.ZonedDateTime;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
Expand Down Expand Up @@ -101,11 +101,11 @@ void canSignMoces2SystemIdCard() throws Exception {

assertTrue(serialized.contains("Assertion"));

LocalDateTime notBefore = idCard.getNotBefore();
assertTrue(notBefore.isBefore(LocalDateTime.now()));
ZonedDateTime notBefore = idCard.getNotBefore();
assertTrue(notBefore.isBefore(ZonedDateTime.now()));

LocalDateTime notOnOrAfter = idCard.getNotOnOrAfter();
assertTrue(notOnOrAfter.isAfter(LocalDateTime.now()));
ZonedDateTime notOnOrAfter = idCard.getNotOnOrAfter();
assertTrue(notOnOrAfter.isAfter(ZonedDateTime.now()));

idCard.validate();
}
Expand Down Expand Up @@ -134,11 +134,48 @@ void canSignSystemIdCard() throws Exception {

assertTrue(serialized.contains("Assertion"));

LocalDateTime notBefore = idCard.getNotBefore();
assertTrue(notBefore.isBefore(LocalDateTime.now()));
ZonedDateTime notBefore = idCard.getNotBefore();
assertTrue(notBefore.isBefore(ZonedDateTime.now()));

LocalDateTime notOnOrAfter = idCard.getNotOnOrAfter();
assertTrue(notOnOrAfter.isAfter(LocalDateTime.now()));
ZonedDateTime notOnOrAfter = idCard.getNotOnOrAfter();
assertTrue(notOnOrAfter.isAfter(ZonedDateTime.now()));

idCard.validate();
}

@Test
void canSignIdCardUsingLegacySTSService() throws Exception {

IdCard idCard = new IdCardBuilder()
.env(NSPTestEnv.TEST1_CNSP)
.certAndKey(moces3CertAndKey)
.cpr("0501792275")
.role("role")
.occupation("occupation")
.authorizationCode("J0184")
.systemName("systemname")
.buildUserIdCard();

idCard.signUsingLegacySTSService();

idCard.validate();
}

@Test
void canSignDGWS_1_0_IdCard() throws Exception {

IdCard idCard = new IdCardBuilder()
.uselegacyDGWS_1_0(true)
.env(NSPTestEnv.TEST1_CNSP)
.certAndKey(moces3CertAndKey)
.cpr("0501792275")
.role("role")
.occupation("occupation")
.authorizationCode("J0184")
.systemName("systemname")
.buildUserIdCard();

idCard.sign();

idCard.validate();
}
Expand Down

0 comments on commit 7659da9

Please sign in to comment.