From a7e42edee290b8c1f105cda0823ebc17e6394d15 Mon Sep 17 00:00:00 2001 From: Daniel Persson Date: Wed, 19 Jan 2022 15:31:22 +0100 Subject: [PATCH 1/3] [framework] webservice: new "urlsign" parameter which can be used instead of "sign". It is the same as "sign" but uses base64 encoding with URL safe alphabet (and therefore needs no additional URL encoding step). See https://datatracker.ietf.org/doc/html/rfc4648#section-5. --- .../pipeline/webservice/Authenticator.java | 21 +++++++++++++------ .../impl/AuthenticatedResource.java | 12 ++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/framework/webservice/src/main/java/org/daisy/pipeline/webservice/Authenticator.java b/framework/webservice/src/main/java/org/daisy/pipeline/webservice/Authenticator.java index c1c40bbdc9..55b6e4d2e7 100644 --- a/framework/webservice/src/main/java/org/daisy/pipeline/webservice/Authenticator.java +++ b/framework/webservice/src/main/java/org/daisy/pipeline/webservice/Authenticator.java @@ -31,19 +31,19 @@ public Authenticator(RequestLog requestLog) { this.requestLog = requestLog; } - public boolean authenticate(Client client, String hash, String timestamp, String nonce, String URI, long maxRequestTime) { + public boolean authenticate(Client client, String hash, String urlhash, String timestamp, String nonce, String URI, long maxRequestTime) { // rules for hashing: use the whole URL string, minus the hash part (&sign=) // important! put the sign param last so we can easily strip it out int idx = URI.indexOf("&sign=", 0); - + if(urlhash != null) { + idx = URI.indexOf("&urlsign=", 0); + } if (idx > 1) { String hashuri = URI.substring(0, idx); String clientSecret = client.getSecret(); String serverHash = ""; try { - serverHash = calculateRFC2104HMAC(hashuri, clientSecret); - SimpleDateFormat UTC_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); UTC_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -56,6 +56,12 @@ public boolean authenticate(Client client, String hash, String timestamp, String e.printStackTrace(); return false; } + + serverHash = calculateRFC2104HMAC(hashuri, clientSecret, false); + if(urlhash != null) { + serverHash = calculateRFC2104HMAC(hashuri, clientSecret, true); + hash = urlhash; + } if(!hash.equals(serverHash)) { logger.error("Hash values do not match"); return false; @@ -97,7 +103,7 @@ public static URI createUriWithCredentials(String uri, Client client) { String hash; URI newUri = null; try { - hash = calculateRFC2104HMAC(uristring, client.getSecret()); + hash = calculateRFC2104HMAC(uristring, client.getSecret(), false); String authUri = uristring + "&sign=" + hash; newUri = new URI(authUri); } catch (SignatureException e) { @@ -142,7 +148,7 @@ private boolean checkValidNonce(Client client, String nonce, String timestamp) { * @throws * java.security.SignatureException when signature generation fails */ - private static String calculateRFC2104HMAC(String data, String secret) throws java.security.SignatureException { + private static String calculateRFC2104HMAC(String data, String secret, boolean urlEncoded) throws java.security.SignatureException { String result; try { // get an hmac_sha1 key from the raw key bytes @@ -157,6 +163,9 @@ private static String calculateRFC2104HMAC(String data, String secret) throws ja // base64-encode the hmac result = Base64.getEncoder().encodeToString(rawHmac); + if (urlEncoded) { + result = Base64.getUrlEncoder().withoutPadding().encodeToString(rawHmac); + } } catch (Exception e) { throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); } diff --git a/framework/webservice/src/main/java/org/daisy/pipeline/webservice/impl/AuthenticatedResource.java b/framework/webservice/src/main/java/org/daisy/pipeline/webservice/impl/AuthenticatedResource.java index 583466abb9..8368e5b72f 100644 --- a/framework/webservice/src/main/java/org/daisy/pipeline/webservice/impl/AuthenticatedResource.java +++ b/framework/webservice/src/main/java/org/daisy/pipeline/webservice/impl/AuthenticatedResource.java @@ -40,9 +40,15 @@ private boolean authenticate() { } this.client=optionalClient.get(); RequestLog requestLog = webservice().getStorage().getRequestLog(); - return new Authenticator(requestLog).authenticate(this.client, getQuery().getFirstValue("sign"), - getQuery().getFirstValue("time"), getQuery().getFirstValue("nonce"), getReference().toString(), - maxRequestTime); + return new Authenticator(requestLog).authenticate( + this.client, + getQuery().getFirstValue("sign"), + getQuery().getFirstValue("urlsign"), + getQuery().getFirstValue("time"), + getQuery().getFirstValue("nonce"), + getReference().toString(), + maxRequestTime + ); } public boolean isAuthenticated() { From 3117bbf92619cfdde7917704cc55da0eb913f1a8 Mon Sep 17 00:00:00 2001 From: Daniel Persson Date: Wed, 19 Jan 2022 15:31:22 +0100 Subject: [PATCH 2/3] [clientlib] Use new "urlsign" parameter for web service authentication --- .../pipeline/client/http/Pipeline2HttpClient.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/clientlib/java/clientlib-java-httpclient/src/main/java/org/daisy/pipeline/client/http/Pipeline2HttpClient.java b/clientlib/java/clientlib-java-httpclient/src/main/java/org/daisy/pipeline/client/http/Pipeline2HttpClient.java index cc84c8f216..77109a2df5 100644 --- a/clientlib/java/clientlib-java-httpclient/src/main/java/org/daisy/pipeline/client/http/Pipeline2HttpClient.java +++ b/clientlib/java/clientlib-java-httpclient/src/main/java/org/daisy/pipeline/client/http/Pipeline2HttpClient.java @@ -269,14 +269,10 @@ public static String url(String endpoint, String path, String username, String s } url = url.substring(0, url.length() - 1); - // add parameter "sign" + // add parameter "urlsign" try { String hash = calculateRFC2104HMAC(url, secret); - try { - url += "&sign" - + "=" + URLEncoder.encode(hash, "UTF-8"); } - catch (UnsupportedEncodingException e) { - throw new Pipeline2Exception("Unsupported encoding: UTF-8", e); } + url += "&urlsign=" + hash; } catch (SignatureException e) { throw new Pipeline2Exception("Could not sign request."); } @@ -313,7 +309,7 @@ private static String calculateRFC2104HMAC(String data, String secret) throws ja byte[] rawHmac = mac.doFinal(data.getBytes()); // base64-encode the hmac - result = Base64.getEncoder().encode(rawHmac); + result = Base64.getUrlEncoder().withoutPadding().encode(rawHmac); } catch (Exception e) { throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); From 6e9ffb5d8e3a4bf4b9099b81167393ee45ee5f7a Mon Sep 17 00:00:00 2001 From: Daniel Persson Date: Thu, 20 Jan 2022 09:41:10 +0100 Subject: [PATCH 3/3] [website] Document new "urlsign" parameter for web service authentication --- website/src/_wiki/WebServiceAuthentication.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/website/src/_wiki/WebServiceAuthentication.md b/website/src/_wiki/WebServiceAuthentication.md index 23e00a0a66..7ae28d21bf 100644 --- a/website/src/_wiki/WebServiceAuthentication.md +++ b/website/src/_wiki/WebServiceAuthentication.md @@ -46,3 +46,25 @@ This is the URI to submit to the Web Service. If `myclient` has a permission level to do what they want, then their request will be accepted. Currently, only admin requests require a client to have a special permission level (`ADMIN` instead of the default `CLIENTAPP`). + +### Signing using the `urlsign` parameter instead + +Another approach that is similar but might be simpler to handle is using the `urlsign` parameter. +To do this, you follow the approach above but generate a Base64Url encoded string instead and add that at the end of the URL. + +For example + +`http://example.org/ws/scripts?authid=myclient&time=2012-02-09T02:23:40Z&nonce=533473712461604713238933268313&urlsign=gq_lpIuWqEDjhWviAjyccNTzdZk` + +This format is supported by libraries or built-in for most languages. To describe it briefly, you need to generate a standard Base64, replace some characters and remove the padding. + +Let's take the example of `gq/lpIuWqEDjhWviAjyccNT+zdZk==` + +The replacements we need to do is +``` +replace + with - +replace / with _ +remove padding of = +``` + +The result we get then is `gq_lpIuWqEDjhWviAjyccNT-zdZk` \ No newline at end of file