2020년 9월 30일 수요일

WireGuard VPN 해부

이번 시간에는 WireGuard VPN의 동작 원리(protocol)를 상세히 살펴본 후, MACCHIATObin 보드Gl.iNet MangoBox 위에 WireGuard를 올리고, 이 둘간을 연결하는 방법(direct 연결 및 relay server를 경유한 연결)에 관하여 소개해 보고자 한다.

    


See also my WireGuard Analysis document. 😎
 
목차
1. WireGuard VPN Protocol 해부
2. P2P 통신 1: WireGuard VPN으로 Peer간 Direct 연결하기
3. P2P 통신 2: WireGuard VPN으로 Relay Server 구성하기
4. WireGuard VPN Kernel Code 분석
5. WireGuard VPN 활용 예(Use Cases)
6. References
_______________________________________________


1. WireGuard VPN Protocol 해부
WireGuard와 관련해서는 이미 지난 blog post를 통해 두차례 소개한 바 있다. 따라서 여기에서는 같은 내용을 반복 설명하기 보다는, 지난번 설명에서 미흡했던 부분을 중심으로 정리해 보고자 한다.

    => 3 ~ 4장
    => 4장

📌 MACCHIATObin board와 Gl.iNet MangoBox에 관해서는 아래 내용을 참고하도록 하자.

1.1) WireGuard Protocol
지금부터는 WireGuard protocol의 동작 과정을 message handshaking 과정(key 교환 과정)을 중심으로 상세히 분석해 보도록 하겠다.

📌 WireGuard는 Diffie-Hellman(DH) key 교환 방식을 기초로 하는 Noise protocol을 기반으로 만들어졌다. 정확한 protocol 명칭은 Noise IKpsk2 이다.
📌 따라서 WireGuard protocol을 정확히 이해하기 위해서는 Noise protocol과 Diffie-Hellman의 동작 원리를 어느 정도는 이해하고 있어야 한다.

[그림 1.1] ECDH 개요


위 그림이 이해가 되는 분들은 다음으로 넘어가도 좋다. 😋


<용어 정리>
____________________________________________________________________

Initiator : Key 교환을 위해 handshaking을 시작하는 측
Responder : Initiator로 부터의 요청을 받아 처리하는 측
  => Initiator와 Responder는 사전에 정해진 것이 아니며, 상황에 따라 Initiator도 Responder도 될 수 있음.

Si_pub : Initiator의 static(고정) public key(32 bytes). 즉, 사전(before handshake)에 미리 만들어둔 Initiator 측의 public key
Si_priv : Initator의 static(고정) private key(32 bytes)즉, 사전에 미리 만들어둔 Initiator 측의 private key
Ei_pub : Initator의 ephemeral(임시) public key(32 bytes)
Ei_priv : Initator의 ephemeral(임시) private key(32 bytes)

Sr_pub : Responder의 static(고정) public key(32 bytes). 즉, 사전에 미리 만들어둔 Responder 측의 public key
Sr_priv : Responder의 static(고정) private key(32 bytes)즉, 사전에 미리 만들어둔 Responder 측의 private key
Er_pub : Responder의 ephemeral(임시) public key(32 bytes)
Er_privResponder의 ephemeral(임시) private key(32 bytes)

Q : 32 byte preshared 대칭 키(symmetric key)

Ti_send, Ti_recv : 실제 패킷 암호화에 사용되는 대칭키(32 bytes), handshaking의 결과로 얻게됨.
Tr_send, Tr_recv : 실제 패킷 암호화에 사용되는 대칭키(32 bytes), handshaking의 결과로 얻게됨.
  => Ti_send = Tr_recv, Ti_recv = Tr_send

DH(sk, pk) : X25519 공개키 기반의 Diffi-Hellman 함수, sk = private(or secret) key, pk = public key

aead-enc(key, nonce, plaintext, aad) : 암호화 함수, key = 32byte 암호키, nonce = 64bit random 값, plaintext = 암호화할 내용, aad = arbitrary length additional authenticated data
aead-dec(key, nonce, ciphertext, aad) : 복호화 함수, ciphertext = 복호화할 내용
  => 암/복호화 알고리즘 : ChaCha20-Poly1305

Hash(data) : BLAKE2s 해쉬 함수, 임의의 길이의 data를 입력으로 받아 32byes 결과 값을 만들어 줌.

HKDFn(salt, input keying material) :  n번째 key 값을 생성해주는 함수로, 내부적으로는 HAMC을 위한 hash  함수로 BLAKE2s가 사용됨.


<Protocol 분석을 위한 참고 사항>
- handshake pattern IKpsk2
- DH function X25519
- cipher function ChaCha20-Poly1305
- hash function BLAKE2s
- prologue WireGuard v1 zx2c4 Jason@zx2c4.com (34 bytes)
____________________________________________________________________

[그림 1.2] IKpsk2 절차 요약 [출처 - 참고문헌 3]


0) <- s  : a pre-message 단계
  => 이 단계는 key 교환을 시작하기 위한 사전 준비 단계로 offline 상에서 진행된다.

시작에 앞서 아래 정보(Noise protocol name 및 prologue 값)를 이용하여 h0, h1(32-byte handshake hash) 및 ck0(32-byte chaining key) 등을 미리 준비해 둔다.

h0 = Hash("Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s")
ck0 = h0
h1 = Hash(h0 ||"WireGuard v1 zx2c4 Jason@zx2c4.com")

각각의 peer(Initiator와 Responder)는 Curve25519 공개키 알고리즘을 이용하여 private/public key(이를 static key라고 칭함)를 생성 후, 자신의 public key(Si_pub, Sr_pub)를 상대방에게 전달(out-of-band  즉 offline 상태에서 전달)한다. 이후 각각의 peer는 Si_pub or Sr_pub로 부터 hash 값을 구한다.

h2 = Hash(h1 ||Sr_pub )

1) -> e, es, s, ss  : Handshake Initiation 단계
  => 이 단계는 Initiator가 Reponder에게 message를 보내고, 각각 그에 맞는 action을 취하는 단계에 해당한다.

먼저 Initiator는 ephemeral key pair(Curve25519 기반 임시 private/public key 쌍)를 생성한 후, 임시 공개키(Ei_pub)를 Responder에게 전달한다. 이후 Initiator는 h2와 Ei_pub를 이용하여 h3(Hash 값)를 계산하고, HDKF 함수를 통해 ck0와 Ei_pub로 부터 ck1(32-byte chaining key)을 생성(유도: derivation)해 낸다.

