Artifact [049da5ae2a]

Artifact 049da5ae2ad15e256897b06697145ac0a74d8868:


#INCLUDE "..\iguanacms.bi"

'ACME Client Implementation

DIM SHARED AS JSON_Member Properties(), Resources()
DIM SHARED AS STRING Nonce, Response, CertRequest
'Declare global objects
DIM SHARED _
  Account AS ACME_Account, _
  Order AS ACME_Order, _
  Authorization AS ACME_Authorization, _
  Identifier AS ACME_Identifier, _
  Challenge AS ACME_Challenge

FUNCTION ACME_Init(FileContent AS STRING) AS LONG
  DIM objProperties AS JSON_Object
  objProperties.RawContent = FileContent
  JSON_CountMembers(objProperties)
  IF objProperties.MemberCount THEN
    REDIM Properties(objProperties.MemberCount - 1)
    JSON_ParseObject(objProperties, Properties())
    ACME_Init = -1
  END IF
END FUNCTION

SUB ACME_Terminate()
  IF UBOUND(Properties) >= 0 THEN
    ERASE Properties
  END IF
END SUB

SUB ACME_SendRequest(Method AS STRING, Url AS STRING, Content AS STRING = "")
  IF Url <> "" THEN
    HTTP_Open(Method, Url)
    HTTP_SetTimeOuts()
    IF Content <> "" THEN
      HTTP_SetRequestHeader("Content-Type", "application/jose+json")
    END IF
    HTTP_Send(Content)
  END IF
END SUB

FUNCTION ACME_DumpResponse() AS STRING
  ACME_DumpResponse = Response
END FUNCTION

SUB ACME_ParseResponse(Response() AS JSON_Member, ResponseContent AS STRING)
  DIM objResponse AS JSON_Object
  IF ResponseContent <> "" THEN
    objResponse.RawContent = ResponseContent
    JSON_CountMembers(objResponse)
  END IF
  IF objResponse.MemberCount THEN
    REDIM Response(objResponse.MemberCount - 1)
    JSON_ParseObject(objResponse, Response())
  END IF
END SUB

FUNCTION ACME_GetProperty(PropertyName AS STRING) AS STRING
  DIM Result AS STRING
  Result = JSON_GetMemberValue(Properties(), PropertyName)
  IF Result <> "" AND Result <> PropertyName THEN
    ACME_GetProperty = Result
  END IF
END FUNCTION

SUB ACME_UpdateProperty(PropertyName AS STRING, PropertyValue AS STRING)
  JSON_SetMemberValue(Properties(), PropertyName, PropertyValue)
END SUB

FUNCTION ACME_DumpProperties() AS STRING
  ACME_DumpProperties = JSON_GenerateObject(Properties())
END FUNCTION

'NOTE: This function expects an initialized private key
FUNCTION ACME_BuildWebKey() AS STRING
  DIM objKey AS JSON_Object, PubKey AS RSA_PublicKey
  PubKey = Crypto_ExportRawPublicKey()
  JSON_FillObject(objKey, JWK_KEY_TYPE, JSON_GenerateString(KEY_TYPE))
  JSON_FillObject(objKey, JWK_KEY_LEN, JSON_GenerateString(Base64Encode(PubKey.KeyLen, -1)))
  JSON_FillObject(objKey, JWK_KEY_PUB_EXP, JSON_GenerateString(Base64Encode(PubKey.PubExp, -1)))
  JSON_FillObject(objKey, JWK_KEY_ALG, JSON_GenerateString(KEY_ALG))
  ACME_BuildWebKey = objKey.RawContent
END FUNCTION

'NOTE: This function expects an initialized private key
FUNCTION ACME_BuildKeyThumbprint() AS STRING
  DIM objKey AS JSON_Object, PubKey AS RSA_PublicKey, Result AS STRING
  PubKey = Crypto_ExportRawPublicKey()
  JSON_FillObject(objKey, JWK_KEY_LEN, JSON_GenerateString(Base64Encode(PubKey.KeyLen, -1)))
  JSON_FillObject(objKey, JWK_KEY_TYPE, JSON_GenerateString(KEY_TYPE))
  JSON_FillObject(objKey, JWK_KEY_PUB_EXP, JSON_GenerateString(Base64Encode(PubKey.PubExp, -1)))
  Result = objKey.RawContent
  Result = Replace(Result, CHR(JSON_TOKEN_SPACE), "")
  Result = Replace(Result, CHR(JSON_TOKEN_LF), "")
  Result = SHA256(Result, -1)
  ACME_BuildKeyThumbprint = Base64Encode(Result, -1)
