Sample code for 30+ languages & platforms
PureBasic

ebay: Add Digital Signature to HTTP Request

See more eBay Examples

Demonstrates how to add a digital signature to an ebay HTTP request.

Chilkat PureBasic Downloads

PureBasic
IncludeFile "CkBinData.pb"
IncludeFile "CkDateTime.pb"
IncludeFile "CkPrivateKey.pb"
IncludeFile "CkHttp.pb"
IncludeFile "CkEdDSA.pb"
IncludeFile "CkStringBuilder.pb"
IncludeFile "CkHttpResponse.pb"

Procedure ChilkatExample()

    success.i = 0

    ; This example requires the Chilkat API to have been previously unlocked.
    ; See Global Unlock Sample for sample code.

    ; Note: Ebay provides a Key Management API
    ; See https://developer.ebay.com/api-docs/developer/key-management/overview.html

    ; The following test keys can be used: 
    ; 
    ; Ed25519 
    ; 
    ; Private Key:
    ; 
    ; -----BEGIN PRIVATE KEY-----
    ; MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
    ; -----END PRIVATE KEY-----

    strPrivateKey.s = "MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF"

    ; 
    ; Public Key:
    ; 
    ; -----BEGIN PUBLIC KEY-----
    ; MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
    ; -----END PUBLIC KEY-----

    strPublicKey.s = "MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs="

    ; This example assumes you got a JWE for your given private key from the Ebay Key Management REST API.
    ; This JWE is just for example:
    strJwe.s = "eyJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIiwidGFnIjoiSXh2dVRMb0FLS0hlS0Zoa3BxQ05CUSIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiaFd3YjNoczk2QzEyOTNucCJ9.2o02pR9SoTF4g_5qRXZm6tF4H52TarilIAKxoVUqjd8.3qaF0KJN-rFHHm_P.AMUAe9PPduew09mANIZ-O_68CCuv6EIx096rm9WyLZnYz5N1WFDQ3jP0RBkbaOtQZHImMSPXIHVaB96RWshLuJsUgCKmTAwkPVCZv3zhLxZVxMXtPUuJ-ppVmPIv0NzznWCOU5Kvb9Xux7ZtnlvLXgwOFEix-BaWNomUAazbsrUCbrp514GIea3butbyxXLNi6R9TJUNh8V2uan-optT1MMyS7eMQnVGL5rYBULk.9K5ucUqAu0DqkkhgubsHHw"

    sbBody.i = CkStringBuilder::ckCreate()
    If sbBody.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkStringBuilder::ckAppend(sbBody,"{" + Chr(34) + "hello" + Chr(34) + ": " + Chr(34) + "world" + Chr(34) + "}")

    Debug "Body of request:"
    Debug CkStringBuilder::ckGetAsString(sbBody)

    ; -------------------------------------------------
    ; Build the signature base string...

    sbSigBase.i = CkStringBuilder::ckCreate()
    If sbSigBase.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkStringBuilder::ckAppend(sbSigBase,Chr(34) + "content-digest" + Chr(34) + ": sha-256=:")
    CkStringBuilder::ckAppend(sbSigBase,CkStringBuilder::ckGetHash(sbBody,"sha256","base64","utf-8"))
    CkStringBuilder::ckAppend(sbSigBase,":" + Chr(10))

    CkStringBuilder::ckAppend(sbSigBase,Chr(34) + "x-ebay-signature-key" + Chr(34) + ": ")
    CkStringBuilder::ckAppend(sbSigBase,strJwe)
    CkStringBuilder::ckAppend(sbSigBase,Chr(10))

    CkStringBuilder::ckAppend(sbSigBase,Chr(34) + "@method" + Chr(34) + ": POST" + Chr(10))

    ; This is the path part of the URL without query params...
    CkStringBuilder::ckAppend(sbSigBase,Chr(34) + "@path" + Chr(34) + ": ")
    CkStringBuilder::ckAppend(sbSigBase,"/verifysignature")
    CkStringBuilder::ckAppend(sbSigBase,Chr(10))

    ; The is the domain, such as "api.ebay.com" w/ port if the port is something unusual.
    ; In this example, we're testing against a local docker test server (see the info at https://developer.ebay.com/develop/guides/digital-signatures-for-apis)
    ; Normally, I think it would just be "api.ebay.com" instead of "localhost:8080".
    CkStringBuilder::ckAppend(sbSigBase,Chr(34) + "@authority" + Chr(34) + ": ")
    CkStringBuilder::ckAppend(sbSigBase,"localhost:8080")
    CkStringBuilder::ckAppend(sbSigBase,Chr(10))

    CkStringBuilder::ckAppend(sbSigBase,Chr(34) + "@signature-params" + Chr(34) + ": ")

    sbSigInput.i = CkStringBuilder::ckCreate()
    If sbSigInput.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkStringBuilder::ckAppend(sbSigInput,"(" + Chr(34) + "content-digest" + Chr(34) + " " + Chr(34) + "x-ebay-signature-key" + Chr(34) + " " + Chr(34) + "@method" + Chr(34) + " " + Chr(34) + "@path" + Chr(34) + " " + Chr(34) + "@authority" + Chr(34) + ")")
    CkStringBuilder::ckAppend(sbSigInput,";created=")

    dt.i = CkDateTime::ckCreate()
    If dt.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkDateTime::ckSetFromCurrentSystemTime(dt)
    unixTimeNow.s = CkDateTime::ckGetAsUnixTimeStr(dt,0)
    CkStringBuilder::ckAppend(sbSigInput,unixTimeNow)

    CkStringBuilder::ckAppendSb(sbSigBase,sbSigInput)

    ; -------------------------------------------------
    ; Sign the signature base string using the Ed25519 private key

    bdPrivKey.i = CkBinData::ckCreate()
    If bdPrivKey.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkBinData::ckAppendEncoded(bdPrivKey,strPrivateKey,"base64")

    privKey.i = CkPrivateKey::ckCreate()
    If privKey.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    success = CkPrivateKey::ckLoadAnyFormat(privKey,bdPrivKey,"")
    If success = 0
        Debug CkPrivateKey::ckLastErrorText(privKey)
        CkStringBuilder::ckDispose(sbBody)
        CkStringBuilder::ckDispose(sbSigBase)
        CkStringBuilder::ckDispose(sbSigInput)
        CkDateTime::ckDispose(dt)
        CkBinData::ckDispose(bdPrivKey)
        CkPrivateKey::ckDispose(privKey)
        ProcedureReturn
    EndIf

    bdToBeSigned.i = CkBinData::ckCreate()
    If bdToBeSigned.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkBinData::ckAppendSb(bdToBeSigned,sbSigBase,"utf-8")

    eddsa.i = CkEdDSA::ckCreate()
    If eddsa.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    sigBase64.s = CkEdDSA::ckSignBdENC(eddsa,bdToBeSigned,"base64",privKey)
    If CkEdDSA::ckLastMethodSuccess(eddsa) = 0
        Debug CkEdDSA::ckLastErrorText(eddsa)
        CkStringBuilder::ckDispose(sbBody)
        CkStringBuilder::ckDispose(sbSigBase)
        CkStringBuilder::ckDispose(sbSigInput)
        CkDateTime::ckDispose(dt)
        CkBinData::ckDispose(bdPrivKey)
        CkPrivateKey::ckDispose(privKey)
        CkBinData::ckDispose(bdToBeSigned)
        CkEdDSA::ckDispose(eddsa)
        ProcedureReturn
    EndIf

    Debug "sigBase64:"
    Debug sigBase64

    ; ----------------------------------------------------------
    ; Send the JSON POST

    http.i = CkHttp::ckCreate()
    If http.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkHttp::ckSetRequestHeader(http,"x-ebay-signature-key",strJwe)

    sbContentDigestHdr.i = CkStringBuilder::ckCreate()
    If sbContentDigestHdr.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkStringBuilder::ckAppend(sbContentDigestHdr,"sha-256=:")
    CkStringBuilder::ckAppend(sbContentDigestHdr,CkStringBuilder::ckGetHash(sbBody,"sha256","base64","utf-8"))
    CkStringBuilder::ckAppend(sbContentDigestHdr,":")
    CkHttp::ckSetRequestHeader(http,"Content-Digest",CkStringBuilder::ckGetAsString(sbContentDigestHdr))

    sbSigHdr.i = CkStringBuilder::ckCreate()
    If sbSigHdr.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    CkStringBuilder::ckAppend(sbSigHdr,"sig1=:")
    CkStringBuilder::ckAppend(sbSigHdr,sigBase64)
    CkStringBuilder::ckAppend(sbSigHdr,":")
    CkHttp::ckSetRequestHeader(http,"Signature",CkStringBuilder::ckGetAsString(sbSigHdr))

    CkStringBuilder::ckPrepend(sbSigInput,"sig1=")
    CkHttp::ckSetRequestHeader(http,"Signature-Input",CkStringBuilder::ckGetAsString(sbSigInput))

    ; Add this header to make eBay actually check the signature.
    CkHttp::ckSetRequestHeader(http,"x-ebay-enforce-signature","true")

    ; Set the OAuth2 access token to add the "Authorization: Bearer <access_token>" to the header.
    CkHttp::setCkAuthToken(http, "your_oauth2_access_token")

    ; The signature base string constructed above is valid if we send this POST to "http://localhost:8080/verifysignature"
    ; Normally, you'll send your POST to some api.ebay.com endpoint.
    url.s = "http://localhost:8080/verifysignature"

    jsonStr.s = CkStringBuilder::ckGetAsString(sbBody)
    resp.i = CkHttpResponse::ckCreate()
    If resp.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    success = CkHttp::ckHttpStr(http,"POST","http://localhost:8080/verifysignature",jsonStr,"utf-8","application/json",resp)
    If success = 0
        Debug CkHttp::ckLastErrorText(http)
        CkStringBuilder::ckDispose(sbBody)
        CkStringBuilder::ckDispose(sbSigBase)
        CkStringBuilder::ckDispose(sbSigInput)
        CkDateTime::ckDispose(dt)
        CkBinData::ckDispose(bdPrivKey)
        CkPrivateKey::ckDispose(privKey)
        CkBinData::ckDispose(bdToBeSigned)
        CkEdDSA::ckDispose(eddsa)
        CkHttp::ckDispose(http)
        CkStringBuilder::ckDispose(sbContentDigestHdr)
        CkStringBuilder::ckDispose(sbSigHdr)
        CkHttpResponse::ckDispose(resp)
        ProcedureReturn
    EndIf

    Debug "Response status code: " + Str(CkHttpResponse::ckStatusCode(resp))
    Debug "Response body:"
    Debug CkHttpResponse::ckBodyStr(resp)


    CkStringBuilder::ckDispose(sbBody)
    CkStringBuilder::ckDispose(sbSigBase)
    CkStringBuilder::ckDispose(sbSigInput)
    CkDateTime::ckDispose(dt)
    CkBinData::ckDispose(bdPrivKey)
    CkPrivateKey::ckDispose(privKey)
    CkBinData::ckDispose(bdToBeSigned)
    CkEdDSA::ckDispose(eddsa)
    CkHttp::ckDispose(http)
    CkStringBuilder::ckDispose(sbContentDigestHdr)
    CkStringBuilder::ckDispose(sbSigHdr)
    CkHttpResponse::ckDispose(resp)


    ProcedureReturn
EndProcedure