I: (Ei_priv , Ei_pub) = DHGen; publish Ei_pub
R: Read Ei_pub
h3 = Hash(h2 || Ei_pub)
ck1 = HKDF1 (ck0 , Ei_pub)

Initiator는 Ei_priv(자신의 임시 private key), Sr_pub(Responder의 고정 public key)를 가지고 DH(Diffie-Hellman) 함수를 돌려 symmetric key(대칭키)를 얻어낸다. Responder 역시 Sr_priv, Ei_pub(자신의 고정 private key와 Initiator의 임시 public key)를 가지고 DH 함수를 돌려 symmetric key(대칭키) 값을 구한다. 이후 HKDF 함수를 통해 ck2(32-byte chaining key)와 k0(32-byte handshake cipher key)를 재 계산해 낸다.
  ==> 1차 DH( ) 함수 실행
I: (ck2 , k0 ) = HKDF2 (ck1 , DH(Ei_priv, Sr_pub))
R: (ck2 , k0 ) = HKDF2 (ck1 , DH(Sr_priv , Ei_pub))

(1차 암호화에 사용할 대칭키가 준비되었으므로) Initiator는 자신의 고정 public key(Si_pub)를 암호화(k0 대칭키 사용)한 후, Responder로 보낸다. 만일 Responder가 이를 복호화한 후 자신이 보유하고 있는 Si_pub(offline으로 사전에 전달 받은 공개키)와 비교해 일치하지 않는다면, handshaking 과정은 중단된다(일종의 인증 과정으로 볼 수 있음).

I: enc-id = aead-enc(k0 , 0, Si_pub , h3); publish enc-id
R: Si_pub = aead-dec(k0 , 0, enc-id, h3); abort on failure
h4 = Hash(h3 || enc-id)

이후 Initiator는 자신의 고정 private key(Si_priv)와 Responder의 고정 public key(Sr_pub)를 가지고 DH 함수를 돌려 symmetric key 값을 계산한다. Responder 역시 자신의 고정 private key(Sr_priv)와 Initiator의 고정 public key(Si_pub) 값을 가지고 DH 함수를 돌려 동일한 symmetric key 값을 구한다. 이후 각각 HDKF 함수를 통해 ck3와 k1(암호화에 사용할 symmetric key)를 생성해 낸다.
  ==> 2차 DH( ) 함수 실행
I: (ck3 , k1) = HKDF2 (ck2 , DH(Si_priv , Sr_pub))
R: (ck3 , k1) = HKDF2 (ck2 , DH(Sr_priv , Si_pub))

(이번 단계의 마지막 절차로) Initiator는 시간 값을 암호화해서 보내고 Responder는 이를 복호화한다. 만일 실패한다면 handshake 과정은 역시 중단된다.

I: enc-time = aead-enc(k1 , 0, time, h4 ); publish enc-time
R: time = aead-dec(k1 , 0, enc-time, h4 ); abort on failure
h5 = Hash(h4 || enc-time)

2) <- e, ee, se, psk : Handshake Response 단계
  => 이 단계는 Responder가 Initiator에게 message를 보내고, 각각 그에 맞는 action을 취하는 단계에 해당한다.

(반대로) Responder는 Curve25519 공개키 암호 알고리즘을 이용해 임시 암호 key 쌍(ephemeral key pair)을 만들어 낸 후, 공개키(Er_pub)를 Initiator에게 보낸다. 그후  Responder는 hash 값(h6)과 HKDF 함수를 통해 ck4 값을 계산해 낸다.

R: (Er_priv , Er_pub ) = DHGen; publish Er_pub
I: Read Er_pub
h6 = Hash(h5 || Er_pub)
ck4 = HKDF1(ck3 , Er_pub)

그 다음 Reposnder는 자신의 임시 private key(Er_priv)와 (b) 단계에서 받아둔 상대방의 임시 public key(Ei_pub)에 대해 DH 함수를 이용해 symmetric key를 계산해 낸다. Initiator도 동일한 방식, 즉 DH(Ei_priv, Er_pub)를 통해 symmetric key를 계산해 낸다.
  ==> 3차 DH( ) 함수 실행
R: ck5 = HKDF1 (ck4 , DH(Er_priv , Ei_pub))
I: ck5 = HKDF1 (ck4 , DH(Ei_priv , Er_pub))

Responder는 자신의 임시 private key(Er_priv)와 Initiator의 고정 public key(Si_pub)를 가지고 DH 함수를 돌려 symmetric key를 만들어 낸다. 마찬가지로 Initiator도 자신의 고정 private key(Si_priv)와 Responder의 임시 public key(Er_pub)를 가지고 동일한 symmetric key를 생생해 낸다.
  ==> 4차 DH( ) 함수 실행
R: ck6 = HKDF1 (ck5 , DH(Er_priv , Si_pub))
I: ck6 = HKDF1 (ck5 , DH(Si_priv , Er_pub))

만일 preshared key(Q)를 사전(사전 준비 단계, 즉 offline 단계)에 준비해 두었다면, 이를 이용(mix)해 HKDF 함수를 돌려 새로운 key 값을 유도해 낸다. 참고로, 이 preshared key(Q)를 사용하게 되면, PQC(Post Quantum Cryptography) 알고리즘을 사용하지 않고도, Quantum attack을 일정부분 막아낼 수 있다. 물론 post-quantum attack을 방지하기 위한 근본적인 해결책은 PQC 알고리즘을 적용하는 것이긴 하지만 말이다.

(ck7 , τ, k2 ) = HKDF3 (ck6 , Q)
h7 = Hash(h6 || τ )

(이번 단계의 마지막 과정으로) Responder는 빈(empty) payload를 암호화(k2 키값 사용)해서 Initiator에게 보내고, Initiator는 이를 복호화하여 빈 payload인지를 확인한다. 만일 복호화에 실패(빈 payload가 아니라면)한다면 지금까지의 handshake 과정은 여기서 중단되게 된다.

R: enc-empty = aead-enc(k2 , 0, empty, h7); publish enc-empty
I: empty = aead-dec(k2 , 0, enc-empty, h7); abort on failure
h8 = Hash(h7 || enc-empty)

3) Handshaking 완료 단계
끝으로, 지금까지의 과정을 통해 얻는 ck7(chaining key) 값과 e(empty string)에 대해 HKDF 함수를 통해 한번 더 hashing해 줌으로써, 최종 송신 및 수신용 암호키(Ti_send, Ti_recv)를 얻게 된다. 이후 지금까지 임시로 만들어 사용했던 ephemeral key 등은 모두 삭제된다.

