amzn / selling-partner-api-models

This repository contains OpenAPI models for developers to use when developing software to call Selling Partner APIs.
Apache License 2.0
580 stars 730 forks source link

How to get signature in Java #2960

Closed Muschke closed 2 years ago

Muschke commented 2 years ago

I'm trying to make the connections to the sp-api from my java program. We tested all the requests in Postman and are now trying to implement is. The developer docs are a bit vague. Since I have a lot of places where I can do something slightly different which results in a bad signature, it is a pain in the ass to get this right. Would you please take a look at my java code and comment on things that I do wrong or different?

` public ResponseEntity getAssumeRolCredentials() throws ApiException {

    RestTemplate rest = new RestTemplate();
    HttpHeaders headers = new HttpHeaders();
    /*params*/
    final String VERSION = "2011-06-15";
    final String ACTION = "AssumeRole";
    final String ROLESESSIONNAME = "Test1500";
    final String ROLEARN = "arn:aws:iam::796138068875:role/MYRN";
    final String DURATIONSECONDS = "3600";
    final String URI = "https://sts.amazonaws.com/?Version="+VERSION+"&Action="+ ACTION 
      +"&RoleSessionName="+ROLESESSIONNAME+"&RoleArn="+ROLEARN+"&DurationSeconds=" + DURATIONSECONDS;

/*generate signature key*/
    String accesKey = "hidehidehide";
    String secretKey = "hidehidehidehidehidehidehidehidehide";
    LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
    String dateStamp = Aws4SignatureKeyGenerator.getDate(now);
    String dateTimeStamp = Aws4SignatureKeyGenerator.getTimeStamp(now);
    String regionName = "us-east-1";
    String serviceName = "sts";

    //1. create canonical header
    StringBuilder canonicalURL = new StringBuilder("");
    //httpmethod
    canonicalURL.append("GET").append("\n");
    //canonicalURL = canonicalURL == null || canonicalURL.toString().trim().isEmpty() ? canonicalURL.append("/") : canonicalURL; //don't think this line is necessary
    canonicalURL.append("https://sts.amazonaws.com").append("\n");
    //canonical querystring -- alphabetic, yes?, They are already strings so I don't need to encode extra
    canonicalURL.append("Action=" + ACTION + "&").append("DurationSeconds=" + DURATIONSECONDS + "&").append("RoleArn=" + ROLEARN + "&").append("RoleSessionName=" + ROLESESSIONNAME + "&").append("Version="
            + VERSION).append("\n");
    //canonicalURL.append("Version=" + VERSION + "&").append("Action=" + ACTION + "&").append("RoleSessionName=" + ROLESESSIONNAME + "&").append("RoleArn=" + ROLEARN + "&").append("DurationSeconds=" + DURATIONSECONDS).append("\n");
    /*canonicalURL.append("action=" + ACTION + "&").append("durationseconds=" + DURATIONSECONDS + "&").append("rolearn=" + ROLEARN + "&").append("rolesessionname=" + ROLESESSIONNAME + "&").append("version="
            + VERSION).append("\n");*/

    //canonical headers --> yes/no?
    /*
    canonicalURL.append(Lowercase("host")+":"+Trim("https://sts.amazonaws.com")+"\n");
    canonicalURL.append(Lowercase("Content-Type")+":"+Trim()+"\n");
    canonicalURL.append(Lowercase("x-amz-Date")+":"+Trim(dateTimeStamp)+"\n");
    canonicalURL.append(Lowercase("x-amz-content-sha256")+":"+Trim("application/x-www-form-urlencoded")+"\n");
    */

    //signed headers -- SignedHeaders=host;x-amz-date, "
    canonicalURL.append("SignedHeaders=host;x-amz-date").append("\n");
    //hash elements --> moet niet bij getrequest   !!de hash van een empty string
    System.out.println("canonicalUrl:"+ canonicalURL.toString());

    //2. create stringToSign
    StringBuilder stringToSign = new StringBuilder("");
    //algoritme
    stringToSign.append("AWS4-HMAC-SHA256").append("\n");
    //timestamp
    stringToSign.append(dateStamp +"T000000Z").append("\n");
    //scope
    stringToSign.append(dateStamp+"/"+regionName+"/"+ serviceName + "/aws4_request").append("\n");
    //link canonical met stringToSign
    stringToSign.append(DigestUtils.sha256Hex(canonicalURL.toString()));
    System.out.println("stringTosign: " + stringToSign);

    /*generate signing key*/
    byte[] signature = null;
    try {
        signature = Aws4SignatureKeyGenerator.getSignatureKey(secretKey, dateStamp, regionName, serviceName);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("signing key: " + signature);
    //calculate signature: byte[] signature = HmacSHA256(signature, stringToSign);
    try {
        signature = Aws4SignatureKeyGenerator.HmacSHA256(stringToSign.toString(), signature);
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    System.out.println("signature: " + signature);
    //encode signature
    String signatureString = Aws4SignatureKeyGenerator.bytesToHex(signature);
    System.out.println("signatureHex: " + signatureString);
    /*log in headers*/
    String AuthorizationHeader = "AWS4-HMAC-SHA256 Credential="+accesKey+"/"+dateStamp+"/"+regionName+"/"+serviceName+"/aws4_request, SignedHeaders=host;x-amz-date, Signature=" + signatureString;
    headers.add("authorization", AuthorizationHeader);
    headers.add("X-Amz-Date", dateTimeStamp);
    headers.add("Accept", "application/xml");

    HttpEntity<String> entity = new HttpEntity<>(headers);

    try { return rest.exchange(URI, HttpMethod.GET, entity, AssumeRoleRespons.class);}
    catch(HttpClientErrorException | HttpServerErrorException e) {
        throw new ApiException( "Something went wrong while receiving assumeRoleCredentials from Amazon.com: "
                + e.getResponseBodyAsString(), e);
    }

}

Just for the completion, I've added the functions I use above here below:

`

public class Aws4SignatureKeyGenerator { /function to generate signature/ static byte[] HmacSHA256(String data, byte[] key) throws Exception { String algorithm="HmacSHA256"; Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes("UTF-8")); }

static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
    byte[] kSecret = ("AWS4" + key).getBytes("UTF-8");
    byte[] kDate = HmacSHA256(dateStamp, kSecret);
    byte[] kRegion = HmacSHA256(regionName, kDate);
    byte[] kService = HmacSHA256(serviceName, kRegion);
    byte[] kSigning = HmacSHA256("aws4_request", kService);
    return kSigning;
}

static String bytesToHex(byte[] bytes) {
     final char[] hexArray = "0123456789ABCDEF".toCharArray();

    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars).toLowerCase();
}

/*get dateTimeStamp*/
static public String getTimeStamp(LocalDateTime dateTime) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
    String formatDateTime = dateTime.format(formatter);
    return formatDateTime;
}

/* get dateStamp */
static public String getDate(LocalDateTime dateTime) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
    String formatDateTime = dateTime.format(formatter);
    return formatDateTime;
}

}

`

adrian-amz commented 2 years ago

Hello @Muschke,

Thank you for reaching out regarding "How to get Signature in Java".

You can find details regarding this in our Generating a Java SDK with LWA token exchange and authentication, Generating a Java client library, Automate your SP-API calls using Java SDK and Connecting to the Selling Partner API using a generated Java SDK.

If the information available in the documentation link provided above does not fully resolve your inquiry, please open a support case with us.

Thanks, Adrian C. Selling Partner API Developer Support