From 54303c0452f342394dc3ed4bb27d744b66882080 Mon Sep 17 00:00:00 2001 From: svillegas-cdd Date: Tue, 1 Oct 2024 17:19:46 -0300 Subject: [PATCH] fix: Update cleaning regex to match RUTs with non-numeric digits - The regular expression in `get_subject_rut_from_certificate_pfx` caused the removal of all letters, including the letter K, from the RUTs. - It has been updated to a regex that includes the letter K in certain RUTs. - Added tests for RUTs that end with a `K`. - Added a guide named `howto.md`, which explains how to create a valid `.der` certificate. Based on the guide in `FD-CL-Data` [See](https://github.com/cordada/fd-cl-data/blob/ba85835e81c857acbcc0498751c21335591b17b5/test_data/apps/sii_auth/certificate_pfx/howto.md). Ref: https://github.com/cordada/lib-cl-sii-python/issues/700 Ref: https://app.shortcut.com/cordada/story/9821 --- src/cl_sii/rut/crypto_utils.py | 10 ++- .../sii-crypto/TEST-DTE-13185095-K.der | Bin 0 -> 1092 bytes .../sii-crypto/TEST-DTE-13185095-K.pem | 28 +++++++ .../TEST-DTE-WITH-ID-BUT-NO-RUT.der | Bin 0 -> 866 bytes .../TEST-DTE-WITH-ID-BUT-NO-RUT.pem | 28 +++++++ src/tests/test_data/sii-crypto/howto.md | 71 ++++++++++++++++++ src/tests/test_rut_crypto_utils.py | 40 ++++++++++ 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/tests/test_data/sii-crypto/TEST-DTE-13185095-K.der create mode 100644 src/tests/test_data/sii-crypto/TEST-DTE-13185095-K.pem create mode 100644 src/tests/test_data/sii-crypto/TEST-DTE-WITH-ID-BUT-NO-RUT.der create mode 100644 src/tests/test_data/sii-crypto/TEST-DTE-WITH-ID-BUT-NO-RUT.pem create mode 100644 src/tests/test_data/sii-crypto/howto.md diff --git a/src/cl_sii/rut/crypto_utils.py b/src/cl_sii/rut/crypto_utils.py index df98a9c7..1f22efcc 100644 --- a/src/cl_sii/rut/crypto_utils.py +++ b/src/cl_sii/rut/crypto_utils.py @@ -51,6 +51,14 @@ def get_subject_rut_from_certificate_pfx(pfx_file_bytes: bytes, password: Option raise Exception(f'len(results) == {len(results)}') subject_rut_raw: bytes = results[0] - subject_rut = re.sub(r'[^0-9-]', '', subject_rut_raw.decode('utf-8')) + subject_rut_str = subject_rut_raw.decode('utf-8') + + # Regex to extract Chilean RUT formatted string + rut_match = re.search(r'\b\d{1,8}-[0-9Kk]\b', subject_rut_str) + + if not rut_match: + raise Exception('RUT format not found in certificate') + + subject_rut = rut_match.group(0) return Rut(subject_rut) diff --git a/src/tests/test_data/sii-crypto/TEST-DTE-13185095-K.der b/src/tests/test_data/sii-crypto/TEST-DTE-13185095-K.der new file mode 100644 index 0000000000000000000000000000000000000000..b279fc34ae577fafb46e94d2821039974ef760cb GIT binary patch literal 1092 zcmXqLVsS8NV%Au|%*4pVBw}$drp;7FncYC}|3L||-cvAPwSh@Q4JZrf25oDfp(A6y+D>=VX>7<|P^m8t{Xp z*?BmE6Z1+k6VvkzB@M(uLR>roj>);H3eNdO1^GpZB|yD~Dh5g*DQ+HVu#{^>K~8>g zW?8C2NNQeQX0d`(Vopvm%uHq;4zK)-JO!8hR6_*=Ik+=98N~#%fD(Es`KbsX-cSUbw7Mo|L3Chc^6b~{uazQJ7>f1yo(oqeVFR8qWbEmQ!*c~{F$<8?m6A- z9@o!&)cyPB;^e=Yw*<<2&wqFRw=imnlZ(Rk>xVM8PI;^7mEw9chGYAyXIJ+G=g!(R0k2zi@5{6$(1TBcxmsH*aChl9WE~4do$EkJxw^Hr-pf*4DCmk*4IU zz{zW_o~z>GvH$h?<-WczhpIR)Jk!zOxNq~$FHF|+==)p6#sB7+J&KrUTD|)4zm#v? z-Ev}I`tv*-;}@6PP7nLjtEqgGrMa-uU!&jZ+W#|qe&6)9*vj1Z!R+~tviofkzjq#) zVY5s1IlC^?QYL0b2FAs11}+B5z@#NB$RcAPwLpT6Lz|6}h4COG;{qNYE<GZ}&B(y*a%|r(`;8CI z?6i$=P(AkS%eE^0{D?Jr@*%SXN~442?@P_#IqM!!F%sODeI9Q2i`%3n+|n>fCQ^Dw)vv!* z4{8_Ohz~lS@xJx3{QTw}p6`YMH>}#pqg>*yX{liGW^9E|9|A@JQa7flkX4L-+xa&UVqzH9m+EG%aVABXp^~9 zzC7)fFsa|QU3vE`F^yTWAJ6&nU1*skoRW`^` z#=3W{@S|6(t?A#biz82DJ=cx9rQMQzp-tOZhv$VA%Z}3h?j9Sg51-dQlqqn-_I96J z#)XP!o0y7n(^&-DA1_OEUEdjc+wsss?|sWY=V-8QeE9X^>-z;)zB#7kFDQG(XJKyHxn$oRpuP)x0 za^=v1%oWV_$1W&u>fCwn;^SRaiybQs9yojUUg+)NyQfm)xOc;r*6QEo@9r;YaF@Gw z?%IOz(r=oaQjHgWVmtvw!xFH(%{1sYopPb|ZzokokrBKC{^@Q`0hK z;+E*$eKuuU1ACr-TvFbuZGX+4b?Tc>Kjj~nxAJhg- + +## Parameters + +- File to send the key to (`key_file_name`) +- Output file (`certificate_file_name`) +- Number of days cert is valid for (`number_of_days`) + +```sh +key_file_name='key.pem' +certificate_file_name='certificate.der' +number_of_days=365 +subject_rut_oid='1.3.6.1.4.1.8321.1' +subject_rut='13185095-K' +``` + +## Steps + +### Generate the private key and public certificate + +```sh +openssl req \ + -newkey rsa:2048 \ + -nodes \ + -keyout "$key_file_name" \ + -x509 \ + -days "$number_of_days" \ + -outform DER \ + -out "$certificate_file_name" \ + -extensions san -config <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[san]\nsubjectAltName=otherName:$subject_rut_oid;UTF8:$subject_rut")) +``` + +```text +Generating a RSA private key +....................................................................................+++++ +....................................................+++++ +writing new private key to 'key.pem' +----- +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:CL +State or Province Name (full name) [Some-State]:Region Metropolitana +Locality Name (eg, city) []:Santiago +Organization Name (eg, company) [Internet Widgits Pty Ltd]:Acme Corporation +Organizational Unit Name (eg, section) []:Acme Explosive Tennis Balls +Common Name (e.g. server FQDN or YOUR name) []:John Doe +Email Address []:john.doe@acme.com +``` + +### Output + +#### Review the created certificate + +```sh +openssl x509 \ + -inform DER \ + -in "$certificate_file_name" \ + -text -noout +``` + +This will generate a self-signed certificate in DER format and allow you to review its contents diff --git a/src/tests/test_rut_crypto_utils.py b/src/tests/test_rut_crypto_utils.py index c485d53b..ab16eafd 100644 --- a/src/tests/test_rut_crypto_utils.py +++ b/src/tests/test_rut_crypto_utils.py @@ -32,6 +32,46 @@ def test_get_subject_rut_from_certificate_pfx_ok(self) -> None: self.assertIsInstance(subject_rut, rut.Rut) self.assertEqual(subject_rut, rut.Rut('13185095-6')) + def test_get_subject_rut_from_certificate_pfx_ok_with_rut_that_ends_with_K(self) -> None: + cert_der_bytes = utils.read_test_file_bytes('test_data/sii-crypto/TEST-DTE-13185095-K.der') + + x509_cert = load_der_x509_cert(cert_der_bytes) + + with patch.object( + pkcs12, + 'load_key_and_certificates', + Mock(return_value=(None, x509_cert, None)), + ): + pfx_file_bytes = b'hello' + password = 'fake_password' + subject_rut = get_subject_rut_from_certificate_pfx( + pfx_file_bytes=pfx_file_bytes, + password=password, + ) + self.assertIsInstance(subject_rut, rut.Rut) + self.assertEqual(subject_rut, rut.Rut('13185095-K')) + + def test_get_subject_rut_from_certificate_pfx_not_matching_rut_format(self) -> None: + cert_der_bytes = utils.read_test_file_bytes( + 'test_data/sii-crypto/TEST-DTE-WITH-ID-BUT-NO-RUT.der', + ) + + x509_cert = load_der_x509_cert(cert_der_bytes) + + with patch.object( + pkcs12, + 'load_key_and_certificates', + Mock(return_value=(None, x509_cert, None)), + ): + pfx_file_bytes = b'hello' + password = 'fake_password' + with self.assertRaises(Exception) as cm: + get_subject_rut_from_certificate_pfx( + pfx_file_bytes=pfx_file_bytes, + password=password, + ) + self.assertEqual(cm.exception.args, ('RUT format not found in certificate',)) + def test_get_subject_rut_from_certificate_pfx_fails_if_rut_info_is_missing(self) -> None: cert_der_bytes = utils.read_test_file_bytes( 'test_data/crypto/wildcard-google-com-cert.der',