JWK 라이브러리를 만들어 보자! #9

김성현·2022년 1월 6일
0
post-thumbnail

9편 : 아... 제일 힘든 시간... 디버깅과 단위테스팅, 그 2부

오늘은 내가 단위테스팅을 도입하면서 찾아낸 에러들(사실 대단한 에러보다는 실수가 많다.)의 목록을 공유하고자 한다.

오늘 하루는 decode 관련 테스팅의 작성과 테스팅중 찾은 에러들을 찾은 방법과 고친 내용을 기록할 것이다.

사실 크게 잘못된 부분이 없어 간단히 끝낼 수 있었다.

다만 decode관련 파일의 코드가 다른 파일에 비해 더 길어 다른 부분보다 해당 부분의 파싱에서 고생을 좀 했다.

decode.go 전체를 단위 테스팅으로 마치고 나니 60%정도의 전체 코드 커버리지를 달성했는데 잡다한걸 제외하면 대략 45%정도의 코드가 decode.go에 몰려 있는거 같다는 생각이 들었다.

사실 자잘한 에러 메시지 변경이나 정말 사소한 것까지 다 쓰면 너무 늘어질 것 같아서 적당히 기억나는 부분만 정리하도록 하겠다.

DecodeKey 관련 오류

IgnorePrecomputed, AllowUnknownField 충돌

해당 에러는 사실 실수에 더 가깝다.
JWK의 RSA 개인키 항목에는 dq, dp, qi 라는 값들이 있는데 해당 값들은 n, e, d, p, q 이렇게 5개의 정수로부터 유도 가능한 값들이다.
해당 값들은 RSA 연산 중 작업량이 많고 반복적으로 사용되는 값들을 미리 연산해 놓은 값이기 때문에 해당 값들은 사실 없어도 RSA 키를 복구하는데 큰 어려움이 없다.

그래서 나는 DecodeKey 함수에 옵션으로 해당 값들을 무시하고 디코딩 중 해당 값들을 다시 계산하게 강제하는 IgnorePrecomputed 옵션을 만들었다.

그런데 내가 실수한게 이렇게 IgnorePrecomputeddq, dp, qi를 무시했으면 해당 값들을 목록에서 지워 줬어야 했다.

왜냐하면 디코딩 중 처리하지 못한 값들이 남아있는데 더이상 번역할 방법이 없다면
AllowUnknownField에 의해 해당 값들이 번역되지 못한 필드 에러를 유발하기 때문이였다.

그래서 간단하게 IgnorePrecomputed가 설정된 경우 해당 필드는 존재하던, 존재하지 않던 강제로 값들을 목록에서 지우도록 설정했다.

정말 간단한 실수.

너무 모호한 에러 메시지를 수정


해당 부분은 원래 ErrCauseOption 에러를 일으키는 부분이였다.
ErrCauseOption옵션은 에러가 설정값에 의해 일어났다는 의미로 이 경우에는 AllowUnknownField값이 false 인 경우 알려지지 않은 필드값이 주어져서 생기는 문제였다.

다만 이 ErrCauseOption은 그 외의 수많은 설정에 의해서 일어날 수 있는 너무 범용적인 에러명이라는 생각이 들어서 ErrCauseDisallowUnkwownField라는 좀 더 한정적이고 명확한 에러를 이용하기로 수정하였다.

error 메시지를 잘못 쓴 실수

이녀석은 진짜로 사소했다.
ErrCauseRSAPrivateKey는 해당 에러가 RSA 개인키 과정에서 문제가 생겼음을 의미하는 에러인데 해당 에러 메시지에 public 이라고 적어 놨었다.

그래서 private로 고쳤다.

error를 잘못 입력함

이거도 역시 사소한 녀석이였다.

해당 라인은 decodeECPri라는 EC 개인키를 복호하는 과정에서 에러를 일으킬때 ErrCauseECPrivateKey로 입력해야 하는데 ErrCauseECPublicKey로 잘못 입력했다.

타원곡선암호의 인코딩, 디코딩 과정에서 에러를 발견

우선 타원곡선 암호는 P-224, P-384, P-521 이렇게 유명한 3개의 타원이 있다. (더있는걸로 알고 있기는 한데 써 본적도, 아니 애시당초 openssl 옵션으로 있는 걸 제외하면 실 사용사례는 본 적도 없다.)
이때 각 곡선은 x, y, d 이렇게 bigint값을 가지는데 P-224인 경우 각 bigint값은 28바이트 크기여야 한다.
마찬가지로 P-384는 48, P-521는 66이여야 한다.

타원곡선에 따른 bigint 크기의 계산법은 ceil(Bitsize / 8) 이다.

다만 bigint의 언어별 구현에 따라서 신경써야 할 점이 있는데, 만약 N바이트 크기의 bigint 에서 맨 앞의 바이트들이 0으로 차 있는 경우 해당 바이트들을 지워버릴 수도 있다는 것이다.

RFC7518(JWA)#3.4 에 따르면 필요한 경우 바이트 개수를 맞춰주기 위해 0으로 패딩을 넣어야 한다는 말이 있었다.

사실 RFC를 읽으면서 해당 부분에 주의하라라는 말을 본 적은 있지만
괜찮겠지~ 하고 넘겼다가 피봤다.
역시 귀찮음은 성공의 적이다.

나는 이 부분을 간과하고 그냥 만들었다가 디코딩 중에 문제가 생겨서 해당 부분을 부랴부랴 수정했다.

사실 혹시나 싶어서 해당 부분에 문제가 생길법한 위치를 기억해 두고 있었다. 그래서 금방 고칠 수 있었다.

해당 부분은 약간의 디코딩과 인코딩 과정 중 코드 변경과 새로운 private 함수인 safeECByte라는 바이트로부터 bitsize에 안전한 base64 URL, no padding 문자열을 만들어주는 함수를 만들어 줌으로서 해결했다.

해당 부분은 사실 디코딩보다는 인코딩 과정에서 생길 문제이지만 디코딩 테스트 중 문제가 될 수 있다는 사실을 깨달았기에 여기서 썼다.

Unknown Key Type의 kty 설정 에러

이거 역시 간단한 에러다.

알려지지 않은 kty을 분석할때 jwk.UnknownKeyjwk.UnknownKey.KeyType에 값을 깜박하고 넣지 않았다.

이 코드로 해결했다.

URL 타입의 패러미터 에러 메시지 생성 실수

또 간단한 실수다.
x5u같은 필드에서는 URL 타입의 string을 받는데 만약 잘못된 URL이 넘어올 때 에러 메시지에 ErrInvalidURL 정보를 삽입해주는걸 까먹었다.


결국 현재시각 2022-01-07 00:57 decode.go 수백줄 가량의 모든 라인을 코드 커버리지로 다 채웠다.

밑에 coverage: 58.2%라고 적힌 것은 모든 소스코드 중 58.2%라는 뜻이다.
decode.go에 한에서는 100% 코드 커버리지를 달성했다.

정말... 정말... 테스팅은 힘들다.
테스팅에 비하면 만드는 건 일도 아니다.

profile
수준 높은 기술 포스트를 위해서 노력중...

0개의 댓글