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()); 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() { 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