This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Upload Certificate using API

Hi folks,
 
I've started having a play around with XG. I have a PowerShell script for generating a new Let's Encrypt certificate and updating my various components that use it, and wanted to integrate this with XG Home. It looks like the obvious way of achieving this should be the API, but I'm struggling a little with the certificate upload.

I've taken a look at the documentation and fired off a few API calls successfully, but the upload operation is a little different as it references data in a multipart request.
I'm using the v16.5 documentation because the v17 docs appear to be missing this particular page (it appears in the menu but the link is broken because the AddCertificate&UpdateCertificate.html file is missing).
I am not a developer but I did a bit of Googling and have come up with what I think is a multipart request. A Fiddler trace shows this when I fire off my request (PFX file data redacted and passwords changed).
 
POST sophos:4444/.../APIController
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-GB) WindowsPowerShell/5.1.16299.251
Content-Type: multipart/mixed; boundary=Certificate_File_Upload
Host: sophos:4444
Content-Length: 2712
Connection: Keep-Alive
 
--Certificate_File_Upload
Content-Disposition: attachment; filename="test.pfx"
Content-Type: application/x-pkcs12
 
<<<<Encoded PFX data here>>>>
--Certificate_File_Upload--

Tidying that up for readability the decoded XML in the URL is:
<Request>
  <Login>
    <Username>admin</Username>
    <Password>MyPassword</Password>
  </Login>
  <Set operation="add">
    <Certificate>
      <Action>UploadCertificate</Action>
      <Name>Test</Name>
      <Password>PfxPassword</Password>
      <CertificateFormat>pkcs12</CertificateFormat>
      <CertificateFile>test.pfx</CertificateFile>
      <PrivateKeyFile></PrivateKeyFile>
    </Certificate>
  </Set>
</Request>
 
The response I get from the API is: <Status code="510">Operation failed. Deleting entity referred by another entity.</Status>
Going by the documentation this means "Certificate could not be uploaded due to invalid private key or passphrase. Choose a proper key".
 
I've also tried a version with the certificate and key in PEM format rather than PFX and get: <Status code="500">Operation could not be performed on Entity.</Status>
Going by the documentation this means "Certificate could not be updated".
 
I tried a multipart/form-data request initially, but the API didn't provide any feedback - I got an HTTP 200 response, but no XML in the body. The multipart/mixed version at least responds with some XML so I'm assuming that's what it wants.
 
I'm assuming there's something wrong with the way I'm uploading the PFX file - perhaps I've misunderstood what the multipart request should look like? I couldn't find an example in the docs. The encoded PFX file data looks correct though, because it appears to be the same in Fiddler when I upload the PFX via the web UI (successfully). I was hoping one of you might have tried this and could point me in the right direction, please?
 
Thanks,
Andrew