END FUNCTION

'NOTE: This function expects an initialized private key
FUNCTION ACME_BuildCertRequest(RdnSubject AS STRING) AS STRING
  DIM Result AS STRING
  Crypto_InitializeCertRequest(RdnSubject)
  Result = Crypto_GetCertRequest()
  Crypto_TerminateCertRequest()
  ACME_BuildCertRequest = Base64Encode(Result, -1)
END FUNCTION

FUNCTION ACME_BuildProtectedHeader(Url AS STRING, Kid AS STRING = "") AS STRING
  DIM objHeader AS JSON_Object
  JSON_FillObject(objHeader, JWS_URL, Replace(JSON_GenerateString(Url), CHR(JSON_TOKEN_ESCAPE), ""))
  JSON_FillObject(objHeader, JWS_NONCE, JSON_GenerateString(Nonce))
  'Are we receiving a key id?
  IF Kid <> "" THEN
    JSON_FillObject(objHeader, JWS_KID, JSON_GenerateString(Kid))
  ELSE
    'Add the JWK object
    JSON_FillObject(objHeader, JWS_JWK, ACME_BuildWebKey())
  END IF
  JSON_FillObject(objHeader, JWK_KEY_ALG, JSON_GenerateString(KEY_ALG))
  ACME_BuildProtectedHeader = Latin1ToUtf8(objHeader.RawContent)
END FUNCTION

FUNCTION ACME_BuildExternalBinding(Url AS STRING) AS STRING
  DIM AS JSON_Object objHeader, objPayload, objEAB
  DIM AS STRING Kid, HMacKey, Header, Payload, Signature
  Kid = ACME_GetProperty("eab_kid")
  HMacKey = ACME_GetProperty("eab_hmac")
  IF Kid <> "" AND HMacKey <> "" THEN
    JSON_FillObject(objHeader, JWS_URL, Replace(JSON_GenerateString(Url), CHR(JSON_TOKEN_ESCAPE), ""))
    JSON_FillObject(objHeader, JWS_KID, JSON_GenerateString(Kid))
    JSON_FillObject(objHeader, JWK_KEY_ALG, JSON_GenerateString(KEY_HMAC_ALG))
    Header = Base64Encode(objHeader.RawContent, -1)
    Payload = Base64Encode(ACME_BuildWebKey(), -1)
    Signature = Base64Encode(HMAC("HMAC-SHA256", HMacKey, Header + "." + Payload), -1)
    JSON_FillObject(objEAB, "signature", JSON_GenerateString(Signature))
    JSON_FillObject(objEAB, "payload", JSON_GenerateString(Payload))
    JSON_FillObject(objEAB, "protected", JSON_GenerateString(Header))
    ACME_BuildExternalBinding = objEAB.RawContent
  END IF
END FUNCTION

FUNCTION ACME_BuildIdentifier(Identifier AS ACME_Identifier) AS STRING
  DIM objIdentifier AS JSON_Object
  JSON_FillObject(objIdentifier, "type", JSON_GenerateString(Identifier.Type))
  JSON_FillObject(objIdentifier, "value", JSON_GenerateString(Identifier.Value))
  ACME_BuildIdentifier = objIdentifier.RawContent
END FUNCTION