I: (Ti_ send , Ti_recv ) = HKDF 2 (ck7 , e)
R: (Tr_recv , Tr_send ) = HKDF 2 (ck7 , e)

이후 이 두개의 암호 키 쌍 (Ti_send, Ti_recv), (Tr_recv, Tr_send)를 사용하여 실제 data에 대한 암호화 및 복호화를 수행한다.

📌 위의 내용을 요약해 보면, WireGuard 키교환 protocol은 두개의 Curve25519 공개키 쌍(Static, Ephemeral)을 생성한 후, 이를 이용해 DH 함수를 4번 호출(E/S, S/S, E/E, E/S)하는 과정을 통해 실제 암호화에 사용할 대칭키를 생성하는 것임을 알 수 있다. 물론 이 과정에서 중간에 생성된 대칭키로 암/복호화는 과정을 통해 상호 인증을 하고 있으며, 중간 중간에 생성한 암호키에 대해 여러 차례의 Hash 함수를 거치도록 함으로써 암호키의 복잡도(역으로 유추하기 어렵게 만듦)를 높이고 있음도 알 수 있다.


📌 최종적으로 생성되는 암호 키 쌍 (Ti_send, Ti_recv), (Tr_recv, Tr_send)은 새로운 handshaking 마다 새롭게 생성되므로, perfect forward secrecy를 보장한다고 말할 수 있다.
________________________________________

지금까지 설명한 (조금은 복잡하고 난해한) handshaking 과정을 하나의 그림으로 표현해 보면 다음과 같다. 앞서 설명한 바와 같이 내부적으로는 아주 복잡한 과정을 거치지만, 겉으로 보기에는 (마치 TCP 3-way handshaking 처럼) 3번의 handshaking 과정(1.5 RTT handshake)만에 암호화에 사용할 대칭 key 교환이 안전하게 이루어 짐을 알 수 있다.

[그림 1.3] 정상 상태에서의 WireGuard Handshake 절차 [출처 - 참고문헌 3]

📌 IKEv2, SSL/TLS의 key 교환과 비교할 때, WireGuard(Noise 변형)의 key 교환 방식은 (인증서 등을 동원하지는 않았지만) 안정성 면에서 손색이 없다고 판단된다.

한편, 아래 그림은 정상적인 Handshaking 과정이 아니라, Initiator or Responder가 부하가 걸려 있을 경우(under load 상태 즉, 처리할 패킷이 많아 큐에 쌓여 있는 상태), Cookie를 생성하여 해당 Cookie를 처리하는 경우에 대해서만 handshaking을 이어나가도록 하는 내용(나머지 패킷은 drop)을 보여주고 있다. 아래 그림 중 왼쪽은 Reponder가 under load 상태인 경우이고, 오른쪽은 Initiator가 under load 상태인 경우를 보여준다.

[그림 1.4] 부하 발생시 WireGuard Handshake 절차 [출처 - 참고문헌 3]

📌 Cookie Reply 메시지가 발생하는 경우는 under load 상황(예: DoS attack 상황)으로 (linux kernel 상에 구현된 wireguard의 경우) 대략 512개의 handshake packet이 queue에 쌓여 있을 경우를 under load 상황으로 인지한다.
under_load = skb_queue_len(&wg->incoming_handshakes) >=
             MAX_QUEUED_INCOMING_HANDSHAKES / 8;


1.2) WireGuard Protocol Message Format
앞 절에서는 handshake 과정(or Key 교환 과정)을 암호 알고리즘 중심으로 살펴 보았으니, 이번에는 패킷 중심으로 분석해 보도록 하자.

WireGuard handshake message type은 아래와 같이 총 4가지가 있다(아주 간결하다).

[그림 1.5] WireGuard handshake message type[출처 - 참고문헌 3]

이중 첫번째 messge type(1)인 Handshake Initiation message의 format은 다음과 같다.

[그림 1.6] Handshake Initiation Message Format(148 bytes) [출처 - 참고문헌 3]

<필드의 의미>
______________________
Type : 1 = Handshake Initiation message
Reserved (3 bytes) : 정의되지 않은 field(나중에 사용할 목적으로 공간만 잡아둠)
Sender Index(4 bytes) : random하게 생성된 index(local identifier) 값. 이걸 보내는 이유는 Responder가 응답을 보낼 때 Receiver Index field에 이 값을 넣어 보내기 위함임. Initiator는 이 index 값을 사용하여 자신의 hash table을 lookup하게 됨.
Initiator ephemeral public key(32 byes) : initiator가 생성한 임시 public key
Encrypted initiator static public key(48 bytes) : initiator가 생성한 고정 public key. 암호화하여 48 bytes(= 암호화된 32 bytes + 16 bytes authentication tag)가 됨.
Encrypted timestamp(28 bytes) : 암호화된 timestamp(28 = 12(96 bits) + 16 bytes authentication tag)
MAC1(16 bytes) : Responder의 고정 public key를 hash 처리하여 여기에 실어서 보냄. Responder는 자신의 public key로 부터 동일한 hash 값을 계산하여 비교하게 됨. 일종의 인증용으로 볼 수 있을 듯.
MAC2(16 bytes) : Cookie Reply 단계에서 받은 cookie(32 bytes)를 복호화한 후, 여기에 실어서 응답함. 평상시(under load 상황이 아닌 경우)에는 MAC2 field는 그냥 무시됨.
______________________

두번째 message type(2)인 Handshake Response message의 format은 다음과 같다. 

[그림 1.7] Handshake Response message format(92 bytes) [출처 - 참고문헌 3]

<필드의 의미>
______________________
Type : 2 = Handshake Response message
Reserved (3 bytes) : 정의되지 않은 field(나중에 사용할 목적으로 공간만 잡아둠)
Sender Index(4 bytes)random하게 생성된 index(local identifier) 값.
Receiver Index(4 bytes) : Initiator로 부터 받은 Sender Index 값을 여기에 적어 돌려 보냄. Message format 어디를 봐도 ip address에 관한 부분은 없음. 즉, wireguard는 endpoint ip 주소가 변경되어도 tunnel이 유지되는데, 그 이유(index & public key 기반)를 여기에서 찾을 수 있겠음.
Responder ephemeral publick key(32 bytes) : Responder가 생성한 자신의 임시 public key
Encrypted empty(16 bytes) : 빈 data를 암호화하여 전달(16 bytes authentication tag만 존재). Initiator는 복호화한 결과가 빈 data가 아니면 handshaking 과정을 중단하게 됨.
MAC1(16 bytes) Initiator의 고정 public key를 hash 처리하여 여기에 저장함. Initiator는 자신의 public key로 부터 동일한 hash 값을 계산하여 비교하게 됨. 일종의 인증용으로 볼 수 있을 듯.
MAC2(16 bytes) Cookie Reply 단계에서 받은 cookie(16 bytes)를 여기에 실어서 응답함.
______________________