This thread was automatically locked due to age.
Parents
  • Hi Andrew,

    Looks like you are close to a working solution. I too - just as you have tried - create a multipart/form-data object (System.Net.Http.MultipartFormDataContent).

     

    My request looks something like the following (highlighted in yellow where I believe you need to adjust):

    --6332ebe1-9b29-49f6-a4f2-1728b60e4131
    Content-Type: application/xml; charset=utf-8
    Content-Disposition: form-data; name=reqxml
    <Request>
        <Login>
            <Username>*** USER ***</Username>
            <Password passwordform="encrypt">*** HASHED PASSWORD - remove encrypt to use non-hashed ***</Password>
        </Login>
       
        <Set operation="add">
            <Certificate>
                <Name>*** CERT NAME ***</Name>
                <Action>UploadCertificate</Action>
                <CertificateFormat>pkcs12</CertificateFormat>
                <Password>*** PASSWORD ***</Password>
                <CertificateFile>*** CERT NAME ***.pfx</CertificateFile>
            </Certificate>
        </Set>
    </Request>
    --6332ebe1-9b29-49f6-a4f2-1728b60e4131
    Content-Disposition: form-data; filename=*** CERT NAME ***.pfx; name=*** CERT NAME ***
    Content-Type: application/octet-stream
    *** BINARY DATA ***
    --6332ebe1-9b29-49f6-a4f2-1728b60e4131--

     

    With this approach I get the following response:

    <?xml version="1.0" encoding="UTF-8"?>
    <Response APIVersion="1700.1">
      <Login>
        <status>Authentication Successful</status>
      </Login>
      <Certificate transactionid="">
        <Status code="200">Configuration applied successfully.</Status>
      </Certificate>
    </Response>

     

    Also remember that you cannot upload a certificate with a name that already exists. You need to create a new unique name each time (then update all references to use the new and then delete the old).

    A final note here is that it seems that firewall rules that have Path-specific routing looses some of its configuration on update (in my case the Allowed Client Networks). Haven't fully investigated what I'm doing wrong here, but to me it seems to be a bug in the API as I on update return the same configuration the API gives me...

     

    Hope this helps!

    Trond

  • Hi Trond and Andrew,

    Firstly, thanks for the detailed question, it has helped me get closer to uploading the cert. I think.

    This is the PowerShell script that I have got so far (based a lot off this page: http://blog.majcica.com/2016/01/13/powershell-tips-and-tricks-multipartform-data-requests/)


    $ContentType = "application/octet-stream"
    $certFile = "C:\temp\LE_cert.pfx"
    $fileName = Split-Path $certFile -leaf
    $boundary = [guid]::NewGuid().ToString()
    $fileBin = [System.IO.File]::ReadAllBytes($certFile)

    $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")

    $template = @'
       --{0}
       Content-Type: application/xml; charset=utf-8
       Content-Disposition: form-data; name=reqxml
       <Request>
          <Login>
             <Username>apiUSER</Username>
             <Password>apiPWD</Password>
          </Login>
          <Set operation="add">
             <Certificate>
                <Name>LE_tcxapi_20181105_cert</Name>
                <Action>UploadCertificate</Action>
                <CertificateFormat>pkcs12</CertificateFormat>
                <Password>pwd</Password>
                <CertificateFile>{1}</CertificateFile>
             </Certificate>
          </Set>
       </Request>
       --{0}
       Content-Disposition: form-data; filename="{1}"; name="Unique"
       Content-Type: {2}
       {3}
       --{0}--
    '@

    $body = $template -f $boundary, $fileName, $ContentType, $enc.GetString($fileBin)
     

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri 'fw-exg-00:4444/.../APIController operation="add"><Certificate><Name>MHA_TEST_Cert</Name><Action>UploadCertificate</Action><CertificateFormat>pkcs12</CertificateFormat><Password>pwd</Password><CertificateFile>LE_cert.pfx</CertificateFile></Certificate></Set></Request>' -Method POST -body $body

    This gives me the following request body:

    --db611ddb-ea8a-450f-aad8-90bee1c6a889
    Content-Type: application/xml; charset=utf-8
    Content-Disposition: form-data; name=reqxml
    <Request>
       <Login>
          <Username>apiUSER</Username>
          <Password>apiPWD</Password>
       </Login>
       <Set operation="add">
          <Certificate>
             <Name>LE_cert</Name>
             <Action>UploadCertificate</Action>
             <CertificateFormat>pkcs12</CertificateFormat>
             <Password>password1</Password>
             <CertificateFile>LE_cert.pfx</CertificateFile>
          </Certificate>
       </Set>
    </Request>
    --db611ddb-ea8a-450f-aad8-90bee1c6a889
    Content-Disposition: form-data; filename="LE_cert.pfx"; name="LE_cert"
    Content-Type: application/octet-stream
    <<--redactedEncodedTXT-->>
    --db611ddb-ea8a-450f-aad8-90bee1c6a889--

    I think I have got my request in the same format as Trond's example but I am still getting "<Status code="500">Operation could not be performed on Entity.</Status>"

    I was wondering how you were encoding your certificate, I have tried the above iso-8859-1, but also UTF-8 and Base64 and none of them have got the certificate uploaded.

    I think I have got this right, but I just can't get the certificate uploaded, so any help would be greatly appreciated.

    Cheers,

    Tim

     

Reply
  • Hi Trond and Andrew,

    Firstly, thanks for the detailed question, it has helped me get closer to uploading the cert. I think.

    This is the PowerShell script that I have got so far (based a lot off this page: http://blog.majcica.com/2016/01/13/powershell-tips-and-tricks-multipartform-data-requests/)


    $ContentType = "application/octet-stream"
    $certFile = "C:\temp\LE_cert.pfx"
    $fileName = Split-Path $certFile -leaf
    $boundary = [guid]::NewGuid().ToString()
    $fileBin = [System.IO.File]::ReadAllBytes($certFile)

    $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")

    $template = @'
       --{0}
       Content-Type: application/xml; charset=utf-8
       Content-Disposition: form-data; name=reqxml
       <Request>
          <Login>
             <Username>apiUSER</Username>
             <Password>apiPWD</Password>
          </Login>
          <Set operation="add">
             <Certificate>
                <Name>LE_tcxapi_20181105_cert</Name>
                <Action>UploadCertificate</Action>
                <CertificateFormat>pkcs12</CertificateFormat>
                <Password>pwd</Password>
                <CertificateFile>{1}</CertificateFile>
             </Certificate>
          </Set>
       </Request>
       --{0}
       Content-Disposition: form-data; filename="{1}"; name="Unique"
       Content-Type: {2}
       {3}
       --{0}--
    '@

    $body = $template -f $boundary, $fileName, $ContentType, $enc.GetString($fileBin)
     

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri 'fw-exg-00:4444/.../APIController operation="add"><Certificate><Name>MHA_TEST_Cert</Name><Action>UploadCertificate</Action><CertificateFormat>pkcs12</CertificateFormat><Password>pwd</Password><CertificateFile>LE_cert.pfx</CertificateFile></Certificate></Set></Request>' -Method POST -body $body

    This gives me the following request body:

    --db611ddb-ea8a-450f-aad8-90bee1c6a889
    Content-Type: application/xml; charset=utf-8
    Content-Disposition: form-data; name=reqxml
    <Request>
       <Login>
          <Username>apiUSER</Username>
          <Password>apiPWD</Password>
       </Login>
       <Set operation="add">
          <Certificate>
             <Name>LE_cert</Name>
             <Action>UploadCertificate</Action>
             <CertificateFormat>pkcs12</CertificateFormat>
             <Password>password1</Password>
             <CertificateFile>LE_cert.pfx</CertificateFile>
          </Certificate>
       </Set>
    </Request>
    --db611ddb-ea8a-450f-aad8-90bee1c6a889
    Content-Disposition: form-data; filename="LE_cert.pfx"; name="LE_cert"
    Content-Type: application/octet-stream
    <<--redactedEncodedTXT-->>
    --db611ddb-ea8a-450f-aad8-90bee1c6a889--

    I think I have got my request in the same format as Trond's example but I am still getting "<Status code="500">Operation could not be performed on Entity.</Status>"

    I was wondering how you were encoding your certificate, I have tried the above iso-8859-1, but also UTF-8 and Base64 and none of them have got the certificate uploaded.

    I think I have got this right, but I just can't get the certificate uploaded, so any help would be greatly appreciated.

    Cheers,

    Tim

     

Children
No Data