├── .gitignore
├── anonymous-prod-key.pem
├── anonymous-prod-cert.pem
├── README.md
├── handysignatur.py
├── a-sign-SSL-07.pem
└── demo.py
/.gitignore:
--------------------------------------------------------------------------------
1 | digitales-amt-binding-uuid.txt
2 | digitales-amt-key.pem
3 | digitales-amt-csr.pem
4 | digitales-amt-cert.pem
5 | eAusweise-key.pem
6 | eAusweise-csr.pem
7 | eAusweise-cert.pem
8 |
--------------------------------------------------------------------------------
/anonymous-prod-key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDCHLUYcHyPcpSRERZKhiaWJ
3 | l6A1iK9K4zpruHkD2Bx4FXcOI6S5fZoSj+jbdiuq7XY=
4 | -----END PRIVATE KEY-----
5 |
--------------------------------------------------------------------------------
/anonymous-prod-cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC7DCCAnKgAwIBAgIIClg/s9EPLdowCgYIKoZIzj0EAwMwZjELMAkGA1UEBhMC
3 | QVQxITAfBgNVBAoTGEJ1bmRlc3JlY2hlbnplbnRydW0gR21iSDEPMA0GA1UECxMG
4 | QlJaIENBMSMwIQYDVQQDExpCUlotU2VydmljZS1MZXZlbDItQ0EtMy0wMTAeFw0y
5 | MjA1MDIxMTU4MjVaFw0yNzA1MDMxMTU4MjVaMFgxCzAJBgNVBAYTAkFUMSEwHwYD
6 | VQQKExhCdW5kZXNyZWNoZW56ZW50cnVtIEdtYkgxJjAkBgNVBAMTHUJSWi5UQy5B
7 | V1AtUFJPRC1Bbm9ueW1vdXMtMDAxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEqx1R
8 | XHC1U5gx5Mw+6h2CHihSFPJHBBAozoC8lpbWCETLtCFrYcq2yzo8VhtlgQf8gNDV
9 | hBMWXf9JGs57XLns9JR+WAX0O4eJNzZUSFtLRWO6zVzmeEhYp1J3uwouMW5No4H6
10 | MIH3MDsGCCsGAQUFBwEBBC8wLTArBggrBgEFBQcwAYYfaHR0cDovL2JyemNhb2Nz
11 | cC5icnouZ3YuYXQvb2NzcDAdBgNVHQ4EFgQUqYfjpx5JwJM3odTI2Lajb2Hos9Mw
12 | HwYDVR0jBBgwFoAUs5RX9j78/sydpDZitufW6siCH+EwGAYDVR0gBBEwDzANBgsr
13 | BgEEAbM4j10BATA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vYnJ6Y2FjcmwuYnJ6
14 | Lmd2LmF0L2NybC8zc2wyLjAxMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK
15 | BggrBgEFBQcDAjAKBggqhkjOPQQDAwNoADBlAjB9kByORTJ7jJukHVuk4iU2lCUM
16 | qGeRb5QlGGSAIjz5A9WM/Lr0Eg6r/R8szIxY4PMCMQDo/BwWEDatgBr9jJcz//7D
17 | /Qbim8z+40gfDn82MqdvHy119eQm0NGmeTBZDX6H2sc=
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Reverse-Analysis of the Austrian "ID Wallet" (eAusweise / Digitales Amt)
2 |
3 | This is an ongoing effort for documenting and understanding the Austrian "ID Wallet" apps:
4 | - [*Digitales Amt*](https://play.google.com/store/apps/details?id=at.gv.oe.app&hl=en&gl=US)
5 | - [*eAusweise*](https://play.google.com/store/apps/details?id=at.gv.oe.awp.eausweise&hl=de&gl=US)
6 |
7 | First, the user logs in at the *Digitales Amt* app. Then, the *eAusweise* app is set-up, which sends an intent to the first app.
8 |
9 | Based on a reverse-analysis, the `./demo.py` script re-implements this procedure. After a successfull run, it provides you with **TLS Client Certificates** (and keys) for use with:
10 | - The *Digitales Amt* backend API endpoints
11 | - The *eAusweise* backend API endpoints
12 |
13 | You will need some eIDAS compliant identity token during the process, such as the german national identity card.
14 |
15 | ## Handysignatur
16 |
17 | Need a Handysignatur test account? [Here](https://www.a-trust.at/testidentitaetenmanagement) you can get a test account on the production infrastructure using an eIDAS identity token (such as the german ID card). On an iPhone, I logged into the *Digitales Amt* app using the "EU-Login" and then accessed the test identity management on my phone.
18 |
--------------------------------------------------------------------------------
/handysignatur.py:
--------------------------------------------------------------------------------
1 | #!/bin/env python
2 |
3 | import requests
4 | import random
5 | from lxml import etree as ET
6 | from base64 import b64decode, b64encode
7 |
8 | SALT = b'anugBygKOMdtx9AJbhrN5b1sObQ5AyISZbKf1hxiPA73IYFc9I1QggKwgVaO48vsAM75MLkXPm75gNaZxUdDeWHI1aYYOdJxEkE55DgqjzMSv44GgvMIDk6NWy1CSHyHURSRaQjMn6NdaXpa0xdlfdrxOcQx3ajHNqqCHUPJV7BkOtHJaV2rPmhWzEOlD9yFCUWRDenT1mwumnuxbisDqOWtlPWQ293zjWZEV0Wm0eXA1oC7Cz6ZkV0c2f4QIhasWGbm3H1p0Q308J70G666JkaZ2hkmI4AQeAjcnWCuIlSIqGBg5aEQuyjs3jeb5EPtT2xXUVpVew02IIF8X9OePuO5VOyN8WIPtKN1Wxt6qYznjMkKY6qVAwHt9FsKTBo5AHbv02jARUFWzIf5WR3OWL61maEo6rNOl1OjUA0UlSNgJHDNaLwrFZD3AguMoGB7X8hBDCYfapywZ365c3hSZcieQ9QGQWdYXJYWEk9I2ZXf7729l2ZKGbqfiwnjSRNw7ofZEd8I0WAS2reVz34MLAK5oEE0LINjphBWVged2XfEXY95orEfQIIBWdrogtqjOLIhtm6wYMJR27Y8jmADlGOzbC7egGbv5frNyQ36pmgaRwqZgYlZSPAjo8mbZuKUsrRGBQACIv99odb8w4idBawvMs0RNGlH2MEcsgXxEkLf7HNyFNRjdNt6tn2cBCsrVl9YiYZfbcSciMPdor03Y0uv9mZs2nCPcTi4rp4tc4WyZB3vY9vvHRTFNqrfhC7awHmMJNZXPBz3oQmZ02ZYtZhXQ8K6AFb2oLqks8M06MVvlaYTbB9RVQbxWU4yG7N4aiyl02A5D78v6GYQuQJGf2E4ZQk9abaHujqDpWWCfBAhKjvHXnlpPa6V7Llj5fkDQJgA5DecBVk3UVn9Yn6ZAg57VUyy0JgxTHKcnUnEDEOUiBkBAHD95cRaOA4YdvOrDHEWcwBZKlutMmjWFhUW09M9wtJp9dr6CimpkBlgHzYVwKB6hCUUwh6pQyCF'
9 | ATRUST_VERSION_NAME = "2.9.11"
10 |
11 | def minifyXML(xml):
12 | return ET.tostring(ET.XML(xml, ET.XMLParser(remove_blank_text=True))).decode()
13 |
14 | headers = {
15 | 'Host': 'api.a-trust.at',
16 | 'User-Agent': 'Handy-Signatur-App/2.9.11',
17 | 'Connection': 'Keep-Alive',
18 | }
19 |
20 | command = """
21 |
22 | 0
23 | 0
24 | emulator64_x86_64_arm64
25 | 857f87de-81a6-44f4-8e70-03d7b1911cfa
26 | """
27 |
28 | nonce = random.randint(0, 2**64)
29 |
30 | data = {
31 | 'command': minifyXML(command),
32 | 'nonce': nonce.to_bytes(8, 'little').hex().upper(),
33 | }
34 |
35 | response = requests.post('https://api.a-trust.at/WebTanApp/AppAktivierungStart.ashx', headers=headers, data=data, verify='a-sign-SSL-07.pem')
36 |
37 | resp = b64decode(ET.XML(response.text).xpath("/tanapp/b64Response/text()")[0])
38 | data = ET.XML(resp.decode())
39 |
40 | session_id = data.xpath("/tanapp/sessionid/text()")[0]
41 | app_token = data.xpath("/tanapp/apptoken/text()")[0]
42 | g = int(data.xpath("/tanapp/response/Response/G/text()")[0])
43 | p = int(data.xpath("/tanapp/response/Response/P/text()")[0])
44 | pubkeyA = int(data.xpath("/tanapp/response/Response/PubKeyA/text()")[0])
45 |
46 | from cryptography.hazmat.primitives.asymmetric.dh import DHParameterNumbers, DHPublicNumbers
47 |
48 | parameter_numbers = DHParameterNumbers(p=p, g=g)
49 | params = parameter_numbers.parameters()
50 | privkey = params.generate_private_key()
51 | shared_secret = pow(pubkeyA, privkey.private_numbers().x, p)
52 | shared_secret = str(shared_secret).encode()
53 |
54 | from cryptography.hazmat.primitives import hashes
55 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
56 |
57 | kdf = PBKDF2HMAC(
58 | algorithm=hashes.SHA256(),
59 | length=32,
60 | salt=SALT,
61 | iterations=25000
62 | )
63 |
64 | encryption_key = kdf.derive(shared_secret)
65 |
66 | system_info_xml = f"""
67 |
68 | {ATRUST_VERSION_NAME}
69 | Android
70 | 31
71 | """
72 |
73 | import os
74 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
75 |
76 | iv = os.urandom(16)
77 | encryptor = Cipher(algorithms.AES(encryption_key), modes.GCM(iv)).encryptor()
78 |
79 | pubkey_b = str(privkey.public_key().public_numbers().y)
80 | mini = "\n" + minifyXML(system_info_xml.encode())
81 |
82 | enc_init_data = b64encode(
83 | encryptor.update(
84 | mini.encode()
85 | ) + encryptor.finalize() + encryptor.tag
86 | ).decode()
87 |
88 | from urllib.parse import quote_plus
89 |
90 | command = f"""
91 |
92 | 0
93 | 1
94 | { pubkey_b }
95 | { quote_plus(enc_init_data) }
96 | { quote_plus(b64encode(iv).decode()) }
97 | """
98 |
99 | nonce = random.randint(0, 2**64)
100 | postData = f"AppToken={app_token}&Command={minifyXML(command)}&nonce={random.randbytes(8).hex().upper()}"
101 |
102 | params = {
103 | 'step': '1',
104 | 'sid': session_id,
105 | }
106 |
107 | headers = {
108 | 'Host': 'api.a-trust.at',
109 | 'User-Agent': 'Handy-Signatur-App/2.9.11',
110 | 'Content-Type': 'application/x-www-form-urlencoded',
111 | 'Connection': 'Keep-Alive',
112 | # 'Accept-Encoding': 'gzip, deflate',
113 | # 'Content-Length': '1074',
114 | }
115 |
116 | response = requests.post(
117 | 'https://api.a-trust.at/WebTanApp/AppAktivierungCommand.ashx',
118 | params=params,
119 | headers=headers,
120 | data=postData,
121 | verify='a-sign-SSL-07.pem',
122 | )
123 |
124 | print(response)
125 | print(response.headers)
126 | print(response.text)
127 |
--------------------------------------------------------------------------------
/a-sign-SSL-07.pem:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 1408559753 (0x53f4ea89)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: C = AT, O = A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH, OU = A-Trust-Root-07, CN = A-Trust-Root-07
7 | Validity
8 | Not Before: May 17 11:23:22 2018 GMT
9 | Not After : Nov 19 10:23:22 2036 GMT
10 | Subject: C = AT, O = A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH, OU = A-Trust-Root-07, CN = A-Trust-Root-07
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (4096 bit)
14 | Modulus:
15 | 00:a3:07:be:5e:01:fa:25:d8:05:8f:34:7f:91:13:
16 | 28:1f:5a:b9:c3:22:40:ef:2c:40:4a:a3:50:46:5d:
17 | 61:b8:d9:38:28:bb:96:5e:3f:0d:f1:a9:94:74:a6:
18 | 6f:8d:90:4a:6a:80:fd:0d:d9:ab:2a:02:bd:70:08:
19 | e8:49:a1:ea:9b:f7:e6:13:9b:de:1f:29:00:f1:a8:
20 | b5:1e:1a:78:4e:a2:c0:d7:05:a6:b7:c7:61:53:b5:
21 | a0:85:91:b5:5a:54:3e:f7:cf:7d:fb:52:8e:12:70:
22 | 38:cc:9b:45:0c:df:89:95:a7:77:f8:e5:67:03:5b:
23 | 91:c2:01:d4:5b:0a:7b:7f:7d:58:2e:ac:0b:c3:b9:
24 | 77:3b:11:96:ee:9b:fc:e9:27:78:4b:d3:2b:d6:24:
25 | be:02:7c:a8:89:23:75:d7:1e:25:1f:5f:62:92:a1:
26 | 22:f4:7e:c9:03:49:08:91:c5:99:cd:95:32:d2:5e:
27 | 7f:9f:29:48:28:c9:91:57:42:73:fa:7a:c1:b1:dd:
28 | 02:dc:02:a7:54:02:62:f3:84:93:00:58:c7:62:57:
29 | cc:da:19:68:fb:28:50:bb:04:6d:67:4b:84:81:e3:
30 | 30:dc:7c:2f:7f:d1:2e:7a:2e:c5:62:a3:88:0a:d3:
31 | 5a:87:78:e8:f6:a7:51:2b:2d:72:83:a4:0d:8c:e4:
32 | c9:e6:3d:a5:7c:ce:c1:c3:33:86:68:35:52:59:52:
33 | 18:93:ff:62:c9:6a:53:71:b7:b3:51:7e:80:a9:b9:
34 | df:6a:fb:5d:1d:40:c6:57:b3:54:81:ba:46:4d:81:
35 | 5d:bc:d7:57:2b:ac:99:a7:92:e2:2a:9c:e6:5a:f2:
36 | 70:36:33:6b:5a:21:c7:0f:b7:c4:f2:57:00:35:ea:
37 | 74:24:94:2b:9f:06:eb:ce:ad:e3:d6:e5:e9:74:59:
38 | 50:b1:89:ef:7c:e5:5c:8a:d5:51:b5:e4:61:4f:6c:
39 | d3:24:c9:e4:c2:bb:87:28:ae:b9:e6:9b:e3:4a:68:
40 | 4e:a3:96:59:65:46:5d:00:79:70:60:5d:68:4e:a3:
41 | 9c:9d:90:19:a8:6e:53:06:98:a6:79:54:a7:12:62:
42 | 76:a1:0e:3d:d6:6b:b7:4d:2e:aa:be:df:9d:a9:56:
43 | 1f:1d:47:63:ac:be:00:b2:c4:4c:4f:d6:5d:b7:b2:
44 | eb:89:88:f6:72:65:5e:e1:f4:f1:ed:0c:66:a5:28:
45 | 51:67:7d:ac:a4:4b:aa:5a:45:9c:40:27:48:c6:f0:
46 | 39:1c:3b:85:53:bb:f1:b0:82:f0:06:4f:08:56:4c:
47 | ac:f1:49:62:2e:d1:19:02:98:44:88:b6:9a:35:33:
48 | f2:f7:8d:ce:bc:e3:3a:57:09:9f:0e:7d:f0:d8:21:
49 | 13:b5:61
50 | Exponent: 65537 (0x10001)
51 | X509v3 extensions:
52 | X509v3 Subject Key Identifier:
53 | 44:C0:11:AD:53:27:87:F4
54 | X509v3 Key Usage: critical
55 | Certificate Sign, CRL Sign
56 | X509v3 Basic Constraints: critical
57 | CA:TRUE
58 | Signature Algorithm: sha256WithRSAEncryption
59 | Signature Value:
60 | 9f:47:48:ea:f2:bb:b4:56:b9:c7:3d:30:40:d4:d3:a3:95:96:
61 | 18:f4:11:bf:84:b8:64:16:82:0a:a6:fa:84:95:fb:bc:9c:fb:
62 | a4:7c:38:ad:4d:54:81:16:34:60:d7:ef:50:99:ff:88:d2:2f:
63 | cf:a4:10:c8:87:02:60:19:79:1f:75:da:e0:83:71:73:ec:c9:
64 | 80:92:21:8c:40:d5:e9:e7:96:02:54:08:9a:ba:42:6f:bf:a5:
65 | 72:64:f9:ea:ff:16:d6:81:ef:96:ce:64:f2:8f:de:5c:15:8f:
66 | 9f:18:63:86:5e:a5:c5:59:a7:ed:c6:08:60:64:fc:98:95:7a:
67 | 01:f7:0d:3d:85:ca:32:eb:6d:21:b5:50:dc:96:8a:5a:41:73:
68 | d5:49:6e:a0:a5:38:58:b5:7e:c8:19:f4:fd:05:cb:fe:7f:c8:
69 | d4:4a:11:8d:07:3f:47:6b:48:0f:db:06:c4:ff:63:24:7c:f0:
70 | d7:24:7b:92:e5:e8:93:7d:9c:f9:5d:30:8e:eb:92:c3:30:9a:
71 | 60:61:e0:1e:b5:c4:95:21:f5:53:d6:d3:98:1d:df:79:a3:6b:
72 | 80:6d:97:a7:32:fd:c2:4a:69:fb:4c:9e:ab:3f:34:ad:76:fb:
73 | b0:9a:3d:86:3d:ef:c0:a6:ec:17:26:99:84:8b:28:b4:d1:c1:
74 | f3:a2:2c:01:a1:78:be:1a:b4:77:d5:98:cd:e3:72:f5:64:22:
75 | ba:5c:89:56:44:8d:3f:3d:eb:26:82:74:bb:fa:9e:f1:15:32:
76 | 16:6c:13:97:a0:9c:a8:bd:c8:0f:75:a6:43:2d:70:6e:d0:6c:
77 | 2d:c3:dd:57:67:59:6a:aa:fd:9f:f3:d2:05:9b:0d:da:af:e9:
78 | e7:23:31:91:e6:aa:59:bc:e8:71:ce:e8:36:99:d4:db:f7:9d:
79 | a2:6d:9a:92:f0:7d:86:62:39:4a:98:37:59:c4:4d:95:af:d6:
80 | 7b:75:d0:77:8f:81:a9:ab:b5:6c:1c:3a:b0:50:0d:03:d3:23:
81 | 8d:86:13:2e:86:d1:81:bb:dc:75:3c:c7:b4:4e:97:75:ec:a7:
82 | b0:44:33:15:21:ae:b7:13:9d:36:a6:4c:29:a6:99:a8:5e:a5:
83 | 14:1c:43:12:05:f6:d6:c2:d7:2a:4f:e7:5d:22:17:f5:3e:37:
84 | cf:96:22:06:21:32:7b:05:53:ae:67:81:0f:f8:4c:e5:3f:c8:
85 | 76:d2:6f:3b:90:c7:90:e8:9a:c5:f8:18:de:54:76:d9:45:5b:
86 | 9c:5f:9a:69:b5:9e:00:e4:9b:8b:7b:73:4e:ba:59:cb:e4:c2:
87 | f3:61:dd:4e:24:39:45:86:d2:9c:5b:1b:8c:84:ea:26:22:2f:
88 | e2:16:cb:f2:d6:d7:61:88
89 | -----BEGIN CERTIFICATE-----
90 | MIIFzDCCA7SgAwIBAgIEU/TqiTANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC
91 | QVQxSDBGBgNVBAoMP0EtVHJ1c3QgR2VzLiBmLiBTaWNoZXJoZWl0c3N5c3RlbWUg
92 | aW0gZWxla3RyLiBEYXRlbnZlcmtlaHIgR21iSDEYMBYGA1UECwwPQS1UcnVzdC1S
93 | b290LTA3MRgwFgYDVQQDDA9BLVRydXN0LVJvb3QtMDcwHhcNMTgwNTE3MTEyMzIy
94 | WhcNMzYxMTE5MTAyMzIyWjCBizELMAkGA1UEBhMCQVQxSDBGBgNVBAoMP0EtVHJ1
95 | c3QgR2VzLiBmLiBTaWNoZXJoZWl0c3N5c3RlbWUgaW0gZWxla3RyLiBEYXRlbnZl
96 | cmtlaHIgR21iSDEYMBYGA1UECwwPQS1UcnVzdC1Sb290LTA3MRgwFgYDVQQDDA9B
97 | LVRydXN0LVJvb3QtMDcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCj
98 | B75eAfol2AWPNH+REygfWrnDIkDvLEBKo1BGXWG42Tgou5ZePw3xqZR0pm+NkEpq
99 | gP0N2asqAr1wCOhJoeqb9+YTm94fKQDxqLUeGnhOosDXBaa3x2FTtaCFkbVaVD73
100 | z337Uo4ScDjMm0UM34mVp3f45WcDW5HCAdRbCnt/fVgurAvDuXc7EZbum/zpJ3hL
101 | 0yvWJL4CfKiJI3XXHiUfX2KSoSL0fskDSQiRxZnNlTLSXn+fKUgoyZFXQnP6esGx
102 | 3QLcAqdUAmLzhJMAWMdiV8zaGWj7KFC7BG1nS4SB4zDcfC9/0S56LsVio4gK01qH
103 | eOj2p1ErLXKDpA2M5MnmPaV8zsHDM4ZoNVJZUhiT/2LJalNxt7NRfoCpud9q+10d
104 | QMZXs1SBukZNgV2811crrJmnkuIqnOZa8nA2M2taIccPt8TyVwA16nQklCufBuvO
105 | rePW5el0WVCxie985VyK1VG15GFPbNMkyeTCu4corrnmm+NKaE6jllllRl0AeXBg
106 | XWhOo5ydkBmoblMGmKZ5VKcSYnahDj3Wa7dNLqq+352pVh8dR2OsvgCyxExP1l23
107 | suuJiPZyZV7h9PHtDGalKFFnfaykS6paRZxAJ0jG8DkcO4VTu/GwgvAGTwhWTKzx
108 | SWIu0RkCmESItpo1M/L3jc684zpXCZ8OffDYIRO1YQIDAQABozYwNDARBgNVHQ4E
109 | CgQIRMARrVMnh/QwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
110 | KoZIhvcNAQELBQADggIBAJ9HSOryu7RWucc9MEDU06OVlhj0Eb+EuGQWggqm+oSV
111 | +7yc+6R8OK1NVIEWNGDX71CZ/4jSL8+kEMiHAmAZeR912uCDcXPsyYCSIYxA1enn
112 | lgJUCJq6Qm+/pXJk+er/FtaB75bOZPKP3lwVj58YY4ZepcVZp+3GCGBk/JiVegH3
113 | DT2FyjLrbSG1UNyWilpBc9VJbqClOFi1fsgZ9P0Fy/5/yNRKEY0HP0drSA/bBsT/
114 | YyR88Ncke5Ll6JN9nPldMI7rksMwmmBh4B61xJUh9VPW05gd33mja4Btl6cy/cJK
115 | aftMnqs/NK12+7CaPYY978Cm7BcmmYSLKLTRwfOiLAGheL4atHfVmM3jcvVkIrpc
116 | iVZEjT896yaCdLv6nvEVMhZsE5egnKi9yA91pkMtcG7QbC3D3VdnWWqq/Z/z0gWb
117 | Ddqv6ecjMZHmqlm86HHO6DaZ1Nv3naJtmpLwfYZiOUqYN1nETZWv1nt10HePgamr
118 | tWwcOrBQDQPTI42GEy6G0YG73HU8x7ROl3Xsp7BEMxUhrrcTnTamTCmmmahepRQc
119 | QxIF9tbC1ypP510iF/U+N8+WIgYhMnsFU65ngQ/4TOU/yHbSbzuQx5DomsX4GN5U
120 | dtlFW5xfmmm1ngDkm4t7c066WcvkwvNh3U4kOUWG0pxbG4yE6iYiL+IWy/LW12GI
121 | -----END CERTIFICATE-----
122 |
--------------------------------------------------------------------------------
/demo.py:
--------------------------------------------------------------------------------
1 | #!/bin/env python
2 |
3 | # Example code to retrieve a Binding Certificate "Bindungs-Zertifikat" for use with the eAusweise / Digitales Amt
4 | # Usage: python3 ./demo.py
5 | # Dependencies: cryptography
6 | #
7 | # Yes, the code is terrible
8 | # I suggest you to unomment the proxies to inspect and study responses / replies with some MITM proxy (like burp)
9 |
10 | from cryptography import x509
11 | from cryptography.x509.oid import NameOID
12 | from cryptography.hazmat.primitives import hashes
13 | from cryptography.hazmat.primitives import hashes
14 | from cryptography.hazmat.primitives.asymmetric import ec
15 | from cryptography.hazmat.primitives import serialization
16 | from uuid import uuid4
17 | from base64 import b64decode,b64encode,urlsafe_b64encode
18 | import requests
19 | import json
20 | import time
21 | from os.path import isfile
22 |
23 | proxies = {
24 | 'http': 'http://127.0.0.1:8080',
25 | 'https': 'http://127.0.0.1:8080',
26 | }
27 |
28 | verify = 'burp.pem'
29 |
30 | digitalesAmtFiles = [
31 | "digitales-amt-binding-uuid.txt",
32 | "digitales-amt-cert.pem",
33 | "digitales-amt-csr.pem",
34 | "digitales-amt-key.pem"
35 | ]
36 |
37 | if any([not isfile(f) for f in digitalesAmtFiles]):
38 | print("Need to perform initial registration!")
39 | headers = {
40 | 'Host': 'eid.oesterreich.gv.at',
41 | 'Accept': 'application/json',
42 | 'Accept-Language': 'en-US',
43 | 'X-Requested-With': 'Android Binding Library 2.0.14',
44 | 'User-Agent': 'okhttp/4.9.3',
45 | 'Connection': 'close',
46 | }
47 |
48 | params = {
49 | 'os': 'android',
50 | 'packageName': 'at.gv.oe.app',
51 | }
52 |
53 | sess = requests.Session()
54 | response = sess.get('https://eid.oesterreich.gv.at/bindingservice/params', params=params, headers=headers, proxies=proxies, verify=verify)
55 | s = response.text.split(".")[1]
56 | claims = json.loads(b64decode(s + '=' * (-len(s) % 4)).decode())
57 |
58 | private_key = ec.generate_private_key(
59 | ec.SECP256R1()
60 | )
61 |
62 | # Write our key to disk for safe keeping
63 | with open("digitales-amt-key.pem", "wb") as f:
64 | f.write(private_key.private_bytes(
65 | encoding=serialization.Encoding.PEM,
66 | format=serialization.PrivateFormat.TraditionalOpenSSL,
67 | encryption_algorithm=serialization.NoEncryption(),
68 | ))
69 |
70 | # Generate a CSR
71 | x509_name = x509.Name.from_rfc4514_string(claims['subject'])
72 | CN = x509_name.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
73 | O = x509_name.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)[0].value
74 | C = x509_name.get_attributes_for_oid(NameOID.COUNTRY_NAME)[0].value
75 |
76 | csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
77 | # The order matters!
78 | x509.NameAttribute(NameOID.COMMON_NAME, CN),
79 | x509.NameAttribute(NameOID.ORGANIZATION_NAME, O),
80 | x509.NameAttribute(NameOID.COUNTRY_NAME, C),
81 | # Sign the CSR with our private key.
82 | ])).sign(private_key, hashes.SHA256())
83 |
84 | # Write our CSR out to disk.
85 | with open("digitales-amt-csr.pem", "wb") as f:
86 | f.write(csr.public_bytes(serialization.Encoding.PEM))
87 |
88 | previousAppId = str(uuid4())
89 | currentAppId = str(uuid4())
90 |
91 | json_data = {'params': response.text,
92 | 'deviceInfo': {
93 | 'osType': 'android',
94 | 'packageName': 'at.gv.oe.app',
95 | 'osVersion': '12',
96 | 'patchLevel': '2021-12-01',
97 | 'deviceName': 'emulator64_x86_64_arm64',
98 | },
99 | 'previousAppId': previousAppId,
100 | 'currentAppId': currentAppId,
101 | 'csr': b64encode(csr.public_bytes(serialization.Encoding.DER)).decode(),
102 | 'attestationChain': [
103 | # Here we fool the backend, because we're re-using a completely different attestation ;)
104 | 'MIIDNTCCAtugAwIBAgIBATAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDE7MDkGA1UEAwwyQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBJbnRlcm1lZGlhdGUwIBcNNzAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdZOF6P17EOaOcqs7/2yq6MT3QM4hssfRdee9Mdu951uAJZtgUOL1oA/dZvlEqkdyBNgAjbp3HGwXn29VcScU+aOCAZowggGWMA4GA1UdDwEB/wQEAwIHgDCCAYIGCisGAQQB1nkCAREEggFyMIIBbgIBBAoBAAIBKQoBAARyQ049QmluZHVuZ3MtWmVydGlmaWthdC1jOTY4MDg3YzhkMTY1YWJlMTU4Njg2YzNkNGZmMTA2MyxPPVJlcHVibGlrIE9lc3RlcnJlaWNoICh2ZXJ0cmV0ZW4gZHVyY2ggQktBIHVuZCBCTURXKSxDPUFUBAAwgeehCDEGAgECAgEDogMCAQOjBAICAQClCzEJAgEAAgECAgEEqgMCAQG/g3gDAgECv4U9CAIGAYYY26Agv4U+AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv4VBBQIDAdTAv4VCBQIDAxWAv4VFQAQ+MDwxFjAUBAxhdC5ndi5vZS5hcHACBHiHI2UxIgQgjaw8bOWne7y3B4+hMH4EI6mY6XHwH0O2E43S52TT3/0wADAKBggqhkjOPQQDAgNIADBFAiEAt6zwukwgTLbwZwJAbev19JuUtyQTsnf+snGeZ6WdEQsCIA7jwtGEf8kOYW19oo8TiCjXqw7u3hHx8N5U4o4A87p+',
105 | 'MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAxMTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOueefhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6RqTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9Xvsiu+f+uXc/WT/7',
106 | 'MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYDVQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3QwHhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwqQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0OBBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0WEOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqGSM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBNC/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==',
107 | ],
108 | }
109 |
110 |
111 | bindingUuid = claims['postUrl'][len('binding/'):]
112 |
113 | headers = {
114 | 'Host': 'eid.oesterreich.gv.at',
115 | 'Accept': 'application/json',
116 | 'Accept-Language': 'en-US',
117 | 'X-Requested-With': 'Android Binding Library 2.0.14',
118 | 'Content-Type': 'application/json; charset=utf-8',
119 | 'User-Agent': 'okhttp/4.9.3',
120 | 'Connection': 'close',
121 | }
122 |
123 | url = 'https://eid.oesterreich.gv.at/bindingservice/' + claims['postUrl']
124 |
125 | response = sess.post(url,
126 | headers=headers,
127 | json=json_data,
128 | verify=verify,
129 | proxies=proxies,
130 | allow_redirects=False,
131 | )
132 |
133 | headers = {
134 | 'Host': 'eid.oesterreich.gv.at',
135 | 'Accept': 'application/json',
136 | 'Sl2clienttype': 'nativeApp',
137 | 'X-Moa-Vda': '0',
138 | 'Accept-Language': 'de',
139 | 'User-Agent': 'okhttp/4.9.3',
140 | 'Connection': 'close',
141 | }
142 |
143 | redirect = response.headers['location']
144 | response = sess.get(redirect,
145 | headers=headers,
146 | verify=verify,
147 | proxies=proxies,
148 | allow_redirects=False,
149 | )
150 |
151 | resp = json.loads(response.text)
152 | pendingid = resp['params']['pendingid']
153 | pendingReqID = resp['params']['pendingReqID']
154 |
155 | headers = {
156 | 'Host': 'eid.oesterreich.gv.at',
157 | 'Accept': 'application/json',
158 | 'Sl2clienttype': 'nativeApp',
159 | 'X-Moa-Vda': '0',
160 | 'Accept-Language': 'de',
161 | 'User-Agent': 'okhttp/4.9.3',
162 | 'Connection': 'close',
163 | }
164 |
165 | data = {
166 | 'pendingid': pendingid,
167 | 'pendingReqID': pendingReqID,
168 | 'useeIDAS': 'true',
169 | }
170 |
171 | response = sess.post(
172 | 'https://eid.oesterreich.gv.at/authHandler/public/secure/process',
173 | headers=headers,
174 | data=data,
175 | verify=verify,
176 | proxies=proxies,
177 | allow_redirects=False,
178 | )
179 |
180 | resp = json.loads(response.text)
181 | pendingid = resp['params']['pendingid']
182 | pendingReqID = resp['params']['pendingReqID']
183 |
184 |
185 | print("Open https://oesterreich.gv.at/eu-login in a browser")
186 | print("And enter QR Code contents:")
187 | eidasWebSynch = input()
188 | print("In the browser, performing login using eIDAS")
189 |
190 | headers = {
191 | 'Host': 'eid.oesterreich.gv.at',
192 | 'Accept': 'application/json',
193 | 'Sl2clienttype': 'nativeApp',
194 | 'X-Moa-Vda': '0',
195 | 'Accept-Language': 'de',
196 | 'User-Agent': 'okhttp/4.9.3',
197 | 'Connection': 'close',
198 | }
199 |
200 | data = {
201 | 'pendingid': pendingid,
202 | 'qrCodeResult': eidasWebSynch,
203 | }
204 |
205 | response = sess.post(
206 | 'https://eid.oesterreich.gv.at/authHandler//public/secure/process',
207 | headers=headers,
208 | data=data,
209 | verify=verify,
210 | proxies=proxies,
211 | allow_redirects=False,
212 | )
213 |
214 | resp = json.loads(response.text)
215 | pendingid = resp['params']['pendingid']
216 |
217 | finished = False
218 |
219 | while not finished:
220 | headers = {
221 | 'Host': 'eid.oesterreich.gv.at',
222 | 'X-Binding-Wait_auth': pendingid,
223 | 'Accept': 'application/json',
224 | 'Sl2clienttype': 'nativeApp',
225 | 'X-Moa-Vda': '0',
226 | 'Accept-Language': 'de',
227 | 'User-Agent': 'okhttp/4.9.3',
228 | 'Connection': 'close',
229 | }
230 |
231 | response = sess.get(
232 | 'https://eid.oesterreich.gv.at/authHandler//public/secure/binding/waiting/authfinished',
233 | headers=headers,
234 | verify=verify,
235 | proxies=proxies,
236 | allow_redirects=False,
237 | )
238 |
239 | finished = response.status_code != 503
240 | time.sleep(1)
241 |
242 | l = response.headers['location']
243 |
244 | headers = {
245 | 'Host': 'eid.oesterreich.gv.at',
246 | 'Accept': 'application/json',
247 | 'Sl2clienttype': 'nativeApp',
248 | 'X-Moa-Vda': '0',
249 | 'Accept-Language': 'de',
250 | 'User-Agent': 'okhttp/4.9.3',
251 | 'Connection': 'close',
252 | }
253 |
254 | response = sess.get(
255 | l,
256 | headers=headers,
257 | verify=verify,
258 | proxies=proxies,
259 | allow_redirects=False,
260 | )
261 |
262 | l = response.headers['location']
263 |
264 | headers = {
265 | 'Host': 'eid.oesterreich.gv.at',
266 | 'Accept': 'application/json',
267 | 'Accept-Language': 'en-US',
268 | 'X-Requested-With': 'Android Binding Library 2.0.14',
269 | 'Content-Type': 'application/json; charset=utf-8',
270 | 'User-Agent': 'okhttp/4.9.3',
271 | 'Connection': 'close',
272 | }
273 |
274 | response = sess.post(
275 | l + "/" + bindingUuid + "/" + currentAppId,
276 | headers=headers,
277 | verify=verify,
278 | proxies=proxies,
279 | allow_redirects=False,
280 | )
281 |
282 | d = json.loads(response.text)
283 | cert = x509.load_der_x509_certificate(b64decode(d['certificate']))
284 |
285 | with open("digitales-amt-cert.pem", "wb") as f:
286 | f.write(cert.public_bytes(serialization.Encoding.PEM))
287 |
288 | with open("digitales-amt-binding-uuid.txt", "w") as f:
289 | f.write(bindingUuid)
290 |
291 | print("key => digitales-amt-key.pem")
292 | print("crt => digitales-amt-cert.pem")
293 | print("bindingUuid => digitales-amt-binding-uuid.txt")
294 | print("Digitales Amt Registration Done")
295 |
296 | eAusweise = requests.Session()
297 | bindingUuid = open('digitales-amt-binding-uuid.txt').read()
298 | cert = x509.load_pem_x509_certificate(open('digitales-amt-cert.pem', 'rb').read())
299 | key = serialization.load_pem_private_key(open('digitales-amt-key.pem', 'rb').read(), password=None)
300 |
301 | headers = {
302 | 'Host': 'identity.awp.oesterreich.gv.at',
303 | 'User-Agent': 'eAusweise/1.1.0+304911 (Android 12; sdk_gphone64_x86_64)',
304 | 'Connection': 'close',
305 | }
306 |
307 | response = eAusweise.get('https://identity.awp.oesterreich.gv.at/login', headers=headers, verify=verify, proxies=proxies, allow_redirects=False)
308 | l = response.headers['location']
309 |
310 | headers = {
311 | 'Host': 'eid.oesterreich.gv.at',
312 | 'Accept': 'application/json',
313 | 'Sl2clienttype': 'nativeApp',
314 | 'X-Binding-Token': bindingUuid,
315 | 'X-Moa-Vda': '0',
316 | 'Accept-Language': 'de',
317 | 'User-Agent': 'okhttp/4.9.3',
318 | 'Connection': 'close',
319 | }
320 |
321 | response = eAusweise.get(
322 | l,
323 | headers=headers,
324 | verify=verify,
325 | proxies=proxies,
326 | allow_redirects=True,
327 | )
328 | resp = json.loads(response.text)
329 |
330 | pendingid = resp['params']['pendingid']
331 | pendingReqID = resp['params']['pendingReqID']
332 |
333 | # fetch challenge
334 | headers = {
335 | 'Host': 'eid.oesterreich.gv.at',
336 | 'Accept': 'application/json',
337 | 'Sl2clienttype': 'nativeApp',
338 | 'X-Binding-Token': 'f0be51ae-5b7f-4e40-8a97-8724fb3c789a',
339 | 'X-Moa-Vda': '0',
340 | 'Accept-Language': 'de',
341 | 'User-Agent': 'okhttp/4.9.3',
342 | 'Connection': 'close',
343 | }
344 |
345 | data = {
346 | 'pendingid': pendingid,
347 | 'useBindingAuth': 'true',
348 | 'pendingReqID': pendingReqID,
349 | 'storeConsent': 'true',
350 | }
351 |
352 | response = eAusweise.post(
353 | 'https://eid.oesterreich.gv.at/authHandler/public/secure/process',
354 | headers=headers,
355 | data=data,
356 | verify=verify,
357 | proxies=proxies,
358 | allow_redirects=True,
359 | )
360 |
361 | resp = json.loads(response.text)
362 | challenge = resp['challenge']['challenge']
363 | issuedAt = resp['challenge']['issuedAt']
364 | pendingid = resp['params']['pendingid']
365 | pendingReqID = resp['params']['pendingReqID']
366 | response.close()
367 |
368 | head = json.dumps({
369 | "x5c": [ b64encode(cert.public_bytes(serialization.Encoding.DER)).decode() ],
370 | "typ": "bindingAuth",
371 | "alg": "ES256",
372 | }, separators=(",",":")).encode()
373 |
374 | pay = json.dumps({
375 | "challenge": challenge,
376 | "issuedAt": issuedAt,
377 | }, separators=(",",":")).encode()
378 |
379 | head_b64 = urlsafe_b64encode(head).replace(b'=', b'')
380 | pay_b64 = urlsafe_b64encode(pay).replace(b'=', b'')
381 | sig = key.sign(head_b64 + b'.' + pay_b64, ec.ECDSA(hashes.SHA256()))
382 | from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
383 | r,s = decode_dss_signature(sig)
384 | sig1 = r.to_bytes(32, 'big') + s.to_bytes(32, 'big')
385 | jwt = head_b64 + b'.' + pay_b64 + b'.' + urlsafe_b64encode(sig1).replace(b'=', b'')
386 |
387 | headers = {
388 | 'Host': 'eid.oesterreich.gv.at',
389 | 'Accept': 'application/json',
390 | 'Sl2clienttype': 'nativeApp',
391 | 'X-Binding-Token': bindingUuid,
392 | 'X-Moa-Vda': '0',
393 | 'Accept-Language': 'de',
394 | 'User-Agent': 'okhttp/4.9.3',
395 | 'Connection': 'close',
396 | }
397 |
398 |
399 | data = {
400 | 'challengeResponse': jwt.decode(),
401 | 'pendingid': pendingid,
402 | 'pendingReqID': pendingReqID,
403 | }
404 |
405 | response = eAusweise.post(
406 | 'https://eid.oesterreich.gv.at/authHandler/public/secure/process',
407 | headers=headers,
408 | data=data,
409 | verify=verify,
410 | proxies=proxies,
411 | allow_redirects=False,
412 | )
413 |
414 | l = response.headers['location']
415 | response.close()
416 |
417 | # Yes, this code is horrible, I warned you!
418 | response = eAusweise.get(
419 | l,
420 | headers=headers,
421 | verify=verify,
422 | proxies=proxies,
423 | allow_redirects=False,
424 | )
425 |
426 | l = response.headers['location']
427 | response.close()
428 |
429 | response = eAusweise.get(
430 | l,
431 | headers=headers,
432 | verify=verify,
433 | proxies=proxies,
434 | allow_redirects=False,
435 | )
436 |
437 | l = response.headers['location']
438 | response.close()
439 |
440 | response = eAusweise.get(
441 | "https://eid.oesterreich.gv.at" + l,
442 | headers=headers,
443 | verify=verify,
444 | proxies=proxies,
445 | allow_redirects=False,
446 | )
447 | response.close()
448 |
449 | headers = {
450 | 'Host': 'identity.awp.oesterreich.gv.at',
451 | 'User-Agent': 'eAusweise/1.1.0+304911 (Android 12; sdk_gphone64_x86_64)',
452 | 'Connection': 'close',
453 | }
454 | l = response.headers['location']
455 | s = requests.Session()
456 | response = eAusweise.get(
457 | l,
458 | headers=headers,
459 | allow_redirects=False,
460 | )
461 |
462 | resp = json.loads(response.text)
463 | registrationToken = resp['registrationToken']
464 |
465 | headers = {
466 | 'Host': 'backend.awp.oesterreich.gv.at',
467 | 'User-Agent': 'okhttp/4.10.0',
468 | 'Connection': 'close',
469 | }
470 |
471 | url = "https://backend.awp.oesterreich.gv.at/backend/app/api/v11/registrations/" + registrationToken
472 |
473 | response = requests.get(url,
474 | headers=headers,
475 | allow_redirects=False,
476 | cert=("anonymous-prod-cert.pem", "anonymous-prod-key.pem"),
477 | )
478 |
479 | resp = json.loads(response.text)
480 | assert(resp['registered'] == True)
481 |
482 | private_key = ec.generate_private_key(
483 | ec.SECP256R1()
484 | )
485 |
486 | # Write our key to disk for safe keeping
487 | with open("eAusweise-key.pem", "wb") as f:
488 | f.write(private_key.private_bytes(
489 | encoding=serialization.Encoding.PEM,
490 | format=serialization.PrivateFormat.TraditionalOpenSSL,
491 | encryption_algorithm=serialization.NoEncryption(),
492 | ))
493 |
494 | # Generate a CSR
495 | csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
496 | x509.NameAttribute(NameOID.COMMON_NAME, "fuck-the.gov"),
497 | # Sign the CSR with our private key.
498 | ])).sign(private_key, hashes.SHA256())
499 |
500 | # Write our CSR out to disk.
501 | with open("eAusweise-csr.pem", "wb") as f:
502 | f.write(csr.public_bytes(serialization.Encoding.PEM))
503 |
504 | json_data = {
505 | 'csr': csr.public_bytes(serialization.Encoding.PEM).decode(),
506 | 'device': {
507 | 'manufacturer': 'Google',
508 | 'name': 'sdk_gphone64_x86_64',
509 | 'pushMessageType': 'gcm',
510 | 'pushMessageToken': '',
511 | },
512 | 'user': {},
513 | 'password': None,
514 | }
515 |
516 | response = requests.post(
517 | url,
518 | headers=headers,
519 | json=json_data,
520 | cert=("anonymous-prod-cert.pem", "anonymous-prod-key.pem"),
521 | )
522 |
523 | resp = json.loads(response.text)
524 |
525 | with open("eAusweise-cert.pem", "w") as f:
526 | f.write(resp['cert'])
527 |
528 | print("Done eAusweise Login")
529 | print("Usage example: curl --key eAusweise-key.pem --cert eAusweise-cert.pem https://backend.awp.oesterreich.gv.at/backend/app/api/v11/users/status")
530 |
--------------------------------------------------------------------------------