개발하는 두더지

[C/C++/MFC] C++ HTTP Web Server에서 HTTPS/SSL 사용하기 (AJAX 통신) 본문

C,C++

[C/C++/MFC] C++ HTTP Web Server에서 HTTPS/SSL 사용하기 (AJAX 통신)

덜지 2016. 7. 22. 03:35

# HTTP VS HTTPS

HTTPS에서 마지막의 S는 Over Secure Socket Layer의 약자로 강화된 HTTP이다.

HTTP는 암호화되지 않은 방법으로 데이터를 전송하므로 서버와 클라이언트가 주고 받는 메시지를 감청하는 것이 매우 쉽다.

예를들어 로그인을 위해서 서버로 비밀번호를 전송하거나, 또는 중요한 기밀 문서를 열람하는 과정에서 악의적인 감청이나

데이터의 변조등을 보안하는 것이 HTTPS다.


# SSL 디지털 인증서

SSL 인증서는 클라이언트와 서버간의 통신을 제 3자가 보증해주는 전자화된 문서다. 클라이언트가 서버에 접속한 직후에

서버는 클라이언트에게 이 인증서 정보를 전달한다. SSL과 SSL 디지털 인증서를 이용했을 때의 이점은 아래와 같다.

   - 통신 내용이 공격자에게 노출되는 것을 막을 수 있다.

   - 클라이언트가 접속하려는 서버가 신뢰할수 있는 서버인지를 판단할 수 있다.

   - 통신 내용의 악의적인 변경을 방지할 수 있다.


SSL 인증서의 역할은 다소 복잡하기 때문에 인증서의 매커니즘을 이해하기 위한 몇가지 지식들을 알고 있어야 한다.

인증서의 기능은 크게 두가지다.

   1. 클라이언트가 접속한 서버가 신뢰할 수 있는 서버임을 보장한다.

   2. SSL 통신에 사용할 공개키를 클라이언트에게 제공한다.


# CA

SSL을 통해서 암호화된 통신을 제공하려는 서비스는 CA를 통해서 인증서를 구입해야 한다.


# 사설 인증기관

개발이나 사적인 목적을 위해서 SSL의 암호화 기능을 이용하려한다면 자신이 직접 CA의 역할을 할수도 있지만

공인된 인증서가 아니기 때문에 경고를 출력한다.


# SSL 인증서의 내용

SSL 인증서에는 다음과 같은 정보가 포함되어 있다.

   1. 서비스의 정보 ( 인증서를 발급한 CA, 서비스의 도메인 등등) : 클라이언트가 접속한 서버가 의도한 서버가 맞는지

   2. 서버 측 공개키 ( 공개키의 내용 , 공개키의 암호화 방법 )     : 서버와 통신할 때 사용할 공개키와 그 공개키의 암호화 방법



# 구현 코드


Client 쪽 요청 보내기/받기 코드

- AJAX 통신

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    function makeRequest(url) {
        try{
            //BrowserType Set
            BrowserType = CheckBrowser();
                
            if(BrowserType >= 6 && BrowserType <= 7)    // code for IE8이하
            {
                httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
            }
            else    // code for IE9+, Firefox, Chrome, Opera, Safari
            {
                httpRequest = new XMLHttpRequest();
            }
 
            if (!httpRequest) {
              alert('Giving up :( Cannot create an XMLHTTP instance');
              return false;
            }
            httpRequest.onreadystatechange = alertContents;
            httpRequest.open('POST', url, true);
            httpRequest.send();
        }
        catch(e){
            alert("catch : " + e.responseText);
        }
    }
 
    function alertContents() {
        
        if (httpRequest.readyState === 4) {
          if (httpRequest.status === 200) {
            if(httpRequest.responseText == "Success"){
                
            }
            else if(httpRequest.responseText >= VERSION){
                // 버전 일치
                ShowWaitMsg(false);
                document.location = LIST_URL;
            }
            else{
                alert("업데이트 필요");
                ShowWaitMsg(false);
            }
            
            
          } else {
            //alert('There was a problem with the request.');
            setBlock();
          }
        }
    }
cs


서버에서 HttpReceiveHttpRequest 를 실행한 후에 클라이언트의 요청을 대기한다.

클라이언트에서 httpRequest.send를 실행하면 서버의 HttpReceiveHttpRequest 다음 코드가 진행된다.

서버에서 요청을 처리 한 후 SendHttpResponse 를 실행하면 클라이언트의 alertContents가 동작한다.

readyState = 4 , status = 200 을 받으면 다음 AJAX 코드가 진행된다.


