This document outlines how a client, which has been granted the appropriate permissions, can make use of OIDC signing support and optionally the SignDoc Resource Server.

Introduction

OIDC signing is an extension to the authorization flow in BankID OIDC.  Adding a scope, a signorder reference or a text to be signed in a query parameter and then calling the authorize endpoint, a BankID signing will be performed. The actual signing will be done using the ordinary BankID WebClient or using BankID on mobile. The result of the signing will be available either as a claim in the id_token or as a json object requested from the SignDoc resource server.

Flows

Below [signdoc-baseurl] means the baseurl as given by the .well-known/openid-configuration endpoint.

Kind of flow is independent of choosing BankID WebClient or BankID on Mobile signing. Due to limitations on mobile there are restrictions on type of signing etc.

Simplified flow

One authorize request is sent to the OIDC server, result of signing in id_token. 

[authorize_endpoint]... &scope=sign&sign_txt=base64encoded(utf8-string)

result is placed in the id_token as the claim sign_result which contains the enduser and merchant basic-signatures and a hash over the received text.


Full flow

Three requests, one for uploading to the SignDoc server, one for authorizing and signing, one for downloading the result from the SignDoc server.

Merchant uploads a signing order

POST [signdoc-baseurl]/signdoc

header: access-token with scope signdoc/read_write

body: document(s) to be signed, signing parameters, result specification

result: sign_id in json

EndUser performs the signing

GET [authorize_endpoint]....&scope=sign&sign_id=[sign_id from upload]

Merchant downloads the signing result

DELETE [signdoc-baseurl]/signdoc?sign_id=[sign_id from upload]

header: access-token with scope signdoc/read_write

result: json body containing result


How to get the url to the SignDoc Resource Server

The Url to access the SignDoc resource server is found in .well-known/openid-configuration result as signdoc-baseurl. The merchant should add /signdoc to the base-url.


How to get the access token to use in the Full flow, i.e. granting access to the SignDoc server

To get an access token with the correct content, first of all the OIDC client must have been granted the correct accesses, 

Then send a request to the token endpoint using client_credential grant and scope=signdoc/read_write, and the access token is returned.


An example: 

POST /auth/realms/current/protocol/openid-connect/token

HTTP/1.1
Host: oidc-current.bankidapis.no
Content-Type: application/x-www-form-urlencoded
Authorization: Basic b2lkYy10ZXN0Y2xpZW50OmVmMWE4ZWM2LTUwODctNDQ0Yy04NGJlLTU0YTYxZjg4MTIyZQ==
 
grant_type=client_credentials&scope=signdoc%2Fread_write

The response looks like