다음으로 세번째 message type(3)인 Cookie Reply message의 format은 다음과 같다. 

[그림 1.8] Cookie Reply message format(64 bytes) [출처 - 참고문헌 3]

<필드의 의미>
______________________
Type : 3 = Cookie Reply message
Reserved (3 bytes) : 정의되지 않은 field(나중에 사용할 목적으로 공간만 잡아둠)
Receiver Index(4 bytes) Initiator or Responder로 부터 받은 Sender Index 값을 여기에 적어 돌려 보냄.
Nonce(24 bytes) : 아래 cookie를 암호화는 과정에서 사용되는 nonce 값.
Encrypted cookie(32 bytes) : cookie는 random number + peer ip 주소/port를 조합한 내용을 BLAKE2s로 hash(16 bytes 결과 생성)한 후, 이를 plaintext 형태로 보내지 않고, 다시 암호화해서 보내게 됨. 32bytes = 16bytes(암호화된 hash 값) + 16 authentication tag(by Polcy1305)

     nonce ∈ R {0, 1} 192
     encrypted-cookie = XChaCha20Poly1305(cookie-key, nonce, cookie, msg)
______________________

📌 Cookie를 만들기 위해 peer의 source ip 주소가 사용됨.

마지막으로 네번째 message type(4)인 Transport Data message의 format은 다음과 같다. 

[그림 1.9] Transport Data(실제 암호화된 packet) message format(최소 32 이상) [출처 - 참고문헌 3]

<필드의 의미>
______________________
Type : 4 = Transport Data message
Reserved (3 bytes) : 정의되지 않은 field(나중에 사용할 목적으로 공간만 잡아둠)
Receiver Index(4 bytes) Initiator or Responder로 부터 받은 Sender Index 값을 여기에 적어 돌려 보냄.
Counter (8 bytes) : handshake과정이 성공할 경우, Initiator & Responder는 0 부터 시작하는 Nonce 값을 관리하게 됨(이 값은 encryption 과정에서 사용되고, encryption 후에 1씩 증가시킴). 이 Nonce 값을 encoding 후 Counter 필드에 실어서 보냄. 이 Counter 값은 UDP packet이 중복 수신되는 경우를 방지하는 용도 즉, 동일한 Counter 값을 가진 UDP packet이 수신될 경우 drop 시키는 목적(replay attck 차단)으로도 사용됨.
Encrypted packet (>= 16 bytes) : 실제 ip packet을 암호화하기 위해 16 bytes의 배수인지 체크하여 아닐 경우 padding 처리(0으로 채움)를 한 후, 암호화를 하게 됨. 패킷 암호화 후에는 Poly1305 알고리즘을 이용하여 checksum 16 byte를 생성하여 packet의 맨끝에 추가하게 됨. 
______________________

📌위의 그림 1.9와 같이 Wireguard tunnel header는 16 byte로 고정되어 있어, parsing 과정이 필요 없다. 따라서 가변 길이 header를 갖는 protocol에 비해 (parsing 과정이 필요 없으무로) 그만큼 빠른 속도를 내기에 유리하다(또한 h/w 화 하기에도 유리하다).
📌Encrypted packet size가 16(empty payload)인 packet은 Keepalive mssage이다.

<여기서 잠깐 !>
    => WireGuard의 MTU 값이 1420인 이유에 대하여...

20 or 40 bytes : 20 = minimum IPv4 header size, 40 = mininum IPv6 header size
+
8 bytes : UDP header size
+
32 bytes : WireGuard message header size(그림 1.9 참조)
= 80

따라서 1500 - 80 = 1420이 된다. 만일 IPv4 망만을 사용한다고 가정하면 1440으로 설정해도 되겠다.

[그림 1.10] WireGuard IP tunnel packet format
_______________________________

<여기서 잠깐 !>
    => x.509 인증서를 사용하지 않는 wireguard ~ 그럼 인증 및 ECDSA와 같은 서명 & 검증 기능은 ?

WireGuard는 x.509 인증서를 사용하지 않는다. ECDSA 등을 이용한 서명 & 검증 기능 같은 것도 사용하지 않는다. 그럼 인증을 어떻게 한단 말인가 ? 앞서도 잠시 언급하긴 했지만, 암호화의 과정, 즉 private key로 암호한 내용을 public key로 복호화 가능한지 여부, 혹은 offline으로 전달한 public key에 대한 hash 값(MAC1)을 수신 측에서 다시 계산하여 일치하는지를 확인하는 방법 등을 통해 정당한 peer인지를 확인(인증)하도록 하고 있다.
물론, 이러한 인증 방식이 x.509 인증서 기반 or ECDSA 서명/검증 방식에 비해서 보안 강도가 좀 낮다고 주장하는 이도 있을 수 있겠으나, 복잡하다고 무조건 좋은 것은 아니며, 간결함과 빠른 속도가 때로는 해법일 수도 있다는 의견을 드리고 싶다.
Wireguard는 간결하고, 빠른 handshaking 과정 덕분에 2분 간격으로 새로운 handshaking 과정을 수행하게 되고, 그 과정에서 새로운 암호키를 생성하고 있으니, 그야말로 완벽한 PFS(Perfect Forward Secrecy)를 지원한다고 말할 수 있겠다.
_______________________________

<여기서 잠깐 !>
    => WireGuard message format을 보면 어디에도 peer의 endpoint(ip & port) 정보가 포함되어 있지 않다. 그렇다면 wireguard는 여러 peer(peer table) 중에서 정확하게 원하는 peer 하나를 어떻게 찾아 낼 수 있을까 ?

