Send message to cloudAMQP over TLS?

254 views Asked by At

We have been using the stomp client developed by Julian Lyndon-Smith/dot.r for a couple of years. So far, we have been sending unencrypted messages.

We would now like to send messages over TLS. So naturally, I've changed the port from 61613 to 61614 which is the secured one but this give the following error.

None of the widgets used in the WAIT-FOR statement are in a state (such as SENSITIVE) such that the specified event can occur. WAIT-FOR terminated. (4123)

This error occur on line WAIT-FOR "U2":U OF SocketHandle PAUSE 5. in method CONNECT() of class dotr.Stomp.StompConnection.

In the cloudAMQP server log we have the following log when attempting to connect using port 61614.

2022-03-14 14:07:36.915534+00:00 [noti] <0.23840.19> TLS server: In state hello at tls_record.erl:558 generated SERVER ALERT: Fatal - Unexpected Message
2022-03-14 14:07:36.915534+00:00 [noti] <0.23840.19> - {unsupported_record_type,67}

Here is what the log say when we connect using the unsecured port 61613. (I've hidden IP address by replacing it with x)

2022-03-14 14:08:05.388817+00:00 [info] <0.23882.19> accepting STOMP connection <0.23882.19> (xxx.xx.xxx.xxx:63837 -> xx.xx.xx.x:61613) 2022-03-14 14:08:10.445381+00:00 [info] <0.23882.19> closing STOMP connection <0.23882.19> (xxx.xx.xxx.xxx:63837 -> xx.xx.xx.x:61613)

The server log also contains the following warning that occur at night time.

certificate chain verification is not enabled for this TLS connection. Please see https://rabbitmq.com/ssl.html for more information.

but according to documentation, this warning can be ignored because they don't recommend client certs at the moment.

Using Julian's stomp client or another, can we sent message over TLS? If yes, how?
Sorry if the question is a little broad, but my knowledge on TLS are limited and master google didn't had a solution for this one.


Here is the code of the class StompConnection we are using.

/*
Copyright (c) 2011-2012, Julian Lyndon-Smith ([email protected])
http://www.dotr.com
All rights reserved.

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction except as noted below, including without limitation
the rights to use,copy, modify, merge, publish, distribute,
and/or sublicense, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

 The Software and/or source code cannot be copied in whole and
 sold without meaningful modification for a profit.

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.

 Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in
 the documentation and/or other materials provided with
 the distribution.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

USING Progress.Lang.*.
USING dotr.Stomp.*.
USING dotr.Stomp.Interface.*.

ROUTINE-LEVEL ON ERROR UNDO, THROW.

CLASS dotr.Stomp.StompConnection FINAL:

      DEF PRIVATE VAR StompConfig          AS StompConfig NO-UNDO.    
      DEF PRIVATE VAR ServerResponseFrames AS CHAR INIT "CONNECTED,RECEIPT,ERROR":U NO-UNDO.
      DEF PRIVATE VAR DisconnectID         AS CHAR        NO-UNDO.
      
   DEF PUBLIC PROPERTY UserName     AS CHAR NO-UNDO GET. PRIVATE SET. 
   DEF PUBLIC PROPERTY Password     AS CHAR NO-UNDO GET. PRIVATE SET. 
   DEF PUBLIC PROPERTY ConnectionID AS CHAR NO-UNDO GET. PRIVATE SET.
   DEF PUBLIC PROPERTY ClientID     AS CHAR NO-UNDO GET. PRIVATE SET.

   DEF PUBLIC PROPERTY StompVersion AS CHAR NO-UNDO GET. PRIVATE SET.
   DEF PUBLIC PROPERTY StompServer  AS CHAR NO-UNDO GET. PRIVATE SET.
   DEF PUBLIC PROPERTY ErrorMessage AS CHAR NO-UNDO GET. PRIVATE SET.
   DEF PUBLIC PROPERTY ConnectionEstablished    AS LOGI NO-UNDO GET. PRIVATE SET.
 
   DEF PUBLIC PROPERTY ConnectedBroker AS CHAR NO-UNDO
       GET():
                  RETURN SUBST("&1:&2":U,StompConfig:StompActiveServer,StompConfig:StompActivePort).
            END GET. PRIVATE SET.
     
   DEF PUBLIC PROPERTY SocketHandle  AS HANDLE NO-UNDO GET. PRIVATE SET.
   DEF PUBLIC PROPERTY SocketHandler AS HANDLE NO-UNDO GET. PRIVATE SET.
   
   DEF PUBLIC EVENT NewMessage         SIGNATURE VOID (p_Message AS dotr.Stomp.StompMessage).
   DEF PUBLIC EVENT NewRawMessage      SIGNATURE VOID (p_Message AS LONGCHAR).
   DEF PUBLIC EVENT DisconnectedEvent  SIGNATURE VOID ().
   DEF PUBLIC EVENT ConnectedEvent     SIGNATURE VOID ().
  
   /*-----------------------------------------------------------------------------
     Purpose:    
     Parameters: 
     Notes:      
   -----------------------------------------------------------------------------*/
   CONSTRUCTOR StompConnection(p_StompConfig AS dotr.Stomp.StompConfig,
                               p_ClientID AS CHAR,
                               p_UserName AS CHAR,
                               p_Password AS CHAR):
     
      IF p_UserName <> "":U THEN ASSIGN p_StompConfig:login = p_UserName.
      IF p_Password <> "":U THEN ASSIGN p_StompConfig:passcode = p_Password.
      
      ASSIGN THIS-OBJECT:StompConfig = p_StompConfig
                 THIS-OBJECT:UserName    = p_StompConfig:login
                 THIS-OBJECT:Password    = p_StompConfig:passcode
                 THIS-OBJECT:ClientId    = (IF p_ClientID = "":U THEN GUID ELSE p_ClientID).
  
      THIS-OBJECT:CONNECT().
   END CONSTRUCTOR.
   
   /*-----------------------------------------------------------------------------
     Purpose:    
     Parameters: 
     Notes:      
   -----------------------------------------------------------------------------*/
   CONSTRUCTOR StompConnection(p_ClientID AS CHAR,p_UserName AS CHAR,p_Password AS CHAR):
      THIS-OBJECT(dotr.Stomp.StompConfig:Default,p_ClientID,p_UserName,p_Password).
   END CONSTRUCTOR. 
  
   /*-----------------------------------------------------------------------------
     Purpose:    
     Parameters: 
     Notes:      
   -----------------------------------------------------------------------------*/
  DESTRUCTOR StompConnection():
    
    THIS-OBJECT:DISCONNECT() NO-ERROR.
    IF VALID-HANDLE(SocketHandle) THEN 
    DO: SocketHandle:DISCONNECT() NO-ERROR.
        DELETE OBJECT SocketHandle NO-ERROR.
    END.

    IF VALID-HANDLE(SocketHandler) THEN 
    DO: RUN shutdown IN SocketHandler.
        DELETE PROCEDURE SocketHandler NO-ERROR.
    END.  
    
  END DESTRUCTOR.
  
  /*-----------------------------------------------------------------------------
    Purpose:    
    Parameters: 
    Notes:      
  -----------------------------------------------------------------------------*/
  METHOD PUBLIC VOID Disconnect():
  
        DEF VAR ID AS CHAR NO-UNDO.
   
     ConnectionEstablished = FALSE.
   
        ASSIGN id = GUID.           
        SendFrame(SUBST("DISCONNECT~nreceipt:&1~n~n":U,id)).
        
        ASSIGN DisconnectId = id. /** no more frames to be sent */          
        
        IF VALID-HANDLE(SocketHandle) THEN 
     DO: SocketHandle:DISCONNECT() NO-ERROR.
         DELETE OBJECT SocketHandle NO-ERROR.
     END.

     IF VALID-HANDLE(SocketHandler) THEN 
     DO: RUN shutdown IN SocketHandler.
         DELETE PROCEDURE SocketHandler NO-ERROR.
     END.    
    
        CATCH e AS Progress.Lang.AppError : /** swallow all errors, as we're disconnecting anyway */
           DELETE OBJECT e NO-ERROR.
        END CATCH.
        
      END METHOD.
  
    /** Raise the SocketDisconnected event
     */ 
  METHOD PUBLIC VOID DoSocketDisconnected():
     ConnectionEstablished = FALSE.
     DisconnectedEvent:PUBLISH() NO-ERROR.      
  END METHOD.

  /*-----------------------------------------------------------------------------
    Purpose:    
    Parameters: 
    Notes:      
  -----------------------------------------------------------------------------*/
  METHOD PUBLIC VOID ProcessData(p_Message AS LONGCHAR):
    DEF VAR lv_Frame  AS CHAR NO-UNDO.
    DEF VAR lv_Dest   AS CHAR NO-UNDO.
    DEF VAR lv_Header AS CHAR NO-UNDO.    
    DEF VAR lv_Body   AS LONGCHAR NO-UNDO.    
    DEF VAR lv_Index AS INT NO-UNDO.
    
    ASSIGN lv_Frame  = ENTRY(1,p_message,"~n":U).
    
    ASSIGN lv_Frame  = ENTRY(2,p_message,"~n":U) WHEN lv_Frame = "":U 
           lv_index  = INDEX(p_Message,"~n~n":U)
           lv_Body   = IF lv_index = 0 THEN "":U ELSE TRIM(SUBSTRING(p_message,lv_index + 1))
           lv_Header = TRIM(SUBSTRING(p_message,1,lv_index))
           lv_Index  = INDEX(lv_Header,"destination:":U).
    
    ASSIGN lv_Dest = ENTRY(2,ENTRY(1,SUBSTRING(lv_Header,lv_index),"~n":U),":":U) WHEN lv_index > 0
           lv_Dest   = SUBSTR(lv_Dest,INDEX(lv_Dest,"/",2) + 1)                   WHEN lv_index > 0 .
    
    IF lv_header = "":U AND INDEX(ServerResponseFrames,lv_Frame) > 0 THEN 
       lv_Header = STRING(TRIM(SUBSTRING(p_message,LENGTH(lv_Frame) + 1))).
    
    CASE lv_frame:
        WHEN "CONNECTED" THEN 
        DO: setConnectionDetails(lv_Header).  
            ConnectionEstablished = TRUE.           
            ConnectedEvent:PUBLISH() NO-ERROR.    
            APPLY "U2":U TO SocketHandle.       
        END.
        WHEN "RECEIPT" THEN 
        DO: APPLY "U1":U TO SocketHandle.
        END.
        WHEN "ERROR" THEN 
       DO: ErrorMessage = lv_Body.
           APPLY "U1":U TO SocketHandle.
       END.
    END CASE.
            
    NewRawMessage:Publish(p_Message) NO-ERROR.           
    
    THIS-OBJECT:MessageParsed(lv_Frame,lv_Dest,lv_Header,lv_Body) NO-ERROR.
  END METHOD.
  
  /*-----------------------------------------------------------------------------
    Purpose:    
    Parameters: 
    Notes:      
  -----------------------------------------------------------------------------*/
  METHOD PROTECTED VOID MessageParsed(p_Frame AS CHAR,p_Dest AS CHAR,p_Header AS CHAR, p_Body AS LONGCHAR):
    NewMessage:Publish(NEW dotr.Stomp.StompMessage(p_frame,p_Dest,p_Header,p_Body)) NO-ERROR.
  END METHOD.



  /*-----------------------------------------------------------------------------
    Purpose:  Send message
    Parameters: 
    Notes: 
  -----------------------------------------------------------------------------*/  
  METHOD PUBLIC VOID SendFrame(INPUT p_Data AS LONGCHAR):

     DEF VAR lv_MemPtr              AS MEMPTR   NO-UNDO.          
     DEF VAR lv_ToWrite             AS INT      NO-UNDO.
     DEF VAR lv_offset              AS INT      NO-UNDO.
     
     IF DisconnectId <> "":U THEN RETURN. /** Clients MUST NOT send any more frames after the DISCONNECT frame is sent. */

     IF NOT VALID-HANDLE(SocketHandle) OR p_Data = "":U OR p_Data = ? THEN RETURN.

     ASSIGN lv_ToWrite  = PutStringIntoMemoryPointer(p_Data, lv_MemPtr)
            lv_offset   = 0.
          
     DO WHILE lv_ToWrite > 0:
        SocketHandle:WRITE(lv_MemPtr,lv_offset + 1, lv_ToWrite) NO-ERROR.      
        IF SocketHandle:BYTES-WRITTEN = 0 THEN 
            UNDO, THROW NEW AppError("IO Exception":U,0).
                    
            ASSIGN  lv_offset  = lv_offset  + SocketHandle:BYTES-WRITTEN
                    lv_ToWrite = lv_ToWrite - SocketHandle:BYTES-WRITTEN.
     END.
     FINALLY:
        SET-SIZE(lv_MemPtr) = 0.
     END FINALLY.
  END METHOD.
  
  /*-----------------------------------------------------------------------------
    Purpose:  Put string into given memory pointer. 
    Returns: Return the number of bytes in the memory pointer.
    Notes: Make sure character are converted from current session encoding to UTF-8 for french characters (accent).
  -----------------------------------------------------------------------------*/  
  METHOD PRIVATE INT PutStringIntoMemoryPointer(INPUT p_Data AS LONGCHAR, INPUT p_MemPtr AS MEMPTR):
      
     DEF VAR lv_DataUTF8            AS LONGCHAR NO-UNDO.
     DEF VAR lv_NbBytesToWrite      AS INT      NO-UNDO.
     DEF VAR lv_NbCharToWrite       AS INT      NO-UNDO.
     DEF VAR lv_NbCharWritten       AS INT      NO-UNDO.
     DEF VAR lv_NbBytesWritten      AS INT      NO-UNDO.
     DEF VAR vl_CurrentCharacter    AS CHAR     NO-UNDO.

     FIX-CODEPAGE (lv_DataUTF8) = 'utf-8'. 
     lv_DataUTF8 = p_Data.

     ASSIGN lv_NbBytesToWrite  = LENGTH(lv_DataUTF8, "RAW":U) + 1  /* add 1 bytes for 0-terminated string */
            lv_NbCharToWrite   = LENGTH(lv_DataUTF8, "CHARACTER":U)
            lv_NbCharWritten   = 0
            lv_NbBytesWritten  = 0.
            
     SET-SIZE(p_MemPtr) = lv_NbBytesToWrite.
     
     //Line `COPY-LOB FROM p_Data TO p_MemPtr CONVERT TARGET CODEPAGE "UTF-8".` does work.
     //However, it is so slow that it cause error(s) when using it.
     //Thus, we convert encoding character by character with the following loop instead.
     DO lv_NbCharWritten = 1 TO lv_NbCharToWrite:
         
        vl_CurrentCharacter = CHR(ASC(STRING(SUBSTRING(lv_DataUTF8, lv_NbCharWritten, 1, "CHARACTER")), "UTF-8":U, SESSION:CPINTERNAL), "UTF-8":U, "UTF-8":U).
        PUT-STRING(p_MemPtr, lv_NbBytesWritten + 1) = vl_CurrentCharacter.
        lv_NbBytesWritten = lv_NbBytesWritten + LENGTH(vl_CurrentCharacter, "RAW").

     END.
     RETURN lv_NbBytesToWrite.
  END METHOD.
  
  /*-----------------------------------------------------------------------------
    Purpose:    
    Parameters: 
    Notes:      
  -----------------------------------------------------------------------------*/
  METHOD PUBLIC VOID CONNECT():
    
    IF NOT VALID-HANDLE(SocketHandle) THEN 
    DO:  CREATE SOCKET SocketHandle.            
         RUN dotr/Stomp/stompSocketProc.p PERSISTENT SET SocketHandler (THIS-OBJECT).
      
         SocketHandle:SET-READ-RESPONSE-PROCEDURE(IF StompConfig:LargeMessageSupport THEN "SocketBigReadHandler":U
                                                  ELSE "SocketReadHandler":U,SocketHandler).
    END.
        
    SocketHandle:CONNECT(SUBSTITUTE("-H &1 -S &2":U, StompConfig:StompServer, StompConfig:StompPort)) NO-ERROR.
                        
    IF ERROR-STATUS:NUM-MESSAGES > 0 OR NOT SocketHandle:CONNECTED() THEN
    DO:
       /* try connecting using the slave connection details, if set */
       IF StompConfig:FailoverSupported THEN 
       DO: SocketHandle:CONNECT(SUBSTITUTE("-H &1 -S &2":U, StompConfig:StompSlaveServer, StompConfig:StompSlavePort)) NO-ERROR.
           StompConfig:SetActive(StompConfig:StompSlaveServer,StompConfig:StompSlavePort).
       END.
       
       IF ERROR-STATUS:NUM-MESSAGES > 0 OR NOT SocketHandle:CONNECTED() THEN
          UNDO, THROW NEW AppError(SUBSTITUTE("Cannot connect to Stomp Server with Server:&1 Port:&2":U,
                                              StompConfig:Stompserver + "/" + StompConfig:StompSlaveServer, 
                                              StompConfig:StompPort + "/" + StompConfig:StompSlavePort),0).
    END.
    ELSE StompConfig:SetActive(StompConfig:Stompserver,StompConfig:StompPort).
            
    SocketHandle:SET-SOCKET-OPTION ("SO-RCVBUF":U,STRING(1048576)).

    SocketHandle:SET-SOCKET-OPTION ("TCP-NODELAY":U,"TRUE":U).
    
    SocketHandle:SET-SOCKET-OPTION ("SO-KEEPALIVE":U,"TRUE":U).
        
    SendFrame(SUBSTITUTE("CONNECT~naccept-version:1.0,1.1,2.0~nhost:&3~nclient-id:&4~nlogin:&1~npasscode:&2~n~n":U,  THIS-OBJECT:UserName, THIS-OBJECT:Password,StompConfig:StompHost,THIS-OBJECT:ClientID)).
        
    WAIT-FOR "U2":U OF SocketHandle PAUSE 5.
    
    IF ConnectionEstablished = FALSE THEN 
       UNDO, THROW NEW AppError(SUBSTITUTE("Cannot connect to Stomp Server with Server:&1 Port:&2":U,
                                StompConfig:Stompserver + "/" + StompConfig:StompSlaveServer, 
                                StompConfig:StompPort + "/" + StompConfig:StompSlavePort),0).

  END METHOD.

  /*-----------------------------------------------------------------------------
    Purpose:    
    Parameters: 
    Notes:      
  -----------------------------------------------------------------------------*/
  METHOD PRIVATE VOID setConnectionDetails (p_Header AS CHAR):
     DEF VAR i         AS INT  NO-UNDO.
     DEF VAR lv_header AS CHAR NO-UNDO.
     
     DO i = 1 TO NUM-ENTRIES(p_Header,"~n":U):
         lv_header = ENTRY(i,p_Header,"~n":U). 
         CASE ENTRY(1,lv_header,":":U):
             WHEN "session":U THEN THIS-OBJECT:ConnectionID = SUBSTRING(lv_header,INDEX(lv_header,":":U) + 1).
             WHEN "server":U  THEN THIS-OBJECT:stompServer  = SUBSTRING(lv_header,INDEX(lv_header,":":U) + 1).
             WHEN "version":U THEN THIS-OBJECT:stompVersion = SUBSTRING(lv_header,INDEX(lv_header,":":U) + 1).
         END CASE.    
     END.     
  END METHOD.
      
END CLASS.
0

There are 0 answers