FUNCTION ACME_BuildPayload(Resource AS ACME_Resource) AS STRING
  DIM objPayload AS JSON_Object
  SELECT CASE Resource
    CASE ACME_NEW_ACCOUNT
      DIM ArrayContacts AS JSON_Array, ContactsCount AS LONG, ExternalBinding AS STRING
      IF ACME_RequiresExternalBinding() THEN
        'Add external account binding
        ExternalBinding = ACME_BuildExternalBinding(ACME_GetResourceUrl("newAccount"))
        IF ExternalBinding <> "" THEN
          JSON_FillObject(objPayload, "externalAccountBinding", ExternalBinding)
        END IF
      END IF
      'Agree terms of service
      JSON_FillObject(objPayload, "termsOfServiceAgreed", "true")
      'Add email address
      ContactsCount = UBOUND(Account.Contacts)
      IF ContactsCount >= 0 THEN
        FOR i AS LONG = 0 TO ContactsCount
          JSON_FillArray(ArrayContacts, JSON_GenerateString(Account.Contacts(i)))
        NEXT 
      END IF
      JSON_FillObject(objPayload, "contact", ArrayContacts.RawContent)
    CASE ACME_NEW_ORDER
      DIM ArrayIdentifiers AS JSON_Array, IdentifiersCount AS LONG
      'Get identifiers count from the global order object
      IdentifiersCount = UBOUND(Order.Identifiers)
      IF IdentifiersCount >= 0 THEN
        FOR i AS LONG = 0 TO IdentifiersCount
          JSON_FillArray(ArrayIdentifiers, ACME_BuildIdentifier(Order.Identifiers(i)))
        NEXT
      END IF
      JSON_FillObject(objPayload, "identifiers", ArrayIdentifiers.RawContent)
    CASE ACME_ORDER_FINALIZE
      JSON_FillObject(objPayload, "csr", JSON_GenerateString(CertRequest))
  END SELECT
  ACME_BuildPayload = objPayload.RawContent
END FUNCTION

'NOTE: This function expects an initialized private key
FUNCTION ACME_BuildRequest(Resource AS ACME_Resource) AS STRING
  DIM oJWS AS JSON_Object
  DIM AS STRING Header, Payload, Signature
  IF Account.Url = "" THEN
    Account.Url = ACME_GetProperty("account_url")
  END IF
  SELECT CASE Resource
    CASE ACME_NEW_ACCOUNT
      Header = ACME_BuildProtectedHeader(ACME_GetResourceUrl("newAccount"))
      Payload = ACME_BuildPayload(ACME_NEW_ACCOUNT)
    CASE ACME_EXISTING_ACCOUNT
      Header = ACME_BuildProtectedHeader(Account.Url, Account.Url)
    CASE ACME_NEW_ORDER
      Header = ACME_BuildProtectedHeader(ACME_GetResourceUrl("newOrder"), Account.Url)
      Payload = ACME_BuildPayload(ACME_NEW_ORDER)
    CASE ACME_EXISTING_ORDER
      IF Order.Url <> "" THEN
        Header = ACME_BuildProtectedHeader(Order.Url, Account.Url)
      END IF
    CASE ACME_EXISTING_AUTHORIZATION
      IF Authorization.Url <> "" THEN
        Header = ACME_BuildProtectedHeader(Authorization.Url, Account.Url)
      END IF
    CASE ACME_EXISTING_CHALLENGE
      IF Challenge.Url <> "" THEN
        Header = ACME_BuildProtectedHeader(Challenge.Url, Account.Url)
        Payload = CHR(JSON_TOKEN_OBJECT_OPEN) + CHR(JSON_TOKEN_OBJECT_CLOSE)
      END IF
    CASE ACME_ORDER_FINALIZE
      IF Order.Finalize <> "" THEN
        Header = ACME_BuildProtectedHeader(Order.Finalize, Account.Url)
        Payload = ACME_BuildPayload(ACME_ORDER_FINALIZE)
      END IF
    CASE ACME_CERT
      IF Order.Certificate <> "" THEN
        Header = ACME_BuildProtectedHeader(Order.Certificate, Account.Url)
      END IF
  END SELECT
  IF Header <> "" THEN
    Header = Base64Encode(Header, -1)
    'If payload comes empty, then it is a POST-as-GET request
    IF Payload <> "" THEN
      Payload = Base64Encode(Payload, -1)
    END IF
    'Build the message signature
    Signature = Base64Encode(Crypto_SignMessage(Header + "." + Payload), -1)
    'Build the JWS object
    JSON_FillObject(oJWS, "signature", JSON_GenerateString(Signature))
    JSON_FillObject(oJWS, "payload", JSON_GenerateString(Payload))
    JSON_FillObject(oJWS, "protected", JSON_GenerateString(Header))
  END IF
  ACME_BuildRequest = oJWS.RawContent
