API 的 eBay 数字签名:215120 + 签名验证无法满足请求

2024-01-25

我想对 eBay 进行 API 调用以进行订购消除 https://developer.ebay.com/devzone/post-order/post-order_v2_cancellation__post.html(添加争议)。我正在关注 eBay 提供的文件https://developer.ebay.com/develop/guides/digital-signatures-for-apis https://developer.ebay.com/develop/guides/digital-signatures-for-apis。当我调用 API 时,出现以下错误。

{
  "errors": [
    {
      "errorId": 215120,
      "domain": "ACCESS",
      "category": "REQUEST",
      "message": "Signature validation failed",
      "longMessage": "Signature validation failed to fulfill the request."
    }
  ]
}

这是我的 Java 代码,我从其中复制的https://github.com/eBay/digital-signature-java-sdk/blob/main/src/main/java/com/ebay/developer/SignatureService.java https://github.com/eBay/digital-signature-java-sdk/blob/main/src/main/java/com/ebay/developer/SignatureService.java

import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.signers.RSADigestSigner;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.util.encoders.Base64;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * // https://github.com/eBay/digital-signature-java-sdk/blob/main/src/main/java/com/ebay/developer/SignatureService.java
 * // https://github.com/eBay/digital-signature-java-sdk
 *
 */
public class SignatureService {

    private static final List<String> SIGNATURE_PARAMS = Arrays.asList("content-digest", "x-ebay-signature-key", "@method", "@path", "@authority");
    public static final String CONTENT_DIGEST = "content-digest";
    public static final String SIGNATURE_PREFIX = "sig1=:";
    public static final String SIGNATURE_INPUT_PREFIX = "sig1=";

    public void sign(HttpRequest request, PrivateKeys privateKeys) {
        request.usingHeader("x-ebay-signature-key", privateKeys.getJwe());
        if(HttpMethodName.POST.equals(request.getHttpMethod())
            || HttpMethodName.PUT.equals(request.getHttpMethod())){
            String contentDigest = generateContentDigest(request.getPayload().getData(), "SHA-256");
            request.usingHeader("Content-Digest", contentDigest);
        }
        String signature = getSignature(request, privateKeys);
        request.usingHeader("Signature", signature);
        request.usingHeader("Signature-Input", SIGNATURE_INPUT_PREFIX + getSignatureInput());
        request.usingHeader("x-ebay-enforce-signature", "true");
    }

    /**
     * Generate Content Digest
     *
     * @param body request body
     * @param cipher ciper to use SHA-256 or SHA-512
     * @return contentDigest content digest
     */
    private String generateContentDigest(String body, String cipher){
        if(StringUtil.nullOrEmpty(body)){
            return null;
        }

        String contentDigest = "";
        try {
            MessageDigest messageDigest = MessageDigest
                    .getInstance(cipher.toUpperCase());
            String digest = new String(Base64.encode(
                    messageDigest.digest(body.getBytes(StandardCharsets.UTF_8))));
            if (StringUtil.nonNullNonEmpty(digest)) {
                contentDigest = cipher + "=:" +
                        digest + ":";
            }

        } catch (Exception ex) {
            throw new RuntimeException(
                    "Error generating Content-Digest header: " + ex.getMessage(),
                    ex);
        }
        return contentDigest;
    }

    /**
     * Get 'Signature' header value
     *
     * @return signature signature
     */
    private String getSignature(HttpRequest httpRequest, PrivateKeys privateKeys){
        try {
            String baseString = calculateBase(httpRequest);
            System.out.println(baseString);
            byte[] base = baseString.getBytes(StandardCharsets.UTF_8);

            Signer signer = new RSADigestSigner(new SHA256Digest());
            AsymmetricKeyParameter privateKeyParameters = PrivateKeyFactory
                    .createKey(getPrivateKey(privateKeys.getPrivateKey()).getEncoded());
            signer.init(true, privateKeyParameters);
            signer.update(base, 0, base.length);
            byte[] signature = signer.generateSignature();

            String signatureStr = new String(Base64.encode(signature));
            return SIGNATURE_PREFIX + signatureStr +
                    ":";
        } catch (CryptoException | IOException ex) {
            throw new RuntimeException(
                    "Error creating value for signature: " + ex.getMessage(), ex);
        }
    }