그 해답은 static public key와 index(sender, receiver) 필드에 있다.
[그림 1.6] Handshake Initiation Message Format을 보면, initiator는 첫번째 DH() 결과로 얻은 key 값 즉, ck2를 이용하여 자신의 static public key를 암호화한 후, random하게 생성된 index(local identifier) 값 등과 함께 handshake initiation message를 구성 후, 이를 responder에게 전달한다.
Message를 수신한 responder는 역시 DH()를 이용해 ck2를 얻은 후, initiator가 암호화해서 보내준 static public key를 복호화하게 되고, 이를 이용해 peer(initiator)가 누구인지를 1차적으로 알게 된다. 이후 responder는 initiator로 부터 받은 sender index 값을 receiver index에 넣고, 역시 자신이 random하게 생성한 sender index 값 등으로 Handshake Response Message를 만들어 initiator에게 돌려 보낸다.
Responder로 부터 handshake response message를 수신한 initiator는 receiver index 필드 값이 자신이 전에 만들어 보낸 index 값인지를 확인하게되고, 이를 통해 peer table에서 원하는 peer를 정확하게 선택할 수 있게 된다.
따라서, 앞서 설명한 static public key 및 sender/receiver index 값은 initiator 및 responder 각각에서 운용하는 peer table에서 원하는 peer를 lookup하는데 사용하는 값으로 해석될 수 있다.
_______________________________

끝으로 WireGuard  protocol의 handshake state machine 관련 그림을 추가하는 것으로 이번 장을 끝마치도록 하겠다.

[그림 1.11] WireGuard handshake state machine [출처 - 참고문헌 3]



2. P2P 통신 1: WireGuard VPN으로 Peer간 Direct 연결하기
이번 장에서는 아래와 같은 네트워크 환경에서 WireGuard를 이용하여 안전한 1:1 VPN 통신이 이루어지는지를 확인해 보고자 한다.

[그림 2.1] WireGuard를 이용한 direct VPN 구성(1)


<MACCHIATObin board>
root@localhost:~/workspace# ./wg genkey | tee ./privatekey | ./wg pubkey > ./publickey
root@localhost:~/workspace# ip link add dev wg0 type wireguard
root@localhost:~/workspace# ip address add dev wg0 10.1.1.200/24
root@localhost:~/workspace# ip link set up dev wg0
root@localhost:~/workspace# ./wg set wg0 listen-port 59760 private-key ./privatekey peer oGqQEGdTho5jwoqt3aIiYYXfehQTy83FNYKHC6HBUUs= allowed-ips 10.1.1.0/24 endpoint 0.0.0.0:0
  => endpoint 값으로 0.0.0.0:0을 준 이유는 반대편 endpoint가 LTE 망을 타고 나오기 때문이다.
  => peer 다음에 들어가는 public key는 1장의 내용을 기준으로 하면, peer의 고정(static) public key 값이다.

root@localhost:~/workspace# iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE

<Ubuntu B>
root@localhost:~/workspace$ sudo wg genkey | tee ./privatekey | wg pubkey > ./publickey
root@localhost:~/workspace$ sudo ip link add dev wg0 type wireguard
root@localhost:~/workspace$ sudo ip address add dev wg0 10.1.1.100/24
root@localhost:~/workspace$ sudo ip link set up dev wg0
root@localhost:~/workspace$ sudo wg set wg0 listen-port 59760 private-key ./privatekey peer FB6Tnz76Kl/DGXSNxlNqXZ+yNCvQVg3FGsYORQoe+mw= allowed-ips 10.1.1.0/24 endpoint x.x.x.x:59760
  => endpoint 값 x.x.x.x는 왼쪽 상단의 Wi-Fi Access Point의 WAN IP 주소이다.
  => peer 다음에 들어가는 public key는 1장의 내용을 기준으로 하면, peer의 고정(static) public key 값이다.

📌 위의 설정이 제대로 먹히려면 사전에 왼쪽 상단의 공유기에서 아래와 같은 port forwarding 설정을 해 주어야 한다.
0.0.0.0/0 ---> x.x.x.x:59760 ---> MACCHIATObin private IP:59670

이 상태에서 ping test를 해 보도록 하자. OK, 정상 동작한다.

[그림 2.2] ping from 10.1.1.200 to 10.1.1.100

[그림 2.3] ping from Ubuntu A to Ubuntu B

📌그림 2.1의 오른쪽 망이 LTE로 연결되어 있으므로, Ubuntu B -> MACCHIATObin 쪽으로 먼저 ping을 시도해야 한다(그래야 tunnel이 뚫린다).

(또 다른 테스트베드인) 아래 환경은 우측망에 MangBox가 추가된 점이 앞서와 다르다.

[그림 2.4] WireGuard를 이용한 direct VPN 구성(2)

(위 내용에는 없지만 MangBox에 Wireguard를 문제없이 설치했다면) 여기서 특별히 문제가 될 만한 부분은 OpenWrt firewall 설정 뿐이다. 즉, wireguard를 위한 별도의 zone(wg)을 하나 만들고, wg <=> lan, wg <=> wan 간에 forwarding이 가능하도록 firewall 설정을 추가해주기만 하면 문제 없이 tunnel이 뚫릴 것이다.

# Add the firewall zone
root@mango:~# uci add firewall zone
root@mango:~# uci set firewall.@zone[-1].name='wg'
root@mango:~# uci set firewall.@zone[-1].input='ACCEPT'
root@mango:~# uci set firewall.@zone[-1].forward='ACCEPT'
root@mango:~# uci set firewall.@zone[-1].output='ACCEPT'
root@mango:~# uci set firewall.@zone[-1].masq='1'
root@mango:~# uci set firewall.@zone[-1].mtu_fix='1'

# Add the WireGuard interface to it
root@mango:~# uci set firewall.@zone[-1].network='wg0'

# Forward WAN and LAN traffic to/from it
root@mango:~# uci add firewall forwarding
root@mango:~# uci set firewall.@forwarding[-1].src='wg'
root@mango:~# uci set firewall.@forwarding[-1].dest='wan'
root@mango:~# uci add firewall forwarding
root@mango:~# uci set firewall.@forwarding[-1].src='wg'
root@mango:~# uci set firewall.@forwarding[-1].dest='lan'
root@mango:~# uci add firewall forwarding
root@mango:~# uci set firewall.@forwarding[-1].src='lan'
root@mango:~# uci set firewall.@forwarding[-1].dest='wg'
root@mango:~# uci add firewall forwarding
root@mango:~# uci set firewall.@forwarding[-1].src='wan'
root@mango:~# uci set firewall.@forwarding[-1].dest='wg'

root@mango:~# uci commit firewall
root@mango:~# /etc/init.d/firewall restart

나머지 설정은 크게 어려운 부분이 없으니, 이에 관해서는 독자 여러분의 몫으로 남겨두도록 하겠다. 😋
_________________________________________