END FUNCTION

SUB ACME_GetResources()
  DIM AS STRING DirectoryUrl = ACME_GetProperty("directory_url")
  IF DirectoryUrl <> "" THEN
    ACME_SendRequest("GET", DirectoryUrl)
    Response = HTTP_GetResponse()
    IF Response <> "" THEN
      ACME_ParseResponse(Resources(), Response)
    END IF
  END IF
END SUB

FUNCTION ACME_GetResourceUrl(ResourceName AS STRING) AS STRING
  DIM Result AS STRING = JSON_GetMemberValue(Resources(), ResourceName)
  IF Result <> "" THEN
    ACME_GetResourceUrl = Result
  END IF
END FUNCTION

SUB ACME_UpdateNonce()
  Nonce = HTTP_GetResponseHeader("Replay-Nonce")
END SUB

SUB ACME_GenerateNonce()
  DIM NewNonceUrl AS STRING = ACME_GetResourceUrl("newNonce")
  IF NewNonceUrl <> "" THEN
    ACME_SendRequest("HEAD", NewNonceUrl)
    ACME_UpdateNonce()
  END IF
END SUB

FUNCTION ACME_RequiresExternalBinding() AS LONG
  DIM MetaData() AS JSON_Member
  ACME_ParseResponse(MetaData(), ACME_GetResourceUrl("meta"))
  IF UBOUND(MetaData) >= 0 THEN
    IF JSON_GetMemberValue(MetaData(), "externalAccountRequired") = "true" THEN
      ACME_RequiresExternalBinding = -1
    END IF
  END IF
END FUNCTION

