【问题标题】:eBay Digital Signatures for APIs VBA okay but Python Signature validation failed to fulfill the requesteBay Digital Signatures for APIs VBA okay but Python Signature validation failed to fulfill the request
【发布时间】:2022-12-01 14:12:52
【问题描述】:

I can successfully get a response from eBay's REST API with a digital signature using the following VBA code (I have not included all the code as it is quite a lot, but below is the important part):

Set http = New MSXML2.XMLHTTP60
created_time = DateDiff("s", "1/1/1970", now())
dig_sig = TRUE
url = "https://apiz.ebay.com/sell/finances/v1/seller_funds_summary"
With http
    .Open "GET", url
    .setRequestHeader "Content-Type", "application/json"
    .setRequestHeader "Accept", "application/json"
    .setRequestHeader "Authorization", "Bearer " & USER_TOKEN
    If dig_sig Then
        signature_input = "sig1=(""x-ebay-signature-key"" ""@method"" ""@path"" ""@authority"");created=" & created_time
        signature = getSignature("GET", _
            "/sell/finances/v1/seller_funds_summary", _
            "apiz.ebay.com", _
            created_time)
        .setRequestHeader "signature-input", signature_input
        .setRequestHeader "signature", "sig1=:" & signature & ":"
        .setRequestHeader "x-ebay-signature-key", JWE
        .setRequestHeader "x-ebay-enforce-signature", "true"
    End If
    .Send
    Do Until .readyState = 4
        DoEvents
        Application.Wait (Now + 0.00005)
    Loop
    Debug.Print .responseText
End With

This successfully returns the seller funds summary with either dig_sig set to TRUE or FALSE.

Now, using Python with the same headers and signature (I have compared the signature and it is identical):

import time, requests

dig_sig = True
request_url = "https://apiz.ebay.com/sell/finances/v1/seller_funds_summary"
if dig_sig:
    created_time = int(time.time())
    sig_input = f'sig1=("x-ebay-signature-key" "@method" "@path "@authority");created={created_time}'
    signature = getSignature("GET", 
        "/sell/finances/v1/seller_funds_summary",
        "apiz.ebay.com",
        created_time)
    public_key_jwe = *** jwe supplied by eBay ***
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Authorization": "Bearer " + getUserToken(),
        "Signature-Input": sig_input,
        "Signature": f'sig1=:{signature}:',
        "x-ebay-signature-key": public_key_jwe,
        "x-ebay-enforce-signature": "true"
    }
else:
    headers = {
        "Authorization" : "Bearer " + token,
        "Accept" : "application/json",
        "Content-Type" : "application/json"
    }
try:
    response = requests.get(request_url, headers=headers)
    print(response.text)
except Exception as e:
    print(e)

With dig_sig = True, the response is:

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

If I set dig_sig = False, then the response is OK:

{"totalFunds":{"value":"1.00","currency":"GBP"},"processingFunds":{"value":"0.00","currency":"GBP"},"availableFunds":{"value":"1.00","currency":"GBP"}}

I tried using the same signature with both VBA and Python code and expected the same OK response. However, the Python code does not accept the signature. The VBA and Python code are both run on the same PC. Software versions:

Microsoft Excel for Microsoft 365 MSO (Versions 2210 Build 16.0.15726.20188) 64-bit

Microsoft Visual Basic for Applications 7.1

Python 3.7.6

【问题讨论】:

  • Could you share you're getSignature function? trying to get this working in PHP and having a nightmare
  • The code is too long to add in a comment, so I'll add another answer below.

标签: python-3.x digital-signature ebay-api vba7


【解决方案1】:

Oh my word! I spotted my mistake!

"x-ebay-signature-key" "@method" "@path "@authority"

was missing a double quote after "@path Replaced with:

"x-ebay-signature-key" "@method" "@path" "@authority"

and it is now working. Anyhow, I hope this topic helps others trying to get eBay Digital Signatures for APIs working.

