YASMIN (Yet Another jSon Message notatIoN), was designed to offer an alternative to
REST based APIs, by bringing the following
enhancements to the table:
- Support for event based communication like
WebSocket
and
postMessage()
- Unified signature option for requests and responses
- Unified serialization and deserialization methods for requests and responses
Note: If you are looking for an API you won't find one because YASMIN
is a
message-centric concept, where "self-sufficient" messages using
JSON syntax
have been "liberated"
from interfering HTTP bindings, URIs, and clunky
SOAP envelopes.
However, nothing prevents you from
mapping APIs into messages!
Although YASMIN was initially created for security-related client-applications
like "wallets", credential enrollment, etc., it should
be equally applicable for quite different tasks including bank-to-bank transactions.
Aided by WebSockets, high-performance, interactive Web applications also seem to be in scope,
in particular those building on the
SPA concept.
One of the requirements (derived from XML and XML Schema),
was the ability recognizing the type of a message (object) by including such information in the message itself.
This allows you to process different messages through a single dispatcher mechanism as well
as easing debugging and documentation.
The following sample message from the
Saturn
project shows the YASMIN typing scheme:
{
"@context": "https://webpki.github.io/saturn/v3",
"@qualifier": "PayeeAuthority",
"authorityUrl": "https://payments.bigbank.com/payees/86344",
"providerAuthorityUrl": "https://payments.bigbank.com/authority",
"localPayeeId": "86344",
"commonName": "Demo Merchant",
"homePage": "https://demomerchant.com",
"accountVerifier": {
"algorithm": "S256",
"hashedPayeeAccounts": ["kUwpqk-cbkDaBjwDD_etPSh_FtC-Ap2K_A2MQzXNy_U", "hMALZWfHmLNN0fzWjWZisF_0y4Q4M_p7pJsULyg1xWM"]
},
"signatureParameters": [{
"algorithm": "ES256",
"publicKey": {
"kty": "EC",
"crv": "P-256",
"x": "_7bQ8JTt6_r1lh46kwmwypqMkZOJ0cYs-w2LHWOYt5M",
"y": "tLcyLWDQoAk4cMaWY7BdV3JaywQQoLxO2WQ30Klj6fc"
}
}],
"timeStamp": "2020-03-21T08:02:30Z",
"expires": "2020-03-21T09:02:31Z",
"issuerSignature": {
"algorithm": "ES256",
"publicKey": {
"kty": "EC",
"crv": "P-256",
"x": "-Vr8Wk3ygt5J2_J3R8TrRaa-AWW7ZiXa6q1P7ELs6gc",
"y": "Vuc6z3WiZ3tgXTXvU6F5qdiiYePWeUI1q9Tx83ySDcM"
},
"value": "OcRz5yxFFa_lJZ03czji6d4GUpPPBXRI....PmpuRR8N5UUfDhdlJjTUB_yZkd1rCK1w"
}
}
Explanation: The @context
property is essentially a counterpart to an XML name-space,
while @qualifier
emulates an XML top-level element.
In a typical implementation @context
is a URI constant identifying "audience", transport method, security considerations, etc.,
while @qualifier
denotes actual message type.
The point with having designated "keywords" for type information is for
enabling creation of YASMIN tools as well as facilitating
automatic object instantiation.
A major advantage with typed (and self-contained) messages is that they become transport-independent,
allowing them to be embedded in HTML pages, sent over NFC,
or be pushed through various proxies without losing their meaning.
Requests containing YASMIN messages must be invoked by HTTP POST,
while requests that only lookup existing data may also use GET (and any for the application suitable URI scheme including REST).
Successful requests including "soft" (handled) errors like
"Insufficient Funds" should return HTTP 200,
while "hard" (non-recoverable) errors should return an applicable HTTP 4xx or 5xx status code,
plus a suitable text message for logging and debugging purposes.
POSTed messages should preferably be idempotent within their validity time to facilitate secure retransmissions.
Below is an example of a POSTed transaction request:
POST /transact HTTP/1.1
User-Agent: Java
Host: saturn.mybank.com
Content-Type: application/json Mandatory MIME type
Content-Length: 1234
{
"@context":"some-name-space",
"@qualifier":"some-object-type",
Additional Transaction Payload Request Properties
"signature":{...} Optional signature
}
GET and POST responses follow the same pattern:
HTTP/1.1 200 OK
Content-Type: application/json Mandatory MIME type
Content-Length: 9541
{
"@context":"some-name-space",
"@qualifier":"some-object-type",
Additional Response Properties
"signature":{...} Optional signature
}
If the HTTP "Accept"
header is defined it must only contain a single "application/json"
property.
JSON String = JSON.stringify( JavaScript Object); |
| // Hey, I don't like complexity! |
JavaScript Object = JSON.parse( JSON String); |
| // Me neither :-) |
The vision was to have something similar to
XML DSig
but preferably not depending on megabytes of fairly intricate library code.
Using the IETF JOSE suite of JSON based cryptographic standards would have been cool, but
the
JOSE signature scheme
forces you encoding messages in Base64Url
which do not only make messages
unreadable by humans, but worse,
disrupts the YASMIN message typing scheme.
Since existing JSON signature solutions did not match YASMIN,
a new signature scheme had to be developed which (completely unintentional),
turned out to be compatible with recent versions of JavaScript as well!
Due to the fact that YASMIN messages are based on pure JSON, countersigning and embedding previous requests do not require any
special arrangements.
Message Handlers and Decoders
The devised scheme does not specify whether all requests should go to a single handler
(e.g. Java Servlet) and URI,
but in the case a handler supports more than one message type you need to dispatch
messages to the appropriate sub-handler. This is trivial using typed messages here expressed in JavaScript:
var input = JSON.parse(readRequest(request)); // Hypothetical method: "readRequest()"
if (input['@context'] != 'https://example.com/system') {
throw new TypeError('Unrecognized Type');
}
var output;
switch (input['@qualifier']) {
case 'Authorize':
output = authorize(input); // Handler
break;
case 'CommitPayment':
output = commitPayment(input); // Handler
break;
default:
throw new TypeError('Unrecognized Message');
}
writeResponse(response, JSON.stringify(output)); // Hypothetical method: "writeResponse()"
In case you prefer APIs before
messages, the following fictitious example in Java illustrates how this
could be achieved using YASMIN:
@YService("https://iotstandards.org/device")
public class MyService {
@YMethod
@YSimpleReturn("currentValue")
public double setFurnaceTemperature(@YParam("deviceId") String deviceId,
@YParam("value") double value) throws IOException {
Actual method code...
}
}
A request message would presumably look like the following:
{
"@context": "https://iotstandards.org/device",
"@qualifier": "setFurnaceTemperature",
"deviceId": "#3407-B",
"value": 223.5
}
A matching response would then be:
{
"@context": "https://iotstandards.org/device",
"@qualifier": "setFurnaceTemperature#R",
"currentValue": 104.2
}
Note: At the time of writing there are currently no specific tools available for creating YASMIN based APIs.
The security and privacy characteristics of YASMIN based systems are no worse (or better) than
offered by the underlying transport and the quality of the implementation.
Using signed messages is recommended for integrity and authentication purposes (if possible in the actual scenario).
HTTP requests should use TLS (HTTPS) to cater for privacy and end-point authentication.
Unsigned YASMIN messages can be created with any JSON tool.
Signed YASMIN messages need specific library support, currently only available on GitHub:
Date | Ver | Comment |
2017-01-27 | 0.5 | Initial publication in HTML5 |
2017-04-16 | 0.6 | Changed public keys to use JWK format |
2020-02-03 | 0.61 | Updated references |
Anders Rundgren (anders.rundgren.net@gmail.com
), WebPKI.org