{
    "access_token""eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZQjR1R0JDLU5yeFJjamhTU3BaUVJwRzFiRUd6U0huVE0zeHJRTkxabU80In0.eyJqdGkiOiIwY2QwMWY5YS1mMTgxLTRmMTAtYmVlYS1iMjRiYjcyOWU0Y2YiLCJleHAiOjE1MzM3MzkyNTEsIm5iZiI6MCwiaWF0IjoxNTMzNzM4OTUxLCJpc3MiOiJodHRwczovL3Byb3RvdHlwZS5iYW5raWRub3JnZS5uby9hdXRoLWRldi9yZWFsbXMvRGV2ZWxvcCIsImF1ZCI6WyJnZW5lcmljLXJlc291cmNlLXNlcnZlciIsInNpZ25kb2MiLCJmcmF1ZC1kYXRhLXJzIl0sInN1YiI6ImZjY2VlOTM2LTFiNGEtNGNmNC1hNWU4LThkOWYyZTljNThiMiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9pZGMtdGVzdGNsaWVudCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjI3ZTY5NDc2LTRjZjktNGFkYS1hZjhjLWNkOTNjNjhmYzBlZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZXNvdXJjZV9hY2Nlc3MiOnsiZ2VuZXJpYy1yZXNvdXJjZS1zZXJ2ZXIiOnsicm9sZXMiOlsicm9sZTEiXX0sInNpZ25kb2MiOnsicm9sZXMiOlsicmVhZF93cml0ZSJdfSwiZnJhdWQtZGF0YS1ycyI6eyJyb2xlcyI6WyJHZXRTZWN1cml0eURhdGEiXX19LCJjbGllbnRJZCI6Im9pZGMtdGVzdGNsaWVudCIsImNsaWVudEhvc3QiOiIxNzIuMTYuMC4xNTgiLCJyZXNvdXJjZV9jbGFpbXMiOnt9LCJjbGllbnRBZGRyZXNzIjoiMTcyLjE2LjAuMTU4In0.OOR2RsNEdQUDWL-wpQY7qjNnFucv4zLeQoL8mLVcSggOkrGspQ7z1h-ZZjDzVx294zoJpB0rDVrYgLAlugfQ0z-dqFZPvwUc1vMg4OFeYc4W1rR5L8WEMdprm-TbW6f8lnOGIfROZuijwzyeLZsQ2rKtg13KHGdvlJbeue6PlAVM89SFerFbfoJHpN_AhvATpGsN6GAniwGEgJtD0blCLcC3PQKRJfMUa9ZXo2NjVVtLocjQEwiMAu4EnWRTupv9cmhmFolSDZV_7jYs72KMnn025KroMMvBHL538r8wMztByiT45xMOZHRTbClM_Bwd7zjVdcTdaQp4OuE5vVWIMg",
    "expires_in": 300,
    "refresh_expires_in": 3600,
    "refresh_token""eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZQjR1R0JDLU5yeFJjamhTU3BaUVJwRzFiRUd6U0huVE0zeHJRTkxabU80In0.eyJqdGkiOiIwNWJiNWM5YS1jNmRkLTQ5ZDYtOGEyYS1jZTRlYTFkZTE0NGYiLCJleHAiOjE1MzM3NDI1NTEsIm5iZiI6MCwiaWF0IjoxNTMzNzM4OTUxLCJpc3MiOiJodHRwczovL3Byb3RvdHlwZS5iYW5raWRub3JnZS5uby9hdXRoLWRldi9yZWFsbXMvRGV2ZWxvcCIsImF1ZCI6WyJnZW5lcmljLXJlc291cmNlLXNlcnZlciIsInNpZ25kb2MiLCJmcmF1ZC1kYXRhLXJzIl0sInN1YiI6ImZjY2VlOTM2LTFiNGEtNGNmNC1hNWU4LThkOWYyZTljNThiMiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvaWRjLXRlc3RjbGllbnQiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiIyN2U2OTQ3Ni00Y2Y5LTRhZGEtYWY4Yy1jZDkzYzY4ZmMwZWYiLCJyZXNvdXJjZV9hY2Nlc3MiOnsiZ2VuZXJpYy1yZXNvdXJjZS1zZXJ2ZXIiOnsicm9sZXMiOlsicm9sZTEiXX0sInNpZ25kb2MiOnsicm9sZXMiOlsicmVhZF93cml0ZSJdfSwiZnJhdWQtZGF0YS1ycyI6eyJyb2xlcyI6WyJHZXRTZWN1cml0eURhdGEiXX19fQ.Sppv8D8NobOrMabWAFL4wknQlHKC4CINeDvDVPPL0zLqRquQZ0x_Uwm9Z-KaUV5jsPuVXtt-jduBXhszptupLyhRVrY2v1ASomdvnud-5sFia042VkqqQTjg47u8n1-p9hjie1qYdXMZp504girUOV6DDPL8Wr5rcC2HBS9oHhXBwKOGZQUsK1mfTTz9KYBo4x6XbV8XSlJyvnBUSism3Told40UEetWtxS-78ohjoyQENcn-d02TTsSnyQh-6ZOEPgBtVHovenMi6p4uMlenklcUUefL40GGpMt3gUp7Hk0jaWPTBU363mjFhrrIrAKgFoztYuwt-M0cPmMkdkQXQ",
    "token_type""bearer",
    "not-before-policy": 1520416620,
    "session_state""27e69476-4cf9-4ada-af8c-cd93c68fc0ef"
}

Looking in the internals of the access_token, this reveals that the "aud" claim contains "signdoc" and that the "resource_access" claim contains the 'scope' "signdoc/read_write" 

{
  "jti""0cd01f9a-f181-4f10-beea-b24bb729e4cf",
  "exp": 1533739251,
  "nbf": 0,
  "iat": 1533738951,
  "iss""https://oidc-current.bankidapis.no/auth/realms/current",
  "aud": [
    "generic-resource-server",
    "signdoc",
    "fraud-data-rs"
  ],
  "sub""fccee936-1b4a-4cf4-a5e8-8d9f2e9c58b2",
  "typ""Bearer",
  "azp""oidc-testclient",
  "auth_time": 0,
  "session_state""27e69476-4cf9-4ada-af8c-cd93c68fc0ef",
  "acr""1",
  "allowed-origins": [],
  "resource_access": {
    "generic-resource-server": {
      "roles": [
        "role1"
      ]
    },
    "signdoc": {
      "roles": [
        "read_write"
      ]
    },
    "fraud-data-rs": {
      "roles": [
        "GetSecurityData"
      ]
    }
  },
  "clientId""oidc-testclient",
  "clientHost""172.16.0.158",
  "resource_claims": {},
  "clientAddress""172.16.0.158"
}

Selection of BankID on Mobile or BankID WebClient

The BankID WebClient is by default selected when scope sign is given.

