Skip to content

Commit

Permalink
Support both oiosaml2 and oiosaml3 attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
jsotrifork committed Jan 14, 2025
1 parent 827f891 commit fe609b5
Show file tree
Hide file tree
Showing 13 changed files with 549 additions and 318 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/trifork/unsealed/BootstrapToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ public UserIdCard exchangeToUserIdCard(String audience, String role, String occu
NsPrefixes.wst13.name() + ":RequestedSecurityToken/" + NsPrefixes.saml.name()
+ ":Assertion");

return new IdCardBuilder().assertion(assertion).buildUserIdCard();
return new IdCardBuilder().env(env).assertion(assertion).buildUserIdCard();
}

public String getXml() {
Expand Down
52 changes: 39 additions & 13 deletions src/main/java/com/trifork/unsealed/BootstrapTokenIssuer.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ public BootstrapTokenIssuer orgName(String orgName) {
}

/**
* Specify the SP (Service Provider) {@link CertAndKey} (certificate keypair). This is used if the issued bootstrap token is exchanged to an IDWS IdentityToken or a DGWS
* Idcard.
* Specify the SP (Service Provider) {@link CertAndKey} (certificate keypair). This is used if the issued bootstrap token is exchanged to an IDWS
* IdentityToken or a DGWS Idcard.
*
* @see BootstrapToken#exchangeToIdentityToken(String, String)
* @see BootstrapToken#exchangeToIdentityToken(String, String, String)
* @see BootstrapToken#exchangeToUserIdCard(String, String, String, String, String)
Expand All @@ -118,6 +119,12 @@ public BootstrapTokenIssuer spCertAndKey(CertAndKey spCertAndKey) {
return new BootstrapTokenIssuer(params);
}

public BootstrapTokenIssuer spCert(X509Certificate spCert) {
var params = this.params.copy();
params.spCert = spCert;
return new BootstrapTokenIssuer(params);
}

/**
* Specify the IdP (Identify Provider) {@link CertAndKey} (certificate keypair). This will be the issuer of issued tokens.
*
Expand All @@ -131,7 +138,8 @@ public BootstrapTokenIssuer idpCertAndKey(CertAndKey idpCertAndKey) {
}

/**
* Issue a bootstrap token for a citizen.
* Issue a bootstrap token for a citizen.
*
* @return The issued bootstrap token
* @throws Exception
*/
Expand All @@ -155,12 +163,15 @@ public BootstrapToken issueForCitizen()

String xml = XmlUtil.node2String(assertion, false, false);

return new BootstrapToken(params.env, params.spCertAndKey.certificate, params.spCertAndKey.privateKey,
return new BootstrapToken(params.env,
params.spCertAndKey != null ? params.spCertAndKey.certificate : null,
params.spCertAndKey != null ? params.spCertAndKey.privateKey : null,
xml, null);
}

/**
* Issue a bootstrap token for a healthcare professional
*
* @return The issued bootstrap token
* @throws Exception
*/
Expand All @@ -187,7 +198,9 @@ public BootstrapToken issueForProfessional()

String xml = XmlUtil.node2String(assertion, false, false);

return new BootstrapToken(params.env, params.spCertAndKey.certificate, params.spCertAndKey.privateKey,
return new BootstrapToken(params.env,
params.spCertAndKey != null ? params.spCertAndKey.certificate : null,
params.spCertAndKey != null ? params.spCertAndKey.privateKey : null,
xml, null);
}

Expand Down Expand Up @@ -224,17 +237,30 @@ private Element createBootstrapToken(String audience, String nameId, String name
nameID.setAttribute("Format", nameIdFormat);

Element subjectConfirmation = appendChild(subject, NsPrefixes.saml.namespaceUri, "SubjectConfirmation");
subjectConfirmation.setAttribute("Method", "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key");
Element subjectConfirmationData = appendChild(subjectConfirmation, NsPrefixes.saml.namespaceUri,
"SubjectConfirmationData");
subjectConfirmationData.setAttributeNS(NsPrefixes.xsi.namespaceUri, "type",
"KeyInfoConfirmationDataType");

Element keyInfo = appendChild(subjectConfirmationData, NsPrefixes.ds, "KeyInfo");

Element x509Data = appendChild(keyInfo, NsPrefixes.ds, "X509Data");
appendChild(x509Data, NsPrefixes.ds, "X509Certificate",
Base64.getEncoder().encodeToString(params.spCertAndKey.certificate.getEncoded()));
X509Certificate spCert = params.spCert;
if (spCert == null && params.spCertAndKey != null) {
spCert = params.spCertAndKey.certificate;
}
if (spCert != null) {
// Service provider certificate is known, so issue a holder-of-key assertion
subjectConfirmation.setAttribute("Method", "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key");
subjectConfirmationData.setAttributeNS(NsPrefixes.xsi.namespaceUri, "type",
"KeyInfoConfirmationDataType");

Element keyInfo = appendChild(subjectConfirmationData, NsPrefixes.ds, "KeyInfo");

Element x509Data = appendChild(keyInfo, NsPrefixes.ds, "X509Data");
appendChild(x509Data, NsPrefixes.ds, "X509Certificate",
Base64.getEncoder().encodeToString(spCert.getEncoded()));
} else {
subjectConfirmation.setAttribute("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer");
subjectConfirmationData.setAttribute("NotOnOrAfter",
XmlUtil.ISO_WITHOUT_MILLIS_FORMATTER.format(now.plusSeconds(3600)));
subjectConfirmationData.setAttribute("Recipient", "https://sosi");
}

Element conditions = appendChild(assertion, NsPrefixes.saml.namespaceUri, "Conditions");
conditions.setAttribute("NotOnOrAfter",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.trifork.unsealed;

import java.security.cert.X509Certificate;

public class BootstrapTokenIssuerParams extends AbstractBuilderParams {
NSPEnv env;
String cpr;
String uuid;
String cvr;
String orgName;
X509Certificate spCert;
CertAndKey spCertAndKey;
CertAndKey idpCertAndKey;

Expand Down
64 changes: 49 additions & 15 deletions src/main/java/com/trifork/unsealed/IdCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,26 @@

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.parsers.DocumentBuilder;
Expand All @@ -28,6 +38,7 @@

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

public abstract class IdCard {
public static final String DEFAULT_SIGN_IDCARD_ENDPOINT = "/sts/services/NewSecurityTokenService";
Expand Down Expand Up @@ -199,9 +210,13 @@ public void sign() throws Exception {
}

/**
* <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>
* <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
*/
Expand Down Expand Up @@ -243,7 +258,7 @@ protected void sign(boolean useLegacySTSService) throws Exception {
}

/**
* Exchange this IDCard to an IDWS identity token that can be used for logging in via SBO (Safe Browser Start) on a web application
* Exchange this IDCard to an OIOSAML assertion that can be used for logging in via SBO (Safe Browser Start) on a web application
*
* @param audience
* The requested audience for the IDWS token, e.g. "https://saml.test1.fmk.netic.dk/fmk/". This equals the SAML EntityID of the target web
Expand All @@ -254,9 +269,21 @@ protected void sign(boolean useLegacySTSService) throws Exception {
* @throws InterruptedException
* @throws STSInvocationException
* @throws XPathExpressionException
* @throws SAXException
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws UnrecoverableKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
*/
public OIOSAMLToken exchangeToOIOSAMLToken(String audience) throws ParserConfigurationException, IOException,
InterruptedException, STSInvocationException, XPathExpressionException {
public OIOSAMLToken exchangeToOIOSAMLToken(String audience)
throws ParserConfigurationException, IOException, InterruptedException, STSInvocationException, XPathExpressionException,
UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, SAXException, InvalidKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
if (signedIdCard == null) {
throw new IllegalStateException("IdCard must be signed before it can be exchanged");
}
Expand All @@ -271,12 +298,12 @@ public OIOSAMLToken exchangeToOIOSAMLToken(String audience) throws ParserConfigu
Element requestedSecurityToken = xpath.findElement(IDCARD_TO_TOKEN_RESPONSE_XPATH);

assertion = XmlUtil.getChild(requestedSecurityToken, NsPrefixes.saml, "Assertion");
if (assertion != null) {
return new OIOSAMLToken(env, null, null, assertion, false);
if (assertion == null) {
assertion = XmlUtil.getChild(requestedSecurityToken, NsPrefixes.saml, "EncryptedAssertion");
}

encryptedAssertion = XmlUtil.getChild(requestedSecurityToken, NsPrefixes.saml, "EncryptedAssertion");
return new OIOSAMLToken(env, null, null, encryptedAssertion, true);
CertAndKey spCertAndKey = new CertAndKey(certificate, (PrivateKey) privateKey);
return new OIOSAMLTokenBuilder().env(env).spCertAndKey(spCertAndKey).assertion(assertion).build();
}

/**
Expand Down Expand Up @@ -430,7 +457,8 @@ public String getIssuer() {
/**
* Get any SAML attribute of this IDCard by name.
*
* @param attributeName The name of the attribute
* @param attributeName
* The name of the attribute
* @return
*/
public String getAttribute(String attributeName) {
Expand All @@ -443,7 +471,8 @@ public String getAttribute(String attributeName) {
}

/**
* The the subject name of the certificate represented by this IDCard.
* The the subject name of the certificate represented by this IDCard.
*
* @return The subject name
*/
public String getSubjectName() {
Expand All @@ -452,6 +481,7 @@ public String getSubjectName() {

/**
* Serialise this IDCard to a {@link org.w3c.dom.Document}
*
* @param doc
* @return The serialised document
*/
Expand All @@ -466,6 +496,7 @@ public Element serialize2DOMDocument(Document doc) {

/**
* Get the NotBefore condition (valid from time) of this IDCard
*
* @return The NotBefore condition as a {@link java.time.ZonedDateTime}
*/
public ZonedDateTime getNotBefore() {
Expand All @@ -475,6 +506,7 @@ public ZonedDateTime getNotBefore() {

/**
* Get the NotOnOrAfter condition (expiration time) of this IDCard
*
* @return The NotOnOrAftter condition as a {@link java.time.ZonedDateTime}
*/
public ZonedDateTime getNotOnOrAfter() {
Expand All @@ -483,7 +515,8 @@ public ZonedDateTime getNotOnOrAfter() {
}

/**
* Validate this IDCard, i.e. validate than NotBefore/NotOnOrAfter is satisfied and that the signature of the assertion/IDCard is valid.
* Validate this IDCard, i.e. validate than NotBefore/NotOnOrAfter is satisfied and that the signature of the assertion/IDCard is valid.
*
* @throws ValidationException
*/
public void validate() throws ValidationException {
Expand All @@ -492,7 +525,8 @@ public void validate() throws ValidationException {
}

/**
* Get a reference to the {@link org.w3c.dom.Element} representation of this IDCard.
* Get a reference to the {@link org.w3c.dom.Element} representation of this IDCard.
*
* @return A reference to the assertion
*/
public Element getAssertion() {
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/trifork/unsealed/OIOSAML2Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.trifork.unsealed;

public class OIOSAML2Constants {
public static final String COMMON_NAME = "urn:oid:2.5.4.3";
public static final String SURNAME = "urn:oid:2.5.4.4";
public static final String CPR_NUMBER = "dk:gov:saml:attribute:CprNumberIdentifier";
public static final String CVR_NUMBER = "dk:gov:saml:attribute:CvrNumberIdentifier";
public static final String RID_NUMBER = "dk:gov:saml:attribute:RidNumberIdentifier";
public static final String PID_NUMBER = "dk:gov:saml:attribute:PidNumberIdentifier";

public static final String PRIVILEGES_INTERMEDIATE = "dk:gov:saml:attribute:Privileges_intermediate";
public static final String THROUGH_PROCURATION_BY = "urn:dk:gov:saml:actThroughProcurationBy:cprNumberIdentifier";

public static final String EMAIL = "urn:oid:0.9.2342.19200300.100.1.3";
public static final String ORGANIZATION_NAME = "urn:oid:2.5.4.10";
public static final String USER_CERTIFICATE = "urn:oid:1.3.6.1.4.1.1466.115.121.1.8";
public static final String CERTIFICATE_ISSUER = "urn:oid:2.5.29.29";
public static final String IS_YOUTH_CERT = "dk:gov:saml:attribute:IsYouthCert";
public static final String SPEC_VERSION = "dk:gov:saml:attribute:SpecVer";
public static final String CERTIFICATE_SERIAL = "urn:oid:2.5.4.5";
public static final String UID = "urn:oid:0.9.2342.19200300.100.1.1";
public static final String DISCOVERY_EPR = "urn:liberty:disco:2006-08:DiscoveryEPR";

public static final String SURNAME_FRIENDLY = "surName";
public static final String COMMON_NAME_FRIENDLY = "CommonName";
public static final String EMAIL_FRIENDLY = "email";
public static final String ORGANIZATION_NAME_FRIENDLY = "organizationName";
public static final String CERTIFICATE_SERIAL_FRIENDLY = "serialNumber";
public static final String UID_FRIENDLY = "Uid";
public static final String OIOSAML2_VERSION_NAME = "DK-SAML-2.0";
}
31 changes: 31 additions & 0 deletions src/main/java/com/trifork/unsealed/OIOSAML3Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.trifork.unsealed;

public class OIOSAML3Constants {
public static final String COMMON_NAME = "https://data.gov.dk/model/core/eid/fullName";
public static final String GIVEN_NAME = "https://data.gov.dk/model/core/eid/firstName";
public static final String SURNAME = "https://data.gov.dk/model/core/eid/lastName";
public static final String CPR_NUMBER = "https://data.gov.dk/model/core/eid/cprNumber";
public static final String CVR_NUMBER = "https://data.gov.dk/model/core/eid/professional/cvr";
public static final String RID_NUMBER = "https://data.gov.dk/model/core/eid/professional/rid";
public static final String PID_NUMBER = "dk:gov:saml:attribute:PidNumberIdentifier";
public static final String CPR_UUID = "https://data.gov.dk/model/core/eid/cprUuid";
public static final String PROF_UUID = "https://data.gov.dk/model/core/eid/professional/uuid/persistent";

public static final String PRIVILEGES_INTERMEDIATE = "https://data.gov.dk/model/core/eid/privilegesIntermediate";
public static final String THROUGH_PROCURATION_BY = "urn:dk:gov:saml:actThroughProcurationBy:cprNumberIdentifier";

public static final String EMAIL = "https://data.gov.dk/model/core/eid/email";
public static final String ORGANIZATION_NAME = "https://data.gov.dk/model/core/eid/professional/orgName";
public static final String SPEC_VERSION = "https://data.gov.dk/model/core/specVersion";
public static final String BOOTSTRAP_TOKEN = "https://data.gov.dk/model/core/eid/bootstrapToken";

public static final String SURNAME_FRIENDLY = "surName";
public static final String COMMON_NAME_FRIENDLY = "CommonName";
public static final String EMAIL_FRIENDLY = "email";
public static final String ORGANIZATION_NAME_FRIENDLY = "organizationName";
public static final String CERTIFICATE_SERIAL_FRIENDLY = "serialNumber";
public static final String UID_FRIENDLY = "Uid";
public static final String OIOSAML3_VERSION_NAME = "OIOSAML-3.0";
public static final String OIOSAML_H_3_VERSION_NAME = "OIOSAML-H-3.0";

}
Loading

0 comments on commit fe609b5

Please sign in to comment.