이번 장에서는 WireGuard VPN을 이용하여 peer간에 1 대 1 통신이 가능한지를 실험을 통해 확인해 보았다. 이 실험의 전제 조건은 두개의 peer가 위치한 (최소한) 어느 한쪽의 NAT 장비(firewall, 공유기, CGNAT 등)에서는 port forwarding 설정을 해 주어야 한다는 것이었다. 하지만, 이것이 불가능한 경우에는 어찌해야 할까 ? 다음 장에서는 양쪽 NAT 장비 모두에서 port forwarding이 불가할 경우(예: LTE <-> LTE), 이를 해결하는 방법을 소개하도록 하겠다.



3. P2P 통신 2: WireGuard VPN으로 Relay 서버 구성하기
우리는 이전 blog post에서 n2n를 이용하여 NAT 장비의 설정을 변경하지 않으면서도 2개의 peer간에 상호 통신(P2P 통신)이 가능하다는 것을 체험하였다. 이 절에서는 WireGuard를 이용하여 비슷한 동작이 가능하다는 사실을 확인해 보고자 한다.

우선 먼저 아래 환경(2개의 LTE 망과 1개의 Ethernet 망)에서 relay 구성이 가능한지를 확인해 보도록 하자.

[그림 3.1] WireGuard server를 이용한 relay VPN 구성(1)

📌 n2n과 wireguard의 외관상의 차이는 n2n은 중앙에 전용 supernode가 필요했지만, wireguard는 동일한 기능을 가진 제 3의 wireguard를 relay server로 활용한다는 점이다.

<상단 Relay Server>
$ echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
$ echo "net.ipv4.conf.all.proxy_arp = 1" >> /etc/sysctl.conf
$ sudo sysctl -p /etc/sysctl.conf
  => ip forwarding 및 proxy arp 기능을 enable시킨다.

$ wg genkey | tee ./privatekey | wg pubkey > ./publickey
$ sudo ip link add dev wg0 type wireguard
$ sudo ip address add dev wg0 10.1.1.254/24
$ sudo ip link set up dev wg0
  => wg0 interface 기본 설정

$ sudo wg set wg0 listen-port 59760 private-key ./privatekey peer bLEjXKumUj9X7FEVIJYsJSDMfUFjrHfBAGtvg++hmnQ= allowed-ips 10.1.1.200/32 endpoint 0.0.0.0:0
  => Ubuntu(A)를 peer로 등록

$ sudo wg set wg0 listen-port 59760 private-key ./privatekey peer oGqQEGdTho5jwoqt3aIiYYXfehQTy83FNYKHC6HBUUs= allowed-ips 10.1.1.100/32 endpoint 0.0.0.0:0
  => Ubuntu(B)를 peer로 등록 

📌 Relay server는 allowed-ips 값으로 10.1.1.0/24와 같이 network을 사용하면 안되며, 위와 같이 정확히 peer 자체를 기술해 주어야 한다.

sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i wg0 -o wg0 -m conntrack --ctstate NEW -j ACCEPT
 => wg packet을 forwarding해 줌.

$ sudo iptables -t nat -A POSTROUTING -s 10.1.1.0/24 -o wlx909f3309fe00 -j MASQUERADE
  => physical interface에 대해 masquerading rule 추가
  => 이 상황에서 반드시 필요한 설정은 아님.

$ sudo ifconfig wg0 mtu 1300
  => (중요) LTE 내부에 GTP tunnel이 사용되는 관계로 이를 고려하여 mtu size를 1300(대략적인 값임)으로 줄여 줌.


<Ubuntu A>
wg genkey | tee ./privatekey | wg pubkey > ./publickey
sudo ip link add dev wg0 type wireguard
sudo ip address add dev wg0 10.1.1.200/24
$ sudo ip link set up dev wg0

$ sudo wg set wg0 listen-port 59760 private-key ./privatekey peer Q1hf3SMjpq0+ckzs71hcwSBkrMG+m6bB4xTl1s6pU1I= allowed-ips 10.1.1.0/24 endpoint x.x.x.x.:59760

<Ubuntu B>
wg genkey | tee ./privatekey | wg pubkey > ./publickey
sudo ip link add dev wg0 type wireguard
sudo ip address add dev wg0 10.1.1.100/24
$ sudo ip link set up dev wg0

$ sudo wg set wg0 listen-port 59760 private-key ./privatekey peer Q1hf3SMjpq0+ckzs71hcwSBkrMG+m6bB4xTl1s6pU1I= allowed-ips 10.1.1.0/24 endpoint x.x.x.x.:59760

📌 NAT 장비를 통해 연결된 tunnel이 NAT 장비에 의해 끊어지지 않도록 하기 위해서는 위의 명령 끝 부분에 "persistent-keepalive 25" 설정을 추가해 주어야 한다.

자, 모든 설정이 완료되었으니, ping test를 해 보도록 하자.

<Ubuntu A>
$ ping 10.1.1.254
  => OK, relay server까지 ping이 된다.
$ ping 10.1.1.100
  => OK, relay server를 경유하여 peer(Ubuntu B)에게 ping이 전달된다.

<Ubuntu B>
$ ping 10.1.1.254
  => OK,  relay server까지 ping이 된다.
$ ping 10.1.1.200
  => OK, relay server를 경유하여 peer(Ubuntu A)에게 ping이 전달된다.

이 상태에서 relay server에서 wg 명령을 실행해 본 결과는 다음과 같다.

[그림 3.2] Relay server의 wg 상태 확인

다음으로 테스트할 환경은 외부에 Server(예: AWS EC2)가 있고, 두개의 서로 다른 Ethernet 망이 존재하는 좀 더 일반적인 상황이 되겠다. (하지만 설정과 관련해서는 앞서의 내용과 크게 다를 바가 없으니) 이와 관련해서는 독자 여러분이 직접 설정 & 확인해 보시기 바란다. 😋 😂

[그림 3.3] WireGuard server를 이용한 relay VPN 구성(2)


[그림 3.4] WireGuard server를 이용한 relay VPN 구성(3)


어떤가 ? 이 정도면 WireGuard를 어디에 사용하면 좋을지 견적(?)이 나오지 않는가 ? 😎

<여기서 잠깐 !>
    => WireGuard + UDP hole punching에 관하여 ...

지금까지 3장에서 소개한 내용은 NAT device 뒷단에 위치한 WireGuard peer간에 원할한 통신을 위해 별도의 relay server를 두는 방식에 관한 것이었다. 이 방식은 어떠한 상황에서든 p2p 연결이 가능하게 만드는 장점이 있는 반면, relay server를 운용하면서 발생하는 비용과 relay server에 의한 병목(peer가 많아질 수록 심해짐)이 발생할 수 있다는 단점도 가지고 있다. 따라서 이러한 문제를 해결하기 위해 Hole punching 개념이 등장하게 되는데, 아래에 이와 관련한 재밌는 글이 하나 있어 소개해 본다.