【讨论】:

    【解决方案2】:

    Xrender, below is a slightly modified getSignature() function which accepts the private key and public key as input parameters. I'm not sure if it will help you or not as it uses a Python library.

    import base64
    from Crypto.PublicKey import ECC
    from Crypto.Signature import eddsa
    
    def getSignature(private_key, public_key_jwe, method, path, authority, created_time):
        try:
            build_key = f"-----BEGIN PRIVATE KEY-----
    {private_key}
    -----END PRIVATE KEY-----"
            key = ECC.import_key(build_key)
            signer = eddsa.new(key, mode='rfc8032')
            sigbase = (f'"x-ebay-signature-key": {public_key_jwe}
    ' +
                f'"@method": {method}
    ' +
                f'"@path": {path}
    ' +
                f'"@authority": {authority}
    ' +
                f'"@signature-params": ("x-ebay-signature-key" "@method" "@path" "@authority");created={created_time}'
                )
            signature = signer.sign(sigbase.encode())
            return base64.b64encode(signature).decode()
        except Exception as e:
            print(e)
        return False
    

    To get it to work I had to install pycryptodome first:

    pip install pycryptodome
    

    The following Python code also produces the same resulting signature if you call the sign(secret, msg) function as sign(private_key, sigbase)

    Source: https://www.rfc-editor.org/rfc/rfc8032#section-6

    import hashlib
    
    def sha512(s):
        return hashlib.sha512(s).digest()
    
    # Base field Z_p
    p = 2**255 - 19
    
    def modp_inv(x):
        return pow(x, p-2, p)
    
    # Curve constant
    d = -121665 * modp_inv(121666) % p
    
    # Group order
    q = 2**252 + 27742317777372353535851937790883648493
    
    def sha512_modq(s):
        return int.from_bytes(sha512(s), "little") % q
    
    ## Then follows functions to perform point operations.
    
    # Points are represented as tuples (X, Y, Z, T) of extended
    # coordinates, with x = X/Z, y = Y/Z, x*y = T/Z
    
    def point_add(P, Q):
        A, B = (P[1]-P[0]) * (Q[1]-Q[0]) % p, (P[1]+P[0]) * (Q[1]+Q[0]) % p;
        C, D = 2 * P[3] * Q[3] * d % p, 2 * P[2] * Q[2] % p;
        E, F, G, H = B-A, D-C, D+C, B+A;
        return (E*F, G*H, F*G, E*H);
    
    
    # Computes Q = s * Q
    def point_mul(s, P):
        Q = (0, 1, 1, 0)  # Neutral element
        while s > 0:
            if s & 1:
                Q = point_add(Q, P)
            P = point_add(P, P)
            s >>= 1
        return Q
    
    def point_equal(P, Q):
        # x1 / z1 == x2 / z2  <==>  x1 * z2 == x2 * z1
        if (P[0] * Q[2] - Q[0] * P[2]) % p != 0:
            return False
        if (P[1] * Q[2] - Q[1] * P[2]) % p != 0:
            return False
        return True
    
    ## Now follows functions for point compression.
    
    # Square root of -1
    modp_sqrt_m1 = pow(2, (p-1) // 4, p)
    
    # Compute corresponding x-coordinate, with low bit corresponding to
    # sign, or return None on failure
    def recover_x(y, sign):
        if y >= p:
            return None
        x2 = (y*y-1) * modp_inv(d*y*y+1)
        if x2 == 0:
            if sign:
                return None
            else:
                return 0
    
        # Compute square root of x2
        x = pow(x2, (p+3) // 8, p)
        if (x*x - x2) % p != 0:
            x = x * modp_sqrt_m1 % p
        if (x*x - x2) % p != 0:
            return None
    
        if (x & 1) != sign:
            x = p - x
        return x
    
    
    # Base point
    g_y = 4 * modp_inv(5) % p
    g_x = recover_x(g_y, 0)
    G = (g_x, g_y, 1, g_x * g_y % p)
    
    def point_compress(P):
        zinv = modp_inv(P[2])
        x = P[0] * zinv % p
        y = P[1] * zinv % p
        return int.to_bytes(y | ((x & 1) << 255), 32, "little")
    
    def point_decompress(s):
        if len(s) != 32:
            raise Exception("Invalid input length for decompression")
        y = int.from_bytes(s, "little")
        sign = y >> 255
        y &= (1 << 255) - 1
    
        x = recover_x(y, sign)
        if x is None:
            return None
        else:
            return (x, y, 1, x*y % p)
    
    ## These are functions for manipulating the private key.
    
    def secret_expand(secret):
        if len(secret) != 32:
            raise Exception("Bad size of private key")
        h = sha512(secret)
        a = int.from_bytes(h[:32], "little")
        a &= (1 << 254) - 8
        a |= (1 << 254)
        return (a, h[32:])
    
    def secret_to_public(secret):
        (a, dummy) = secret_expand(secret)
        return point_compress(point_mul(a, G))
    
    
    ## The signature function works as below.
    
    def sign(secret, msg):
        a, prefix = secret_expand(secret)
        A = point_compress(point_mul(a, G))
        r = sha512_modq(prefix + msg)
        R = point_mul(r, G)
        Rs = point_compress(R)
        h = sha512_modq(Rs + A + msg)
        s = (r + h * a) % q
        return Rs + int.to_bytes(s, 32, "little")
    
    ## And finally the verification function.
    
    def verify(public, msg, signature):
        if len(public) != 32:
            raise Exception("Bad public key length")
        if len(signature) != 64:
            Exception("Bad signature length")
        A = point_decompress(public)
        if not A:
            return False
        Rs = signature[:32]
        R = point_decompress(Rs)
        if not R:
            return False
        s = int.from_bytes(signature[32:], "little")
        if s >= q: return False
        h = sha512_modq(Rs + public + msg)
        sB = point_mul(s, G)
        hA = point_mul(h, A)
        return point_equal(sB, point_add(R, hA))
    

    However, as I could not figure out how to obtain the 32 bytes private key from the private key in the PEM file using Python alone, I just opted to use the Crypto library as it was much simpler and shorter code.

    【讨论】:

      猜你喜欢
      • 2022-11-10
      • 2022-12-27
      • 2017-10-27
      • 1970-01-01
      • 1970-01-01
      • 2019-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多