In order to use BankID on Mobile, the login_hint=BIM[:[phoneNumber][:birthDate]] must be given in the authorize request. [...] means optional values. Phonenumber is 8 digits, birthDate is ddmmyy.

To preset nnin in the BankID WebClient set login_hint=[BID]:nnin where nnin is 11 digits. The BID may be dropped.

Full flow API description

The full flow starts by one request where the OIDC client uploads documents, signing properties and a wanted result specification to the SignDoc resource server. The return from this request is a sign_id which is used as a query parameter in the authorize request. When the user has finished signing, the OIDC client queries the result from the SignDoc resource server.  

Documented here are the requests for accessing the SignDoc Resource server, the URI is given from the .well-known endpoint. The authorize request is documented above.

Action

Type

Headers

Body or query

Status and result examples

Upload signing orderPOST

authorization: Bearer access_token

Content-type: application/json

Body: a Json object specifying the signing to be performed

{"signProperties"// mandatory
 "orderName""..."// Mandatory Signing order name, ex: "Paper of intent"
 "documentDisplayMode" "interior" "window" "overlay"// optional, default is interior
 "showConfirmation" "true" "false"// optional default true, shows "Signing completed" after signing
 "showUnderstanding""true" "false"// optional default true, shows the "content is understood" box
 "timeoutSeconds" "12345"// default 300 secs, timeout when user is actually signing, an order pickup
                             //timeout time is added before and after posting the order
 
 },
"documents"// a mandatory array of documents, at least one
 // content of each document must be one of "text", "pdf" or the pair ("xml" and "xsl")
 {
 "description""..." // optional string default is "orderName - #documentnumber where first is 1"
 "text" "..."// text to sign
 "pdf"" base64Encoded(pdfbytes) ",
 "xml""xml as string to be used together with the xsl",
 "xsl""xml stylesheet"
 }, more documents ...
 ],
"resultContent"// optional array of one or more resultspecifiers, default is basicSignature
 ["basicSignature"// add basicSignatures to the result, endUser and merchant (text as ISO-8859 here)
 "documentHash"// add hash of documents to be signed, SHA256 (text as UTF-8 for this value)
 "sdo" // Build sdo for signed documents
 ]
}



201 CREATED if all is ok

{
"sign_id": "6be61b7e-e137-4c68-a396-5377ca76a1dd"
}

400 BAD request and a message if something missing

{
"message": "Unknown result specifiers: '[sdoo]'"
}

403 FORBIDDEN if missing or invalid access_token

{
"message": "AccessToken is invalid"
}

Check statusGETauthorization: Bearer access_token

query: ?sign_id=value of signid from Upload signing order


200 OK if all is ok

{
"orderState": "ORDER_RECEIVED"
}

Possible results are ORDER_RECEIVEDGRABBED_BY_IDPUSER_SIGNING,

FAILED, CANCELLED or SIGN_COMPLETED

204 NO CONTENT if sign_id not found or sign_id is owned by another client

no body returned (remark timeout is 90 sec before starting signing)

400 BAD request and a message if something missing

{
"message": "signId should not be empty"
}

403 FORBIDDEN if missing or invalid access token

{
"message": "AccessToken is invalid"
}

Download result and cleanup

When this one is called,

the session associated

to the sign_id is removed

DELETEauthorization: Bearer access_token

query: ?sign_id=value of signid from Upload signing order.

Response content is dependent on the resultContent value in registration of the signing order and the state of the order.

No matter the order state, this method returns the current state and removes the session.

Below is a maximized result. If the orderState is not "SIGN_COMPLETED", the entries specified by "resultContent" is absent.

{
 "documentHashs": [ // array of hashes over the documents to be signed in the same order as
                    //in the signing order, one hash pr. doc, use "documentHash" in "resultContent"
 "w5SZXrar2s7lR+lafX4Bx9v8/dm2xs5eybCTUOE9rao="
 ],
 "sdos": [ // the sdo represented as base64 (unpack to UTF-8), one for each document, set by "sdo"
 "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZ......"
 ],
 "merchantSignatures": [ // the merchant signature, one for each document, set by "basicSignature"
 "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANB...."
 ],
 "endUserSignatures": [ // the endUser signature, one for each document, set by "basicSignature"
 "MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBAA....."
 ],
 "signId""910bd95b-41c0-4b6d-ae3f-d9458110d12a"// the sign_id used
 "clientId""oidc-testclient"// OIDC client used
 "orderState""SIGN_COMPLETED"// Current order state
 "orderName""Overlay-example" // name of order
}

204 NO CONTENT if sign_id not found or sign_id is owned by another client

no body returned (remark timeout is 90 sec after enduser completed signing)

400 BAD request and a message if something missing

{
"message": "signId should not be empty"
}