    /**
     * Method to calculate base string value
     *
     * @return calculatedBase base string
     */
    private String calculateBase(HttpRequest httpRequest){
        Map<String, String> headers = httpRequest.getHeaders();
        try {
            StringBuilder buf = new StringBuilder();
            for (String header : SIGNATURE_PARAMS) {
                if (header.equalsIgnoreCase(CONTENT_DIGEST)
                        && headers.get(CONTENT_DIGEST) == null) {
                    continue;
                }
                buf.append("\"");
                buf.append(header.toLowerCase());
                buf.append("\": ");

                if (header.startsWith("@")) {
                    switch (header.toLowerCase()) {
                        case "@method":
                            buf.append(httpRequest.getHttpMethod().toString().toUpperCase());
                            break;
                        case "@authority":
                            buf.append(httpRequest.getEndPoint().toString().replace("https://", "").replace("/", ""));
                            break;
                        case "@target-uri":
                            buf.append(headers.get("@target-uri"));
                            break;
                        case "@path":
                            buf.append(httpRequest.getResourcePath());
                            break;
                        case "@scheme":
                            buf.append(httpRequest.getAbsoluteURI().getScheme());
                            break;
                        case "@request-target":
                            buf.append(headers.get("@request-target"));
                            break;
                        default:
                            throw new RuntimeException("Unknown pseudo header " + header);
                    }
                } else {
                    if (!headers.containsKey(header)) {
                        throw new RuntimeException("Header " + header + " not included in message");
                    }

                    buf.append(headers.get(header));
                }

                buf.append("\n");
            }

            buf.append("\"@signature-params\": ");
            buf.append(getSignatureInput());
            return buf.toString();
        } catch (Exception ex) {
            throw new RuntimeException("Error calculating signature base: " + ex.getMessage(), ex);
        }
    }

    /**
     * Generate Signature Input header
     *
     * @return signatureInputHeader
     */
    private String getSignatureInput() {
        StringBuilder signatureInputBuf = new StringBuilder();
        signatureInputBuf.append("(");

        for (int i = 0; i < SIGNATURE_PARAMS.size(); i++) {
            String param = SIGNATURE_PARAMS.get(i);
            if(param.equalsIgnoreCase(CONTENT_DIGEST)){
                continue;
            }
            signatureInputBuf.append("\"");
            signatureInputBuf.append(param);
            signatureInputBuf.append("\"");
            if (i < SIGNATURE_PARAMS.size() - 1) {
                signatureInputBuf.append(" ");
            }
        }
        signatureInputBuf.append(");created=");
        signatureInputBuf.append(Instant.now().getEpochSecond());
        return signatureInputBuf.toString();
    }

    /**
     * Get private key value as a file or as a string value
     *
     * @return privateKey private key
     */
    public PrivateKey getPrivateKey(String privateKeyString) {
        byte[] clear = Base64.decode(privateKeyString.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clear);
        try {
            KeyFactory fact = KeyFactory.getInstance("RSA");
            return fact.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }
}

Sample:签名库是由上面的代码生成的。

"x-ebay-signature-key": ************* (JWE from Key Management API)
"@method": POST
"@path": /post-order/v2/cancellation
"@authority": apiz.ebay.com
"@signature-params": ("x-ebay-signature-key" "@method" "@path" "@authority");created=1673328807

Note:不记名令牌是从不同的文件附加的。

已经访问了以下解决方案,但对我不起作用。

API VBA 的 eBay 数字签名可以,但 Python 签名验证无法满足请求 https://stackoverflow.com/questions/74530218/ebay-digital-signatures-for-apis-vba-okay-but-python-signature-validation-failed

https://forums.developer.ebay.com/questions/52521/digital-signatures-for-apis-steps-to-completion.html https://forums.developer.ebay.com/questions/52521/digital-signatures-for-apis-steps-to-completion.html

https://forums.developer.ebay.com/questions/50518/digital-signatures-for-apis.html https://forums.developer.ebay.com/questions/50518/digital-signatures-for-apis.html

用于 API 签名标头生成的 eBay 数字签名 https://stackoverflow.com/questions/74234508/ebay-digital-signatures-for-apis-signature-header-generation


我遇到了和你类似的问题(尝试 POST 时 eBay 数字签名验证失败 https://stackoverflow.com/questions/76175620/ebay-digital-signature-validation-failed-when-trying-to-post/76227170)并设法解决它。

我使用的是 python,但原理应该是相同的。希望这可以帮助

我的新签名库现在看起来像这样:

params = (
   f'"content-digest": sha-256=:{digest}:\n'
   f'"x-ebay-signature-key": {ebay_public_key_jwe}\n'
   f'"@method": POST\n'
   f'"@path": {url.path}\n'
   f'"@authority": {url.netloc}\n'
   f'"@signature-params": {signature_params}'
).encode()

注意属性的排列顺序

和标题:

headers = {
   "Authorization": "TOKEN " + access_token,
   "Signature-Input": f'sig1={signature_input}',
   "Signature": f"sig1=:{signature}:",
   "Accept": "application/json",
   "Content-Type": "application/json",
   "x-ebay-signature-key": ebay_public_key_jwe,
   "content-digest": f"sha-256=:{content_digest}:"
}

Note:因为我正在打电话/post-order/v2/return/{return_id}/issue_refund我必须更换的 API 端点Bearer with TOKEN在 - 的里面Authorization header.

Edit:请注意,您的签名输入需要具有content-digest像这样的属性:

("content-digest" "x-ebay-signature-key" "@method" "@path" "@authority");created=1673328807
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

API 的 eBay 数字签名:215120 + 签名验证无法满足请求 的相关文章

随机推荐