SERVER 쪽 요청 받기/응답 코드

- 1. 초기화

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
int Init(void)
{
    ULONG           retCode;
    int             UrlAdded       = 0;
    HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1;
 
    //
    // Initialize HTTP Server APIs
    //
    retCode = HttpInitialize( 
        HttpApiVersion,
        HTTP_INITIALIZE_SERVER | HTTP_INITIALIZE_CONFIG,    // Flags
        NULL                       // Reserved
        );
 
    if (retCode != NO_ERROR)
    {
        wprintf(L"HttpInitialize failed with %lu \n", retCode);
        return retCode;
    }
 
    //
    // Create a Request Queue Handle
    //
    retCode = HttpCreateHttpHandle(
        &m_hReqQueue,            // Req Queue
        0                        // Reserved
        );
 
    if (retCode != NO_ERROR)
    {    
        wprintf(L"HttpCreateHttpHandle failed with %lu \n", retCode);
        return retCode;
    }
 
    // HTTPS
 
    BYTE hash[] = {0x570xfd0x3f0xee0x8f0x470x3f0x7e0xbe0x0d0xb80xa80x1a0x380x0e0x440x560xa50x910x9f};
 
    GUID gidReference = {0x3997d7bf0xae860x4665, {0xb50x060xe20x4e0x320x9a0x470xe1}};
 
    SOCKADDR_IN sa;
    memset(&sa,0,sizeof(sa));
    sa.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    sa.sin_family = AF_INET;
    sa.sin_port = htons(12332);
 
    HTTP_SERVICE_CONFIG_SSL_SET scssl;
    scssl.KeyDesc.pIpPort = (SOCKADDR*)&sa;
    scssl.ParamDesc.SslHashLength = sizeof(hash);
    scssl.ParamDesc.pSslHash = (void *)hash;
    scssl.ParamDesc.AppId = gidReference;
    scssl.ParamDesc.pSslCertStoreName = L"root";
    scssl.ParamDesc.DefaultCertCheckMode = 0;
    scssl.ParamDesc.DefaultRevocationFreshnessTime = 0;
    scssl.ParamDesc.DefaultRevocationUrlRetrievalTimeout = 0;
    scssl.ParamDesc.pDefaultSslCtlIdentifier = NULL;
    scssl.ParamDesc.pDefaultSslCtlStoreName = NULL;
    scssl.ParamDesc.DefaultFlags = 0;
 
    DWORD retVal = ::HttpSetServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &scssl, sizeof(scssl), NULL);
    if(retVal != NO_ERROR)
    {
        return -1;
    }
 
    return 0;
}
cs


* HttpInitialize 에서 HTTP_INITIALIZE_CONFIG 가 반드시 있어야 한다. 없으면 HTTPS를 이용 못한다.



- 2. 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int AddUrl(LPCWSTR wszUrl)
{
    ULONG           retCode;
    retCode = HttpAddUrl(
        m_hReqQueue,    // Req Queue
        wszUrl,            // Fully qualified URL
        NULL            // Reserved
        );
 
    if (retCode != NO_ERROR)
    {
        wprintf(L"HttpAddUrl failed with %lu \n", retCode);
        return retCode;
    }
}
cs


