DeepCloud Authorization
This documentation outlines the onboarding process for accessing the DeepCloud API's.Essentially, the process is divided into the following 3 steps :
- Step 1 : API Access Setup - Apply for access to the DeepCloud API
- Step 2 : Service User Creation - Create of a Service-User for accessing a DeepCloud account
- Step 3 : Token Retrieval - Obtain an access token to connect to DeepCloud API's
Step 1 : API Access Setup
To request API access, contact DeepCloud at : development@deepcloud.swiss
Note: The recommended and most commonly used method to retrieve the Service-User Credentials is via "Manual Copy-Paste". The creation of the Service-User Credentials is a one time process for the DeepCloud owner to log into their account and authorize the creation the Service-User and grant the access. The Client is typically an "On-Premise" solution using a pre-configured Partner-Service-Client-ID and Partner-Service-Client-Secret to validate access to the DeepCloud. ()
Using "Manual Copy-Paste" gives the DeepCloud owner full visual control over the granting of access and, manually copying the Service-User credentials, allows them to re-confirm the access, after the Service-User has been created. The "Manual Copy-Paste" can be used for both "On-Premise" and "Web-Based" solutions. ()
The following criteria define how you retrieve the Customer Service-User credentials and authorized the access to the DeepCloud API's :
Client Type | ||
Authorization Method | ||
Service-User Credentials Retrieval |
Summary:
During the partner registration the partner is assigned a Partner-Service-Client-ID that is used to identify partner in DeepCloud for the DeepCloud access.
JSON Web Key Set (JWKS)
DeepCloud needs to register external partner service applications as an OAuth client application on DeepCloud (Arcanite). The following information will be required :- A public key as a JWKS (JSON Web Key Set) to access DeepCloud (either an URL which points to the JWKS or alternatively the JWKS itself)
- An optional Redirect-URI, if not using "Manual Copy-Paste" for the Service-User Credential flow (during the Service-User creation in Step 2)
- A logo which can be displayed by the DeepAdmin WebUI during the authentication flow (the logo will be displayed during the Service-User creation in Step 2)
If the Service-User Credentials are retrieved using a Redirect-URI, the Redirect-URI must also be registered with DeepCloud and will be verified before creation of the Service-User.
For the development "DEV" site, the IdP Software (Identity Provider) used by DeepCloud support Redirect-URI with wildcards at the end of the URI but not within the URI itself (e.g. https://my.cloud/api/v2/oauth/deepsign/connect/{*} is possible, but not https://{*}.my.cloud/api/v2/oauth/deepsign/connect)
JWKS - The JSON Web Key Set (JWKS) is a set of keys containing the public keys used to verify any JSON Web Token (JWT) issued by the authorization server and signed using the RS256 signing algorithm.
Note : It is important to define a unique Json-Web-Key-ID for each Json Web Key in the JWKS. A JWKS can contain more than one Json-Web-Key, so it is important to define each Key with a unique Json-Web-Key-ID ("kid").
Example of a randomly generated Public Key in a JSON Web Key Set:
{ "keys": [ { "kty": "RSA", "e": "AQAB", "use": "sig", "kid": "YFFeWxTfTvBYznquRRnbsS00DCJcBtxBOcKLKhwBUAk", "alg": "RS256", "n": "srfsGWNEjanMMZ0HP7gORejXIyGXv75ZB-LG9AvQhgVsOUxhMfR965L8J...[abbreviated for display]...LfohzZ5J-U56wFOQhFEjqeO3oxz7uKqGZzswdz7Q" } ] }There are various ways to create JWKS. The examples shown in this documentation were generated using the website mkjwk.org to generate Private and Public key sets. The Private key is required for the JWT (Json Web Token) signature in the Step 3 (Usage Phase) when generating the "client_assertion".
Step 2 : Service User Creation
For instructions on how to create a DeepCloud account see the support page : Support Page : How to create an account Login to the DeepCloud account with the following URL, to create the service user for the specified Partner-Service-Client-ID.
If the partner registration has been configured with a Redirect-URI, the URL can contain the Redirect-URI with "response_mode=form_post"
or alternatively, "response_mode=copy_paste" can be used, where the DeepCloud User must manually copy the Server-User credentials to the accessing client software.
An example URL using the redirect URI is as follows :
URL's using manual copy-paste :
https://admin.deepbox.swiss/third-party-connection/?response_type=service_account&client_id=Partner-Service-Client-ID&response_mode=form_post&redirect_uri=https%3A%2F%2Fpartner.service.redirect.uri.example.org%2Foauthcb&state=033b52cf-1112-4624-bb63-042b891effb6
|
Production |
https://admin.deepbox.swiss/third-party-connection/?response_type=service_account&client_id=Partner-Service-Client-ID&response_mode=copy_paste
|
|
Test |
https://admin.int.deepbox.swiss/third-party-connection/?response_type=service_account&client_id=Partner-Service-Client-ID&response_mode=copy_paste
|
URL's using manual copy-paste :
Production |
https://admin.deepbox.swiss/third-party-connection/?response_type=service_account&client_id=Partner-Service-Client-ID&response_mode=copy_paste
|
|
Test |
https://admin.int.deepbox.swiss/third-party-connection/?response_type=service_account&client_id=Partner-Service-Client-ID&response_mode=copy_paste
|
Redirect-URI
The URL parameters are defined as follows :- response_type : the normal value is service_account (e.g. response_type=service_account)
- client_id : Partner-Service-Client-ID configured from Step 1 (e.g. client_id=my-partner-client-id)
- response_mode : the normal value is form_post (e.g. response_mode=form_post
- redirect_uri : the Redirect-URI configured in Step 1 (e.g. redirect_uri=https://partner.service.redirect.uri.example.org/oauthcb)
- state : a non-guessable random value such a guid used to validate the Redirect-URI post_form request for the partner service (e.g. state=033b52cf-1112-4624-bb63-042b891effb6)
- actions : optional configured actions - only necessary if actions have been configured on DeepSign(actions=xyz)
After calling the URL in the Internet Browser, the customer can log into their DeepCloud account as normal via the browser, and accept the partner access. The Login to the DeepCloud looks like the following:
Following the login to DeepCloud, the Browser will display something like the following, where the customer can authorize the creation of the Service-User for accessing their DeepCloud account.
After accepting the DeepCloud access, the Service-User will be created on the customer DeepCloud account and the details of the Service-User will be sent to the Redirect-URI configured by the Partner. The Redirect-URI must be registered with DeepCloud during Step 1 (Partner Setup) and will be verified before creation of the Service-User. The partner must store the Service-User information securely, as the Service-User will be used to establish the connection to the customer DeepCloud account in the Step 3 (Usage Phase)
When successfully validated, DeepAdmin sends a POST request form back to the configured Redirect-URI. An example form Request is as follows :
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Cache-Control: no-cache, no-store
Pragma: no-cache
<html>
<head><title>Submit This Form</title></head>
<body onload="javascript:document.forms[0].submit()">
<form method="post" action="Redirect-URI">
<input type="hidden" name="state" value="033b52cf-1112-4624-bb63-042b891effb6"/>
<input type="hidden" name="service_account_id" value="0c482998-e6f7-483d-b57c-668246cacc90"/>
<input type="hidden" name="service_account_username" value="0c482998-e6f7-483d-b57c-668246cacc90@in.deepcloud.swiss"/>
<input type="hidden" name="service_account_password" value="82689c74-055f-483f-aa09-8c96cdd63b40"/>
<input type="hidden" name="creator_email" value="johnny.smith@some-mail.com"/>
<input type="hidden" name="creator_language" value="en"/>
</form>
</body>
</html>
The raw content of the POST message received via the configured Redirect-URI, would look something like the following :
state=033b52cf-1112-4624-bb63-042b891effb6& service_account_id=0c482998-e6f7-483d-b57c-668246cacc90& service_account_username=0c482998-e6f7-483d-b57c-668246cacc90%40in.deepcloud.swiss& service_account_password=82689c74-055f-483f-aa09-8c96cdd63b40& creator_email=johnny.smith%40some-mail.com& creator_language=enAttention: The POST message above does not contain line breaks. It has been shown with line breaks above for clarity. The "&" is the parameter separator.
The service user details service_account_username and service_account_password should be extracted and stored by the partner service. They will be used to obtained the access tokens for the connection to the customer DeepCloud API's.
The "state" parameter defined for the Administration DeepCloud "third-party-connection" URL call, will be sent with the the Redirect-URI post_form request. The "state" value can be used to validate the request.
Manual Copy-Paste
After logging into DeepCloud, authorize the creation of the service user for accessing your DeepCloud account. The service user credentials must be copied manually to the client application, as they are only displayed once.Step 3 : Token Endpoint
Token EndPoint Request
The Endpoint URL to obtain the access token uses a POST request.Environment | Access URL |
Production | https://deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token |
Test | https://int.deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token |
CURL Client Secret
curl -X POST --location "https://deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=password" \ --data-urlencode "username=service_account_username" --data-urlencode "password=service_account_password" \ --data-urlencode "client_id=Partner-Service-Client-ID" --data-urlencode "client_secret=Partner-Service-Client-Secret" |
HTTP Client Secret
POST /auth/realms/sso/protocol/openid-connect/token HTTP/1.1 Host: deepcloud.swiss Content-Type: application/x-www-form-urlencoded grant_type=password& username=service_account_username& password=service_account_password& client_id=Partner-Service-Client-ID& client_secret=Partner-Service-Client-Secret |
Attention: The POST call above does not contain line breaks or whitespaces. It has been shown with line breaks above for clarity. The "&" is the parameter separator.
Public Client - No Authorization
POST /auth/realms/sso/protocol/openid-connect/token HTTP/1.1 Host: deepcloud.swiss Content-Type: application/x-www-form-urlencoded grant_type=password& username=service_account_username& password=service_account_password& client_id=Partner-Service-Client-ID |
JSON Web Key Set (JWKS)
POST /auth/realms/sso/protocol/openid-connect/token HTTP/1.1 Host: deepcloud.swiss Content-Type: application/x-www-form-urlencoded grant_type=password& username=service_account_username& password=service_account_password& client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer& client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0.eyJpc3Mi...[abbreviated for display]...C4hiUPo |
The "client_assertion" is an encrypted key, containing the following header and payload information.
Example of Client Assertion Header
{ "kid": "YFFeWxTfTvBYznquRRnbsS00DCJcBtxBOcKLKhwBUAk", "alg": "RS256" }Example of Client Assertion Payload
{ "iss": "Partner-Service-Client-ID", "sub": "Partner-Service-Client-ID", "aud": "https:\/\/deepcloud.swiss\/auth\/realms\/sso\/protocol\/openid-connect\/token", "exp": 1655271852, "jti": "594c5c42-69f1-4a25-a5af-4371b182fabb" }Note : The DeepCloud URL value of audience, "aud", must have the slashes escaped.
Further general information about the format of client assertions can be found here in RFC 7523 (e.g. JWT format in section 3).
The client_assertion consists of 3 parts :
- Base64URLHeader - Base64Url encoded Header
- Base64URLPayload - Base64Url encoded Payload
- SignedToken - The content of "Base64URLHeader.Base64URLPayload" signed with the Private key from the JWT (Json Web Token)
Base64URLHeader.Base64URLPayload.SignedToken
Required for the Client Assertion :
- "kid" : the Json-Web-Key-ID from the JWKS configured in Step 1 (Partner Setup)
- "alg" : the algorithm (normally RS256)
- "iss" : the issuer value with the Partner-Service-Client-ID received from the configuration in Step 1 (Partner Setup)
- "sub" : the subject value with the Partner-Service-Client-ID (same value a issuer)
- "aud" : the audience value defined by the DeepCloud URL (must match the called DeepCloud Endpoint URL exactly, apart from the escaped slashes)
- "exp" : the expiry in seconds (typically current time + 60 seconds)
- "jti" : Json Web Token ID (typically a randommly generated GUID)
Token EndPoint Response
When the Token EndPoint Request call is successful, an access token will be returned similar to the following structure :{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIg...[abbreviated for display]...bOz-m1Nmh9ee5USu9lxn_ywghpEjTpz71HE17Qgkw", "expires_in": 900, "refresh_expires_in": 36000, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI...[abbreviated for display]...HMAQUE2I6_HsdoVTVSmD8Wk7wsXNbONaNxfzvCqE", "token_type": "Bearer", "not-before-policy": 1569851884, "session_state": "84c4de78-b724-4b68-ae37-34e6e1d11a99", "scope": "email profile" }The access tokens are valid for a limited time (typically 15 minutes / 900 seconds). The partner service requests a new access token from DeepSSO when the current access token expires. Access Tokens should be reused within the period they are valid, and not renewed for every request.
The "access_token" is used in the "Authorization" Header to access the DeepCloud API's of the customer.
Example Response from : https://deepcloud.swiss/api/v1/users/me
{ "email":"d3e496ab-a3c8-4980-8d24-c1d3bc9ba69e@in.deepcloud.swiss", "firstname":"DeepSign", "lastname":"ServiceUser", "id":"e66c6c10-ce5e-4f4a-8935-bcab138ab7ff", "groups":[], "companies":[ { "group_id":"2b5bf019-c1b8-417b-92bc-cf4930431b56", "name":"f29f8b49-8f47-4dd7-8907-2fbad854c85d@automatic.onboarding.deepbox.swiss", "display_name":"Test Organization DeepSign", ...[abbreviated for display]... }], "units":[], ...[abbreviated for display]... }
For further information about using the API to upload and sign PDF documents and start the sign process, please see the separate DeepSign API Documentation.
For a detailed technical overview of all partner set-up options, please see the DeepCloud API Detailed Documentation.
Example Java Code
The following demonstrates how the client_assertion could be generated with Java classes using the Nimbus JOSE + JWT components available on Maven repositories.Note: The Partner-Service-Client-ID and the Private Key created via JWKS should be stored in secure locations. The values are shown in the source code sample below for simplicity.
package ch.abacus.example; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import java.util.Date; import java.util.UUID; /** * An example class to demonstrate the creation of a serialized Client-Assertion for the Access-Token request * This class uses the Maven components from com.nimbusds.* * * Maven dependencies for the com.nimbusds library : * <!-- JWT/JWK handling --> * <dependency> * <groupId>com.nimbusds</groupId> * <artifactId>nimbus-jose-jwt</artifactId> * <version>${nimbus-jose-jwt.version}</version> * </dependency> */ public class JwtClientAssertionUtililty { /** * Return Private Key created via JWKS (Json Web Key Set) via https://mkjwk.org * This private key should match the JWKS public key supplied for the registration of the JWKS (Json-Web-Key-Set), Callback-URL and Logo * * @return return the serialized Private Key in Json format */ private String getSerializedPrivateRsaKey() { return "{" + "\"p\":\"_-JXX6poWyMFWtp9sBAIW...[abbreviated for display]...9I0p-CRZYA1t3a69mYZl0-Fc\"," + "\"kty\":\"RSA\"," + "\"q\":\"6WGBCmB0KCZ8Tt...[abbreviated for display]...B6ROhJn3Hx3wzkzBJeYhpkzhP8\"," + "\"d\":\"dOjw6zwHWWs4uarETEwS1TzplSXKI6FDnFUK1k3P5AzIS0...[abbreviated for display]...lZvdI8PCnA5UB4jh4NXu-7hFe4lplbOPNzbrOtXte6nyRQ\"," + "\"e\":\"AQAB\"," + "\"use\":\"sig\"," + "\"kid\":\"YFFeWxTfTvBYznquRRnbsS00DCJcBtxBOcKLKhwBUAk\"," + "\"qi\":\"TVR4bCaz9Tn2womg...[abbreviated for display]...bPrjoSRtB7rTAyfS77rJ0AMraNiqKyE\"," + "\"dp\":\"HuhL_XYr3LZCM1Mo01dcasPE...[abbreviated for display]...2yS9_qqbtTUnGxifSVTK2KoTymmA0k\"," + "\"alg\":\"RS256\"," + "\"dq\":\"SfS8D63BIXgQrG1tnaTe75K...[abbreviated for display]...IN6rFSDcQOkJhrLWPInWK0xiHXD_cE\"," + "\"n\":\"srfsGWNEjanMMZ0HP7gORejXIyGXv75ZB-LG9AvQhgVsOUxhMfR965L8J...[abbreviated for display]...LfohzZ5J-U56wFOQhFEjqeO3oxz7uKqGZzswdz7Q\"" + "}"; } /** * This is the Partner-Service-Client-ID that is received after the registration of the Json-Web-Key-Set, Callback-URL and Logo * * @return the Partner-Service-Client-ID */ private String getRegistrationServiceClientId() { return "Partner-Service-Client-ID"; } /** * The DeepCloud OpenId Connection Token Endpoint URL. This must exactly match the URL called when retrieving the * Access Token * @return the DeepCloud OpenId Connection Token Endpoint URL */ private String getDeepCloudAuthenicationOpenIdConnectTokenEndpoint() { return "https://deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token"; } /** * Creates the RSA Private Key Object from the serialized content of the Private Key * * @return RSA Private Key Object */ public RSAKey getRsaPrivateKey() { String serializedRsaKey = getSerializedPrivateRsaKey(); if (serializedRsaKey.length() == 0) { throw new IllegalArgumentException("The application requires an RSA Private-Key. The configuration must include one."); } try { // Creates the Json Web Key with the com.nimbusds.jose.jwk.JWK and com.nimbusds.jose.jwk.RSAKey library objects JWK parsedKey = JWK.parse(serializedRsaKey); RSAKey key = new RSAKey.Builder(parsedKey.toRSAKey()) .algorithm(JWEAlgorithm.RSA_OAEP_256) .keyUse(KeyUse.SIGNATURE) .build(); return key; } catch (Exception ge) { throw new IllegalArgumentException("Failed to parse the RSA key specified with the property : " + ge.getMessage()); } } /** * Generates the full client_assertion parameter content used for the access token request : * Example : * "client_assertion=" + getClientAssertion() * * The format of the client assertion is : base64Url(header).base64Url(payload).signed_token * * @return returns client_assertion content value * @throws Exception */ public final String getClientAssertion() throws Exception { // Create RSA-signer with the private key from the JWKS (Json Web Key Set) RSAKey privateKey = getRsaPrivateKey(); JWSSigner privateKeySigner; try { privateKeySigner = new RSASSASigner(privateKey); } catch (JOSEException ge) { ge.printStackTrace(); throw new Exception("ERROR: Failed to get an RSA signer instance from the RSA key for the client assertion.", ge); } // Build the Payload part for the "client_assertion" JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject(getRegistrationServiceClientId()) .issuer(getRegistrationServiceClientId()) .audience(getDeepCloudAuthenicationOpenIdConnectTokenEndpoint()) .jwtID(UUID.randomUUID().toString()) .expirationTime(new Date(new Date().getTime() + 60 * 1000)) .build(); JWSAlgorithm algorithm = JWSAlgorithm.RS256; SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(algorithm).keyID(privateKey.getKeyID()).build(), claimsSet); try { signedJWT.sign(privateKeySigner); } catch (JOSEException ge) { ge.printStackTrace(); throw new Exception("ERROR: Failed sign the client assertion.", ge); } return signedJWT.serialize(); } }
Example C#.NET Code
The following demonstrates how the client_assertion could be generated with C#.NET classes using the Jose components and the System.Security.Cryptography classes from .NET .Note: The Partner-Service-Client-ID and the Private Key created via JWKS should be stored in secure locations. The values are shown in the source code sample below for simplicity.
using System;
using System.Text;
using Jose;
using System.Security.Cryptography;
/**
* An example class to demonstrate the creation of a serialized Client-Assertion for the Access-Token request
* This class uses the Jose components from .NET and the System.Security.Cryptography classes to create the
* client_assertion
*
*/
namespace AbacusRestApiExample
{
public class JwtClientAssertionUtililty
{
/**
* Return Private Key created via JWKS (Json Web Key Set) via https://mkjwk.org
* This private key should match the JWKS public key supplied for the registration of the JWKS (Json-Web-Key-Set), Callback-URL and Logo
*
* @return return the serialized Private Key in Json format
*/
private String getSerializedPrivateRsaKey()
{
return "{" +
"\"p\":\"_-JXX6poWyMFWtp9sBAIW...[abbreviated for display]...9I0p-CRZYA1t3a69mYZl0-Fc\"," +
"\"kty\":\"RSA\"," +
"\"q\":\"6WGBCmB0KCZ8Tt...[abbreviated for display]...B6ROhJn3Hx3wzkzBJeYhpkzhP8\"," +
"\"d\":\"dOjw6zwHWWs4uarETEwS1TzplSXKI6FDnFUK1k3P5AzIS0...[abbreviated for display]...lZvdI8PCnA5UB4jh4NXu-7hFe4lplbOPNzbrOtXte6nyRQ\"," +
"\"e\":\"AQAB\"," +
"\"use\":\"sig\"," +
"\"kid\":\"YFFeWxTfTvBYznquRRnbsS00DCJcBtxBOcKLKhwBUAk\"," +
"\"qi\":\"TVR4bCaz9Tn2womg...[abbreviated for display]...bPrjoSRtB7rTAyfS77rJ0AMraNiqKyE\"," +
"\"dp\":\"HuhL_XYr3LZCM1Mo01dcasPE...[abbreviated for display]...2yS9_qqbtTUnGxifSVTK2KoTymmA0k\"," +
"\"alg\":\"RS256\"," +
"\"dq\":\"SfS8D63BIXgQrG1tnaTe75K...[abbreviated for display]...IN6rFSDcQOkJhrLWPInWK0xiHXD_cE\"," +
"\"n\":\"srfsGWNEjanMMZ0HP7gORejXIyGXv75ZB-LG9AvQhgVsOUxhMfR965L8J...[abbreviated for display]...LfohzZ5J-U56wFOQhFEjqeO3oxz7uKqGZzswdz7Q\"" +
"}";
}
/**
* This is the Partner-Service-Client-ID that is received after the registration of the Json-Web-Key-Set, Callback-URL and Logo
*
* @return the Partner-Service-Client-ID
*/
private String getRegistrationServiceClientId()
{
return "Partner-Service-Client-ID";
}
/**
* The DeepCloud OpenId Connection Token Endpoint URL. This must exactly match the URL called when retrieving the
* Access Token
*
* @return the DeepCloud OpenId Connection Token Endpoint URL
*/
public virtual String getDeepCloudAuthenicationOpenIdConnectTokenEndpoint()
{
return "https://deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token";
}
/**
* Returns expiry seconds as current time plus 10 minutes (10 minutes * 60)
*/
private long getExpirySeconds()
{
return DateTimeOffset.Now.ToUnixTimeSeconds() + (60 * 10); // 10 minutes in the future
}
/**
* Converts a string to Bytes using the Base64 URL conversions
*/
private byte[] getStringAsBase64UrlBytes(String value)
{
return Base64Url.Decode(value);
}
/**
* Generates the full client_assertion parameter content used for the access token request :
* Example :
* "client_assertion=" + getClientAssertion()
*
* The format of the client assertion is : base64Url(header).base64Url(payload).signed_token
*
*/
public String getClientAssertion()
{
// Ref Example : https://stackoverflow.com/questions/50100186/signed-and-encrypt-from-keypair-string
// https://stackoverflow.com/questions/64217089/what-is-the-correct-way-to-transform-jwk-to-rsa-key-value-pair
String serializedPrivateRsaKey = getSerializedPrivateRsaKey();
// Create the RSA key with the specific parameters from the serialized Json Private Key
RSAParameters keyParams = new RSAParameters();
keyParams.P = getStringAsBase64UrlBytes(extractSimpleJsonValue("p", serializedPrivateRsaKey));
keyParams.Q = getStringAsBase64UrlBytes(extractSimpleJsonValue("q", serializedPrivateRsaKey));
keyParams.D = getStringAsBase64UrlBytes(extractSimpleJsonValue("d", serializedPrivateRsaKey));
keyParams.Exponent = getStringAsBase64UrlBytes(extractSimpleJsonValue("e", serializedPrivateRsaKey));
keyParams.InverseQ = getStringAsBase64UrlBytes(extractSimpleJsonValue("qi", serializedPrivateRsaKey));
keyParams.DP = getStringAsBase64UrlBytes(extractSimpleJsonValue("dp", serializedPrivateRsaKey));
keyParams.DQ = getStringAsBase64UrlBytes(extractSimpleJsonValue("dq", serializedPrivateRsaKey));
keyParams.Modulus = getStringAsBase64UrlBytes(extractSimpleJsonValue("n", serializedPrivateRsaKey));
// The original Json Private Key was created with length 2048
RSA key = RSA.Create(2048);
key.ImportParameters(keyParams);
String audience = getDeepCloudAuthenicationOpenIdConnectTokenEndpoint().Replace("/", "\\/");
String claimSetString = "{" +
"\"iss\":\"" + getRegistrationServiceClientId() + "\"" + "," +
"\"sub\":\"" + getRegistrationServiceClientId() + "\"" + "," +
"\"aud\":\"" + audience + "\"" + "," +
"\"exp\":" + getExpirySeconds() + "," +
"\"jti\":\"" + Guid.NewGuid().ToString() + "\"" +
"}";
String kid = extractSimpleJsonValue("kid", serializedPrivateRsaKey);
String alg = extractSimpleJsonValue("alg", serializedPrivateRsaKey);
String headerString = "{" +
"\"kid\":\"" + kid + "\"" + "," +
"\"alg\":\"" + alg + "\"" +
"}";
String base64ClaimSet = Base64Url.Encode(Encoding.UTF8.GetBytes(claimSetString));
String base64Header = Base64Url.Encode(Encoding.UTF8.GetBytes(headerString));
String signingInputString = base64Header + "." + base64ClaimSet;
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider(2048);
RSAalg.ImportParameters(key.ExportParameters(true));
String tokenSigned = Base64Url.Encode(RSAalg.SignData(Encoding.UTF8.GetBytes(signingInputString), SHA256.Create()));
return signingInputString + "." + tokenSigned;
}
/**
* Simple extraction routine to retrieve a specified parameter value from the serialized Json Private Key
* Note : this is shown for demonstration. Other functions are available in .NET to handle Json structures
*
* @return return the specified parameter value for the serialized Json Private Key
*/
private String extractSimpleJsonValue(String paramName, String jsonContent)
{
if (paramName == null || jsonContent == null || "".Equals(paramName) || "".Equals(jsonContent)) return "";
String value = "";
String delimeter = "\"";
String jsonParamName = delimeter + paramName + delimeter;
int ipos = jsonContent.IndexOf(jsonParamName);
if (ipos > 0)
{
value = jsonContent.Substring(ipos + jsonParamName.Length).Trim();
if (value.StartsWith(":"))
{
value = value.Substring(1).Trim();
}
// Search the end for comma or end bracket
ipos = value.IndexOf(",");
if (ipos > 0)
{
value = value.Substring(0, ipos).Trim();
}
ipos = value.IndexOf("}");
if (ipos > 0)
{
value = value.Substring(0, ipos).Trim();
}
if (value.StartsWith(delimeter))
{
value = value.Substring(1).Trim();
}
if (value.EndsWith(delimeter))
{
value = value.Substring(0, value.Length - 1);
}
}
return value;
}
}
}
Example PHP Code
The following demonstrates how the client_assertion could be generated with PHP openssl_* methods.Note: The Partner-Service-Client-ID and the Private Key created via JWKS should be stored in secure locations. The values are shown in the source code sample below for simplicity.
<?php
/**
* An example class to demonstrate the creation of a serialized Client-Assertion for the Access-Token request
*
* Example of use :
* $caUtility = new ClientAssertionUtility();
* $clientAssertionParam = "client_assertion=" . $caUtility->getClientAssertion();
*
*/
class ClientAssertionUtility {
/**
* Returns Private Key created via JWKS (Json Web Key Set) via https://mkjwk.org
* This private key should match the JWKS public key supplied for the registration of the JWKS (Json-Web-Key-Set), Callback-URL and Logo
*/
function getSerializedPrivateRsaKey() {
return "{" .
"\"p\": \"_-JXX6poWyMFWtp9sBAIW...[abbreviated for display]...9I0p-CRZYA1t3a69mYZl0-Fc\"," .
"\"kty\": \"RSA\"," .
"\"q\": \"6WGBCmB0KCZ8Tt...[abbreviated for display]...B6ROhJn3Hx3wzkzBJeYhpkzhP8\"," .
"\"d\": \"dOjw6zwHWWs4uarETEwS1TzplSXKI6FDnFUK1k3P5AzIS0...[abbreviated for display]...lZvdI8PCnA5UB4jh4NXu-7hFe4lplbOPNzbrOtXte6nyRQ\"," .
"\"e\": \"AQAB\"," .
"\"use\": \"sig\"," .
"\"kid\": \"YFFeWxTfTvBYznquRRnbsS00DCJcBtxBOcKLKhwBUAk\"," .
"\"qi\": \"TVR4bCaz9Tn2womg...[abbreviated for display]...bPrjoSRtB7rTAyfS77rJ0AMraNiqKyE\"," .
"\"dp\": \"HuhL_XYr3LZCM1Mo01dcasPE...[abbreviated for display]...2yS9_qqbtTUnGxifSVTK2KoTymmA0k\"," .
"\"alg\": \"RS256\"," .
"\"dq\": \"SfS8D63BIXgQrG1tnaTe75K...[abbreviated for display]...IN6rFSDcQOkJhrLWPInWK0xiHXD_cE\"," .
"\"n\": \"srfsGWNEjanMMZ0HP7gORejXIyGXv75ZB-LG9AvQhgVsOUxhMfR965L8J...[abbreviated for display]...LfohzZ5J-U56wFOQhFEjqeO3oxz7uKqGZzswdz7Q\"" .
"}";
}
/**
* Returns PEM Private Key corresponding to the Json Private key created via JWKS (Json Web Key Set) via https://mkjwk.org
* The PEM private key must correspond to the JWKS public key supplied for the registration of the JWKS (Json-Web-Key-Set), Callback-URL and Logo
*/
function getPrivatePemKey() {
// Can be a "-----BEGIN PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----"
return "-----BEGIN RSA PRIVATE KEY-----" . "\n" .
"MIIEogIBA...[abbreviated for display]...BV0Boo83kk" . "\n" .
"++IUwFV6A...[abbreviated for display]...XZmZPc5jDZ" . "\n" .
" ...[abbreviated for display]... " . "\n" .
"cVh0/zGmQ...[abbreviated for display]...Qw5cA0OEycmUKG" . "\n" .
"CttStNMcj...[abbreviated for display]...KyE=" . "\n" .
"-----END RSA PRIVATE KEY-----";
}
/**
* This is the Partner-Service-Client-ID that is received after the registration of the Json-Web-Key-Set, Callback-URL and Logo
*/
function getRegistrationServiceClientId() {
return "Partner-Service-Client-ID";
}
/**
* The DeepCloud OpenId Connection Token Endpoint URL. This must exactly match the URL called when retrieving the
* Access Token
*/
function getDeepCloudAuthenicationOpenIdConnectTokenEndpoint() {
return "https://deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token";
}
/**
* Returns expiry seconds as current time plus 10 minutes (10 minutes * 60)
*/
function getExpirySeconds()
{
return time() + (60 * 10); // 10 minutes in the future
}
/**
* Generates the full client_assertion parameter content used for the access token request :
* Example :
* "client_assertion=" + getClientAssertion()
*
* The format of the client assertion is : base64Url(header).base64Url(payload).signed_token
*
* @return returns client_assertion content value
* @throws Exception
*/
function getClientAssertion() {
// Load JWKS Private key in Json String format
$testPrivateKeyJsonStruct = json_decode($this->getSerializedPrivateRsaKey(), true);
if ( JSON_ERROR_NONE != json_last_error() ) {
return "";
}
// Extract "kid" and "alg" values from Json structure
$kidValue = $testPrivateKeyJsonStruct["kid"];
$jwksAlgValue = $testPrivateKeyJsonStruct["alg"];
$exp = $this->getExpirySeconds(); // Expiry seconds as current time plus 10 minutes (10 minutes * 60)
$client_id = $this->getRegistrationServiceClientId();
$jti = $this->create_guid(); // Can be any one-time unique value
// The audience value is the Endpoint URL with escaped slash
$audience = str_replace("/", "\\/", $this->getDeepCloudAuthenicationOpenIdConnectTokenEndpoint());
// Create the header and payload stuctures
$header = '{"kid":"' . $kidValue . '","alg":"' . $jwksAlgValue . '"}';
$payload = '{"iss":"'.$client_id.'","sub":"'.$client_id.'","aud":"' . $audience . '","exp":'.$exp.',"jti":"'.$jti.'"}';
$base64_unsigned_token = $this->base64url_encode($header).".".base64url_encode($payload);
$signature = "";
// Depending on the PHP Server configuration the PEM Private key can be generated using
// the key pair from "openssl_pkey_new(..)" and private key "export openssl_pkey_export(..)" method
// Note: depending on the PHP Version and PHP SSL Server configuration this method may not generate the PEM key correctly
// Alternative is to load the PEM key, that has been generated from another source
// Create the key pair from the Json JWKS Private key structure
// $new_key_pair2 = openssl_pkey_new($testPrivateKeyJsonStruct);
// Create private PEM from key pair
//openssl_pkey_export($new_key_pair2, $privateKeyPem);
// Alternative : Load PEM key from externally generated source
$privateKeyPem = $this->getPrivatePemKey();
$encMethodName = "SHA256";
openssl_sign($base64_unsigned_token, $signature, $privateKeyPem, $encMethodName);
// free the key from memory
openssl_free_key($certificateObject);
return $base64_unsigned_token . "." . $this->base64url_encode($signature);
}
/**
* Helper method to convert value with Base64-URL encoding
*/
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// Create a GUID (Globally Unique Identifier)
function create_guid() {
$guid = '';
$namespace = rand(11111, 99999);
$uid = uniqid('', true);
$data = $namespace;
$data .= $_SERVER['REQUEST_TIME'];
$data .= $_SERVER['HTTP_USER_AGENT'];
$data .= $_SERVER['REMOTE_ADDR'];
$data .= $_SERVER['REMOTE_PORT'];
$hash = strtoupper(hash('ripemd128', $uid . $guid . md5($data)));
$guid = substr($hash, 0, 8) . '-' .
substr($hash, 8, 4) . '-' .
substr($hash, 12, 4) . '-' .
substr($hash, 16, 4) . '-' .
substr($hash, 20, 12);
return strtolower($guid);
}
}
?>