(결론부터 얘기하자면) 위에서 제시하는 방법은 흥미를 유발(DNS를 사용하여 peer의 endpoint 정보를 알아냄)하기는 하나, 2개의 peer 중 한쪽이 Full Cone NAT인 상황에서나 가능한 방법이라고 말할 수 있겠다. 즉, 양쪽이 Symmetric NAT나 Port Restricted Cone NAT으로 구성된 경우에는 통하지 않는다.
___________________________________________________


4. WireGuard VPN Kernel Code 분석
이번 장에서는 WireGuard kernel code를 대략적으로 분석해 보고자 한다.

<분석 Point>
1) 주요 data structure를 살펴 본다.
2) 주요 코드의 흐름을 파악한다.
    - 초기화 부분
    - 패킷 수신 및 복호화
    - 패킷 송신 및 암호화
3) 기타 hash table(public key, Index 기반) 관련 코드를 분석해 본다.
4) noise IK code가 1장에서 설명한 대로 되어 있는지 확인해 본다.
______________

📌 정말 대략적으로 분석하였다. 코드 분석 후 전체 구조를 한눈에 볼 수 있는 그럴 듯한 몇 장의 그림을 만들어야 하는데... 좀 귀찮다^^... 😂

WireGuard kernel code는 아래 보이는 내용이 전부이다. 코드량은 많지 않지만 정말로 군더더기 하나 없이 잘 구현되어 있다. 왜 Torvalds 형님이 그렇게 극찬을 했는지 이해가 된다.

[그림 4.1] Wireguard kernel code 목록

그럼, 먼저 주요 파일의 용도(역할)를 간단히 정리하고 넘어가기로 하자.
____________________________________________
allowedips.[ch] 
  • allowed ips 관련 코드
cookie.[ch] 
  • cookie 관련 코드
device.[ch]
  • net_device 관련 코드 - wg_open/wg_stop/wg_xmit/wg_setup/wg_newlink ...
main.c
  • main routine - mod_init/mod_exit
netlink.[ch]
  • userspace tool인 wg와의 netlink socket 통신 코드, device(net_device) 자신 및 peer에 대한 설정
noise.[ch]
  • wireguard key handshaking protocol code(1장에서 설명한 내용)
peer.[ch]
  • peer 생성/삭제 관련 코드
peerlookup.[ch]
  • 2개의 hash table(public key 기반 hash table, index 기반 hash table)에 대한 operation(alloc, add, remove, lookup) 정의
queueing.[ch]
  • (중간 중간에) packet을 담아두는 queue(ptr_ring buffer) 관련 operation(alloc, init, free) 정의
ratelimiter.[ch]
  • rate limit 관련 코드
receive.[ch]
  • 패킷 수신 관련 코드(복호화 포함)
send.[ch]
  • 패킷 송신 관련 코드(암호화 포함)
socket.[ch]
  • send4/6( ) 함수 호출하여 패킷 송신, .encap_rcv = wg_receive 통해 udp tunnel 패킷 수신부 정의
timers.[ch]
  • wireguard에서 사용하는 각종 timer 정의

wireguard-linux-compat/src/crypto/zinc$ ls -al
  • wireguard에서 사용하는 암호/해쉬 알고리즘 위치
blake2s   : blake2s hash 함수
chacha20  : chacha20 stream cipher 암호 알고리즘
chacha20poly1305.c : chapoly AEAD 코드
curve25519 : curve25519 공개키 암호 알고리즘
poly1305 : poly1305 message authentication 알고리즘
____________________________________________

Wireguard는 아래 그림과 같이 wg tool과 wireguard kernel module로 구성되어 있다. 이 둘은 netlink socket을 통해 연결되어 있는데, 이는 netlink.c 파일에 구현되어 있다.

[그림 4.2] wg tool과 wireguard kernel module 간의 netlink socket 통신

그럼, 본격적으로 code 분석에 들어가 보자.

제일 먼저 살펴 볼 파일은 당연히 main.c인데, 이 파일 안에는 모듈 시작 함수인 mod_init( )과 종료 함수인 mod_exit( ) 만이 기술되어 있다. 이중 mod_init( ) 함수는 wg_noise_init( ), wg_device_init( ), wg_genetlink_init( ) 등 몇가지 초기화 함수를 호출하는게 전부인 것을 알 수 있다. 도대체 1장에서 설명한 handshaking은 어디에서 시작하는 것일까 ?

[그림 4.3] mod_init( )  함수 - main.c

(미리 답을 하자면) 실제 handshaking 시작에 관여하는 코드는  아래 그림 4.6(wg_newlink 함수)과 4.7(wg_peer_create 함수)이라고 볼 수 있다. 하지만, 이 코드로 곧 바로 넘어가기 전에, wireguard에서 가장 중요하게 다루고 있는 두개의 data structure 즉, struct wg_device struct wg_peer를 먼저 살펴 보는 것이 순서일 것 같다.

[그림 4.4] wg_device structure - device.h


[그림 4.5] wg_peer structure - peer.h

📌 여기서 자세히 설명하지는 않지만, 위의 두 data structure를 구성하는 각 필드의 내용을 면밀히 살펴보기 바란다. 

그럼, 이제부터 wg_newlink( ) 함수와 wg_peer_create( ) 함수를 살펴 보기로 하자.

먼저 "ip link add dev wg0 type wireguard" 명령 실행시 호출되는 wg_newlink( ) 함수는 2개의 hash table을 생성하고, handshake용 worker(function) 및 work queue를 생성한다. 또한 packet을 encrypt & decrypt하는 worker와 packet crypt workqueue를 생성하는 역할도 한다. 맨 마지막에는 netdevice를 등록하면서 함수를 종료한다.

[그림 4.6] wg_newlink( ) 함수 - device.c

📌 wireguard kernel code는 기본적으로 net_device 형태로 되어 있다. 2~3장에서 wg0 interface가 생성되었던 것을 기억하는가 ? 

한편 wg_peer_create( ) 함수는 "wg set peer ..." 명령 실행시 호출되는 함수로 peer를 hash table에 등록함과 동시에 실제 handshake 동작이 시작되도록 하는 역할을 한다. 더불어 packet을 외부로 내보내기 위한 worker를 초기화하고, decrypt된 패킷을 처리하는 napi 함수를 등록하는 일도 수행한다.