- 3. HTTP 요청 받기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
DWORD DoReceiveRequests(
    IN HANDLE hReqQueue
    )
{
    ULONG              result;
    HTTP_REQUEST_ID    requestId;
    DWORD              bytesRead;
    PHTTP_REQUEST      pRequest;
    PCHAR              pRequestBuffer;
    ULONG              RequestBufferLength;
 
    //
    // Allocate a 2 KB buffer. This size should work for most 
    // requests. The buffer size can be increased if required. Space
    // is also required for an HTTP_REQUEST structure.
    //
    RequestBufferLength = sizeof(HTTP_REQUEST) + 2048;
    pRequestBuffer      = (PCHAR) ALLOC_MEM( RequestBufferLength );
 
    if (pRequestBuffer == NULL)
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }
 
    pRequest = (PHTTP_REQUEST)pRequestBuffer;
 
    //
    // Wait for a new request. This is indicated by a NULL 
    // request ID.
    //
 
    HTTP_SET_NULL_ID( &requestId );
 
    for(;;)
    {
        RtlZeroMemory(pRequest, RequestBufferLength);
 
        result = HttpReceiveHttpRequest(
                    hReqQueue,          // Req Queue
                    requestId,          // Req ID
                    0,                  // Flags
                    pRequest,           // HTTP request buffer
                    RequestBufferLength,// req buffer length
                    &bytesRead,         // bytes received
                    NULL                // LPOVERLAPPED
                    );
 
        if(NO_ERROR == result)
        {
            //
            // Worked! 
            // 
            switch(pRequest->Verb)
            {
                case HttpVerbGET:
                    wprintf(L"Got a GET request for %ws \n"
                            pRequest->CookedUrl.pFullUrl);
 
                    result = SendHttpResponse(
                                hReqQueue, 
                                pRequest, 
                                200,
                                "OK",
                                "Hey! You hit the server \r\n"
                                );
                    break;
 
                case HttpVerbPOST:
 
                    wprintf(L"Got a POST request for %ws \n"
                            pRequest->CookedUrl.pFullUrl);
 
                    result= SendHttpPostResponse(hReqQueue, pRequest);
                    break;
 
                default:
                    wprintf(L"Got a unknown request for %ws \n"
                            pRequest->CookedUrl.pFullUrl);
 
                    result = SendHttpResponse(
                                hReqQueue, 
                                pRequest,
                                503,
                                "Not Implemented",
                                NULL
                                );
                    break;
            }
 
            if(result != NO_ERROR)
            {
                break;
            }
 
            //
            // Reset the Request ID to handle the next request.
            //
            HTTP_SET_NULL_ID( &requestId );
        }
        else if(result == ERROR_MORE_DATA)
        {
            //
            // The input buffer was too small to hold the request
            // headers. Increase the buffer size and call the 
            // API again. 
            //
            // When calling the API again, handle the request
            // that failed by passing a RequestID.
            //
            // This RequestID is read from the old buffer.
            //
            requestId = pRequest->RequestId;
 
            //
            // Free the old buffer and allocate a new buffer.
            //
            RequestBufferLength = bytesRead;
            FREE_MEM( pRequestBuffer );
            pRequestBuffer = (PCHAR) ALLOC_MEM( RequestBufferLength );
 
            if (pRequestBuffer == NULL)
            {
                result = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }
 
            pRequest = (PHTTP_REQUEST)pRequestBuffer;
 
        }
        else if(ERROR_CONNECTION_INVALID == result && 
                !HTTP_IS_NULL_ID(&requestId))
        {
            // The TCP connection was corrupted by the peer when
            // attempting to handle a request with more buffer. 
            // Continue to the next request.
            
            HTTP_SET_NULL_ID( &requestId );
        }
        else
        {
            break;
        }
 
    }
 
    if(pRequestBuffer)
    {
        FREE_MEM( pRequestBuffer );
    }
 
    return result;
}
cs



- 4. HTTP 응답 보내기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 
DWORD SendHttpResponse(
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest,
    IN USHORT        StatusCode,
    IN PSTR          pReason,
    IN PSTR          pEntityString
    )
{
    HTTP_RESPONSE   response;
    HTTP_DATA_CHUNK dataChunk;
    DWORD           result;
    DWORD           bytesSent;
 
    //
    // Initialize the HTTP response structure.
    //
    INITIALIZE_HTTP_RESPONSE(&response, StatusCode, pReason);
 
    //
    // Add a known header.
    //
    ADD_KNOWN_HEADER(response, HttpHeaderContentType, "text/html");
   
    if(pEntityString)
    {
        // 
        // Add an entity chunk.
        //
        dataChunk.DataChunkType           = HttpDataChunkFromMemory;
        dataChunk.FromMemory.pBuffer      = pEntityString;
        dataChunk.FromMemory.BufferLength = 
                                       (ULONG) strlen(pEntityString);
 
        response.EntityChunkCount         = 1;
        response.pEntityChunks            = &dataChunk;
    }
 
    // 
    // Because the entity body is sent in one call, it is not
    // required to specify the Content-Length.
    //
    
    result = HttpSendHttpResponse(
                    hReqQueue,           // ReqQueueHandle
                    pRequest->RequestId, // Request ID
                    0,                   // Flags
                    &response,           // HTTP response
                    NULL,                // pReserved1
                    &bytesSent,          // bytes sent  (OPTIONAL)
                    NULL,                // pReserved2  (must be NULL)
                    0,                   // Reserved3   (must be 0)
                    NULL,                // LPOVERLAPPED(OPTIONAL)
                    NULL                 // pReserved4  (must be NULL)
                    ); 
 
    if(result != NO_ERROR)
    {
        wprintf(L"HttpSendHttpResponse failed with %lu \n", result);
    }
 
    return result;
}
cs


Comments