'Creates a new account, and on success returns the account url
FUNCTION ACME_CreateAccount(Contacts() AS STRING) AS STRING
  DIM AS STRING NewAccountUrl, RequestContent
  DIM ContactsCount AS LONG = UBOUND(Contacts)
  IF ContactsCount >= 0 THEN
    REDIM Account.Contacts(ContactsCount)
    FOR i AS LONG = 0 TO ContactsCount
      Account.Contacts(i) = Contacts(i)
    NEXT
  END IF
  NewAccountUrl = ACME_GetResourceUrl("newAccount")
  RequestContent = ACME_BuildRequest(ACME_NEW_ACCOUNT)
  IF NewAccountUrl <> "" AND RequestContent <> "" THEN
    ACME_SendRequest("POST", NewAccountUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode = "200" THEN
    ACME_CreateAccount = HTTP_GetResponseHeader("Location")
  END IF
END FUNCTION

FUNCTION ACME_GetAccountInfo(AccountUrl AS STRING) AS ACME_Account
  DIM CurrentAccount AS ACME_Account, AccountData() AS JSON_Member
  DIM AS STRING RequestContent
  IF AccountUrl <> "" THEN
    Account.Url = AccountUrl
  END IF
  RequestContent = ACME_BuildRequest(ACME_EXISTING_ACCOUNT)
  IF RequestContent <> "" THEN
    ACME_SendRequest("POST", AccountUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode() = "200" THEN
    ACME_ParseResponse(AccountData(), Response)
  END IF
  IF UBOUND(AccountData) >= 0 THEN
    WITH CurrentAccount
      .Status = JSON_GetMemberValue(AccountData(), "status")
    END WITH
    ACME_GetAccountInfo = CurrentAccount
  END IF
END FUNCTION

'Creates a new order, and on success returns the order url
FUNCTION ACME_InitializeOrder(Identifiers() AS STRING) AS STRING
  DIM AS STRING NewOrderUrl, RequestContent
  DIM IdentifiersCount AS LONG = UBOUND(Identifiers)
  IF IdentifiersCount >= 0 THEN
    REDIM Order.Identifiers(IdentifiersCount)
    FOR i AS LONG = 0 TO IdentifiersCount
      WITH Order.Identifiers(i)
        .Type = "dns"
        .Value = Identifiers(i)
      END WITH
    NEXT
  END IF
  NewOrderUrl = ACME_GetResourceUrl("newOrder")
  RequestContent = ACME_BuildRequest(ACME_NEW_ORDER)
  IF NewOrderUrl <> "" AND RequestContent <> "" THEN
    ACME_SendRequest("POST", NewOrderUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode = "201" THEN
    ACME_InitializeOrder = HTTP_GetResponseHeader("Location")
  END IF
END FUNCTION

FUNCTION ACME_GetIdentifierInfo(IdentifierContent AS STRING) AS ACME_Identifier
  DIM CurrentIdentifier AS ACME_Identifier, IdentifierData() AS JSON_Member
  ACME_ParseResponse(IdentifierData(), IdentifierContent)
  IF UBOUND(IdentifierData) >= 0 THEN
    WITH CurrentIdentifier
      .Type = JSON_GetMemberValue(IdentifierData(), "type")
      .Value = JSON_GetMemberValue(IdentifierData(), "value")
    END WITH
    ACME_GetIdentifierInfo = CurrentIdentifier
  END IF
END FUNCTION

FUNCTION ACME_GetOrderInfo(OrderUrl AS STRING) AS ACME_Order
  DIM CurrentOrder AS ACME_Order, OrderData() AS JSON_Member
  DIM AS STRING RequestContent
  IF OrderUrl <> "" THEN
    Order.Url = OrderUrl
  END IF
  RequestContent = ACME_BuildRequest(ACME_EXISTING_ORDER)
  IF RequestContent <> "" THEN
    ACME_SendRequest("POST", OrderUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode() = "200" THEN
    ACME_ParseResponse(OrderData(), Response)
  END IF
  IF UBOUND(OrderData) >= 0 THEN
    'Parse the identifiers
    DIM ArrayIdentifiers AS JSON_Array, Identifiers() AS JSON_Value, IdentifiersCount AS LONG
    ArrayIdentifiers.RawContent = JSON_GetMemberValue(OrderData(), "identifiers")
    JSON_CountValues(ArrayIdentifiers)
    IF ArrayIdentifiers.ValueCount THEN
      REDIM Identifiers(ArrayIdentifiers.ValueCount - 1)
      JSON_ParseArray(ArrayIdentifiers, Identifiers())
    END IF
    'Parse the authorizations
    DIM ArrayAuths AS JSON_Array, Auths() AS JSON_Value, AuthsCount AS LONG
    ArrayAuths.RawContent = JSON_GetMemberValue(OrderData(), "authorizations")
    JSON_CountValues(ArrayAuths)
    IF ArrayAuths.ValueCount THEN
      REDIM Auths(ArrayAuths.ValueCount - 1)
      JSON_ParseArray(ArrayAuths, Auths())
    END IF
    WITH CurrentOrder
      .Status = JSON_GetMemberValue(OrderData(), "status")
      .Expires = JSON_GetMemberValue(OrderData(), "expires")
      .Finalize = JSON_GetMemberValue(OrderData(), "finalize")
      .Certificate = JSON_GetMemberValue(OrderData(), "certificate")
      IdentifiersCount = UBOUND(Identifiers)
      IF IdentifiersCount >= 0 THEN
        REDIM .Identifiers(IdentifiersCount)
        FOR i AS LONG = 0 TO IdentifiersCount
          .Identifiers(i) = ACME_GetIdentifierInfo(Identifiers(i).RawContent)
        NEXT
      END IF
      AuthsCount = UBOUND(Auths)
      IF AuthsCount >= 0 THEN
        REDIM .Authorizations(AuthsCount)
        FOR i AS LONG = 0 TO AuthsCount
          .Authorizations(i) = JSON_ParseString(Auths(i).RawContent)
        NEXT
      END IF
    END WITH
    ACME_GetOrderInfo = CurrentOrder
  END IF
END FUNCTION

FUNCTION ACME_GetChallengeInfo(ChallengeContent AS STRING) AS ACME_Challenge
  DIM CurrentChallenge AS ACME_Challenge, ChallengeData() AS JSON_Member
  ACME_ParseResponse(ChallengeData(), ChallengeContent)
  IF UBOUND(ChallengeData) >= 0 THEN
    WITH CurrentChallenge
      .Type = JSON_GetMemberValue(ChallengeData(), "type")
      .Status = JSON_GetMemberValue(ChallengeData(), "status")
      .Url = JSON_GetMemberValue(ChallengeData(), "url")
      .Token = JSON_GetMemberValue(ChallengeData(), "token")
    END WITH
    ACME_GetChallengeInfo = CurrentChallenge
  END IF
END FUNCTION

FUNCTION ACME_GetAuthorizationInfo(AuthUrl AS STRING) AS ACME_Authorization
  DIM CurrentAuth AS ACME_Authorization, AuthData() AS JSON_Member
  DIM AS STRING RequestContent
  IF AuthUrl <> "" THEN
    Authorization.Url = AuthUrl
    RequestContent = ACME_BuildRequest(ACME_EXISTING_AUTHORIZATION)
  END IF
  IF RequestContent <> "" THEN
    ACME_SendRequest("POST", AuthUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode = "200" THEN
    ACME_ParseResponse(AuthData(), Response)
  END IF
  IF UBOUND(AuthData) >= 0 THEN
    DIM ArrayChallenges AS JSON_Array, Challenges() AS JSON_Value, ChallengesCount AS LONG
    ArrayChallenges.RawContent = JSON_GetMemberValue(AuthData(), "challenges")
    JSON_CountValues(ArrayChallenges)
    IF ArrayChallenges.ValueCount THEN
      REDIM Challenges(ArrayChallenges.ValueCount - 1)
      JSON_ParseArray(ArrayChallenges, Challenges())
    END IF
    WITH CurrentAuth
      .Status = JSON_GetMemberValue(AuthData(), "status")
      .Expires = JSON_GetMemberValue(AuthData(), "expires")
      .Identifier = ACME_GetIdentifierInfo(JSON_GetMemberValue(AuthData(), "identifier"))
      ChallengesCount = UBOUND(Challenges)
      IF ChallengesCount >= 0 THEN
        REDIM .Challenges(ChallengesCount)
        FOR i AS LONG = 0 TO ChallengesCount
          .Challenges(i) = ACME_GetChallengeInfo(Challenges(i).RawContent)
        NEXT
      END IF
    END WITH
    ACME_GetAuthorizationInfo = CurrentAuth
  END IF
END FUNCTION

FUNCTION ACME_GetKeyAuthorization(Token AS STRING) AS STRING
  ACME_GetKeyAuthorization = Token + "." + ACME_BuildKeyThumbprint()
END FUNCTION

FUNCTION ACME_ValidateChallenge(ChallengeUrl AS STRING) AS LONG
  DIM AS STRING RequestContent
  IF ChallengeUrl <> "" THEN
    Challenge.Url = ChallengeUrl
    RequestContent = ACME_BuildRequest(ACME_EXISTING_CHALLENGE)
  END IF
  IF RequestContent <> "" THEN
    ACME_SendRequest("POST", ChallengeUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode = "200" THEN
    ACME_ValidateChallenge = -1
  END IF
END FUNCTION

FUNCTION ACME_FinalizeOrder(FinalizeUrl AS STRING, CsrContent AS STRING) AS LONG
  DIM OrderData() AS JSON_Member
  DIM AS STRING RequestContent
  IF FinalizeUrl <> "" THEN
    CertRequest = CsrContent
    Order.Finalize = FinalizeUrl
    RequestContent = ACME_BuildRequest(ACME_ORDER_FINALIZE)
  END IF
  IF RequestContent <> "" THEN
    ACME_SendRequest("POST", FinalizeUrl, RequestContent)
    ACME_UpdateNonce()
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode = "200" THEN
    ACME_FinalizeOrder = -1
  END IF
END FUNCTION

FUNCTION ACME_DownloadCertificate(CertUrl AS STRING) AS STRING
  DIM AS STRING RequestContent
  Order.Certificate = CertUrl
  RequestContent = ACME_BuildRequest(ACME_CERT)
  IF RequestContent <> "" THEN
    ACME_SendRequest("POST", CertUrl, RequestContent)
    Response = HTTP_GetResponse()
  END IF
  IF HTTP_GetResponseCode = "200" THEN
    ACME_DownloadCertificate = Response
  END IF
END FUNCTION