[그림 4.7] wg_peer_create( ) 함수 - netlink.c => peer.c

Handshake를 초기화하고 시작하는 코드를 살펴 보았으니, 다음 차례는 packet이 어떻게 transmit or receive되는지를 확인해 보아야 할 것이다.
먼저 아래 코드(그림 4.8 ~ 4.9)는 패킷 수신부가 어떻게 동작하는지를 정리한 것이다.

암호화된 ip tunnel 패킷 수신(wg_receive) -> tunnel 제거 및 복호화(decrypt_packet) -> 상위로 전달(wg_packet_rx_poll)

[그림 4.8] wg_receive( ) 함수 - socket.c => receive.c

[그림 4.9] wg_packet_decrypt_worker( ) 함수 - receive.c

다음으로 패킷 송신부(그림 4.10 ~ 12)를 살펴 보면 다음과 같다.

wg_xmit( ) -> ptr_ring buffer -> encrypt_packet( ) -> send4( )

[그림 4.10] wg_xmit( ) 함수 - device.c

[그림 4.11] wg_packet_encrypt_worker( ) 함수 - device.c => send.c


[그림 4.12] wg_packet_tx_worker( ) 함수 - send.c

지금까지 살펴본 그림 4.6 ~ 4.12의 과정을 handshake message tx/rx와 실제 data message tx/rx의 관점에서 다시 요약해 보면 다음과 같다.

[그림 4.13] handshake message tx flow

[그림 4.14] handshake message rx flow

[그림 4.15] data message tx flow

[그림 4.16] data message rx flow

📌 위의 tx/rx flow 내용 중 대괄호([ ])로 표시된 부분은 worker function에 해당한다.

Wireguard는 public key 기반으로 운용되는 hash table과 index 기반으로 운용되는 hash table 두개를 가지고 있다. hash table의 operation과 관련해서는 peerlookup.c 파일에 정의되어 있는데, 이중 아래 두개의 함수는 각각의 table로 부터 원하는 entry를 찾는데 사용하는 lookup 함수를 보여준다. 언제 public key로 lookup을 하고, 언제 index를 가지고 lookup을 하는지를 확인(이는 다른 파일에서 호출함)해 보면 이 두함수가 왜 필요한지를 바로 알 수 있을 것이다.

[그림 4.17] wg_pubkey_hashtable_lookup( ) 함수 - peerlookup.c

[그림 4.18] wg_index_hashtable_lookup( ) 함수 - peerlookup.c

📌 wireguard는 망 변경으로 peer의 ip 주소가 변경되더라도 tunnel이 유지되는 특징이 있다. 이것이 가능한 이유는 index 기반으로 peer hash table이 운용되기 때문이다. 1장에서 Receiver index가 message format에 있었다는 사실을 상기해 보라. 이 Receiver index가 index hash table에서 사용되는 index 값이다. 

이상으로 (만족스럽지는 못하지만) 아주 간략하게 나마 WireGuard kernel code를 분석해 보았다. 코드량이 많지 않으니, 부족한 부분은 독자 여러분이 직접 분석해 보시기 바란다.

최근(2023년 2월)에 wireguard kernel code를 좀 더 분석해 보았다. 😎



5. WireGuard VPN 활용 예(Use Cases)
이렇게 좋은 WireGuard VPN을 (일반적인 VPN 용도 말고) 어디에 활용하면 좋을까 ?

1.1) 이동중인 차량 내부에 설치한 IP Camera가 보내온 실시간 영상을 원거리에서 안전하게 보고 싶다면 ?

[그림 5.1] Video Surveillance(왼쪽 노란 box <=> 오른쪽 빨간 box)

📌 달리는 차를 예로 들었지만, 가정에 설치한 IP camera를 외부에서 Smart Phone으로 확인해 보는 것도 가능하다.


1.2) 산업 현장의 장비를 원격으로 제어하고 싶다면 ?


[그림 5.2] Industrial IoT 통신 보안(왼쪽의 검정 box <=>  cloud, cloud <=> notebook/phone)

📌 산업 현장의 장치는 RS485, CAN, Ethernet, Wi-Fi, BLE, ZigBee 등 다양한 connectivity 조건을 요구한다.


1.3) 이동중인 차량에서 보내온 데이타(Coldchain)를 안전하게 받아 분석하고 싶다면 ?


[그림 5.3] Coldchain 환경(왼쪽 흰색 box <=> cloud, cloud <=> notebook/phone)

📌 WireGuard는 IP 주소를 기반으로 tunnel을 유지하는 것이 아니라, Public key를 기반으로 peer를 인식하기 때문에 LTE 기지국이 바뀌더라도 tunnel이 끊기지 않고 안정적으로 유지된다.


1.4) 방화벽/공유기에 영향을 받지 않고 Online Game을 자유롭게 하고 싶다면 ?


[그림 5.4] P2P 통신 - online gaming

📌 이러한 상황은 Game말고도 다양하게 존재한다.


1.5) POS 결제 정보를 안전하게 전달하고 싶다면 ?


[그림 5.5] POS 결제 정보 보호

📌 물론 POS의 경우 이미 자체 방식으로 결제 데이타를 암호화해서 전달하고 있는 것으로 알고 있다. 하지만, WireGuard를 이용한다면 한단계 더 안전한 통신 보안이 가능할 것으로 믿는다.


1.6) 달리는 기차 위에서 안정적으로 사내망에 접속하고 싶다면 ?


[그림 5.6] LTE 기지국이 변경되어도 Tunnel이 유지(왼쪽 흰색 box <=> 오른쪽 검정 box)

📌 WireGuard는 IP 주소를 기반으로 tunnel을 유지하는 것이 아니라, Public key를 기반으로 peer를 인식하기 때문에 LTE 기지국이 바뀌더라도 tunnel이 끊기지 않고 안정적으로 유지된다.
__________________________________

이상과 같이 WireGuard VPN의 가능성은 실로 무궁무진하다고 말할 수 있겠다. 어떤가 ~ WireGuard를 사용할 준비가 되었는가 ? 😀




6. References
[1] https://www.wireguard.com/papers/wireguard.pdf
[2] WireGuard - Fast, Modern, Secure VPN Tunnel, Jason A. Donenfeld
[3] Master’s Thesis - Analysis of the WireGuard protocol, Peter Wu
[4] Tiny WireGuard Tweak, Jacob Appelbaum, Chloe Martindale, and Peter Wu
[5] 스토리로 이해하는 암호화 알고리즘, 김수민, 로드북


SlowBoot