403 FORBIDDEN if missing or invalid access token

{
"message": "AccessToken is invalid"
}

Upload Request examples:

Simplified sign flow expressed like a full flow

The signing order below behaves identically to the simplified flow given by sign_txt=VGhlIHNpbmdfdHh0IGdpdmVuIGlzIHBsYWNlZCBoZXJl

{
    "signProperties":   {
        "orderName":"bankID",
        "showUnderstanding":"true",
        "showConfirmation":"false",
        "documentDisplayMode""interior",
        "timeoutSeconds":600
    },
    "documents":[
        {"text":"The sign_txt given is placed here"}
    ],
    "resultContent":["basicSignature","documentHash"]
}

Signing of two texts and one xml 

{
    "signProperties":   {
        "orderName":"test order",
        "showUnderstanding":"true",
        "showConfirmation":"false",
        "documentDisplayMode""overlay"
    },
    "resultContent":["sdo"],
    "documents":[
        {"description":"my first document to sign""text":"Testing å and ø...."},
        {"text":"Short version of a document having two linefeeds\n\ndescription when listet in BankID WebCLient should be 'test order - 2"},
        {"description""An xslt transformation",
        "xml":"<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?><Persons><Person><Name>Ola Normann</Name><Street>Olaveien 99</Street><Zip>0014</Zip><City>Oslo</City></Person><Person><Name>Kalle</Name><Street>Ibsens veg 30</Street><Zip>9876</Zip><City>Stockholm</City></Person><Person><Name>Kari IngridsDottir</Name><Street>Nedre Sofustrøa 2</Street><Zip>7070</Zip><City>Bosberg</City></Person><Person><Name>Olava Olsen</Name><Street>Øvre Strete 12</Street><Zip>2211</Zip><City>Sørpå</City></Person></Persons>",
"xsl":"<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?><xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"> xsl:output method=\"html\" encoding=\"ISO-8859-1\" doctype-system=\"http://www.w3.org/TR/html4/loose.dtd\" doctype-public=\"-//W3C//DTD HTML 4.01 Transitional//EN\" indent=\"yes\" /><xsl:template match=\"/\"><HTML><BODY><h2>Eksempel på XML/XSL.</h2><p>Under er en liste personer tatt fra XML-en</p><ul><xsl:for-each select=\"Persons/Person\"><xsl:if test=\"0 = position() mod 2\"><li  style='color:red'><xsl:value-of select=\"Name\"/>,<xsl:value-of select=\"Street\"/>,<xsl:value-of select=\"Zip\"/>,<xsl:value-of select=\"City\"/></li></xsl:if><xsl:if test=\"1 = position() mod 2\"><li style='color:green'><xsl:value-of select=\"Name\"/>,<xsl:value-of select=\"Street\"/>,<xsl:value-of select=\"Zip\"/>,<xsl:value-of select=\"City\"/></li></xsl:if></xsl:for-each></ul></BODY></HTML></xsl:template></xsl:stylesheet>"
}
    ]
}


sign_id Expiration Time

A sign_id is valid 90 secs (config value) after created, then, while user is signing, the timeout given in the order (300s default), then 90 secs after the user for retrieval by the OIDC client.

BankID on Mobile limitations

Bankid on mobile signs only text, the text must be maximum 118 characters long and has a restricted character set. Legal characters are for now 

[0-9] [a-z] [æ] [ø] [å] [A-Z] [Æ] [Ø] [Å] [ ][CR] [LF] [#] [$] [%-&] [(-?] [@] [¡] [£] [¤] [¥] [§] [¿] [Ä] [Ç] [É] [Ñ] [Ö] [Ü] [ß] [à] [ä] [è] [é] [ì] [ñ] [ò] [ö] [ù]

Flags like show understanding and show confirmation do not apply, and english locale on the mobile is not supported.


Utility code for base64 in javascript


// Format a string str such that it may be used as the sign_txt value
function b64EncodeUnicode(str) {
    // use encodeURIComponent to get %-encoded UTF-8,
    // convert these encodings into raw bytes
    // feed the bytes btoa.
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toRawBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
        }));
}
 
 
function b64DecodeUnicode(str) {
    // This is the inverse of b64EncodeUnicod; from bytestream, to %-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}
 
 
// Computes the sha 256 of the given string, then converts the result to a b64 string
// The result is directly comparable to the hash: value in a sign_result: in id_token.
// idToken.signresult.hash === await computeSha256AsB64FromUtf8("original String to be signed")
//
function computeSha256AsB64FromUtf8(str) {
    var buffer = new TextEncoder().encode(str);
    return crypto.subtle.digest("SHA-256", buffer).then(function (hash) {
        return btoa(String.fromCharCode.apply(nullnew Uint8Array(hash)));
    });
}

  • No labels