-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcerttool.py
287 lines (236 loc) · 11.4 KB
/
certtool.py
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
import zipfile
import requests
import xml.etree.ElementTree as ET
from io import BytesIO
import struct
from typing import List, Optional, Tuple, Dict
import json
import os
import logging
from datetime import datetime, timedelta
from pathlib import Path
def get_cache_path() -> Path:
"""Get the path to the cache file"""
cache_dir = Path.home() / ".config" / "tizen-cert"
cache_dir.mkdir(parents=True, exist_ok=True)
return cache_dir / "config.json"
def load_cached_config() -> Optional[Dict[str, str]]:
"""Load cached configuration if it exists and is valid"""
cache_path = get_cache_path()
if not cache_path.exists():
return None
try:
with open(cache_path, "r") as f:
data = json.load(f)
# Check if cache is expired (7 days)
cached_time = datetime.fromisoformat(data.get("timestamp", "2000-01-01"))
if datetime.now() - cached_time > timedelta(days=7):
logging.info("Cache expired, will fetch fresh configuration")
return None
required_keys = ["SERVICE_ID", "loginUrl"]
if all(key in data for key in required_keys):
logging.info("Using cached configuration")
return {"SERVICE_ID": data["SERVICE_ID"], "loginUrl": data["loginUrl"]}
except Exception as e:
logging.warning(f"Failed to load cache: {e}")
return None
def save_config_cache(config: Dict[str, str]):
"""Save configuration to cache"""
cache_path = get_cache_path()
try:
cache_data = {
"SERVICE_ID": config["SERVICE_ID"],
"loginUrl": config["loginUrl"],
"timestamp": datetime.now().isoformat(),
}
with open(cache_path, "w") as f:
json.dump(cache_data, f, indent=2)
logging.info("Saved configuration to cache")
except Exception as e:
logging.warning(f"Failed to save cache: {e}")
def initialize_server_config() -> Dict[str, str]:
"""Initialize server configuration, using cache if available"""
# Try to load from cache first
cached_config = load_cached_config()
if cached_config:
return cached_config
variables = {
"SERVICE_ID": None,
"loginUrl": None,
}
# Download and process configuration
response = requests.get(
"https://download.tizen.org/sdk/tizenstudio/official/extension_info.xml"
)
if response.status_code == 200:
logging.info("Downloaded repository XML. Getting certificate extension URL.")
tree = ET.ElementTree(ET.fromstring(response.text))
root = tree.getroot()
url = None
for extension in root:
extension_dict = {}
for tag in extension:
extension_dict[tag.tag] = tag.text
if "name" in extension_dict:
if extension_dict["name"] == "Samsung Certificate Extension":
if "repository" in extension_dict:
url = extension_dict["repository"].strip()
if not url:
logging.warning("Could not find url, using default")
url = "https://download.tizen.org/sdk/extensions/tizen-certificate-extension_2.0.70.zip"
zip_response = requests.get(url)
if zip_response.status_code == 200:
with open("certificate_extension.zip", "wb") as f:
f.write(zip_response.content)
# Extract certificates and configuration
with zipfile.ZipFile("certificate_extension.zip", "r") as extension_zip_ref:
for extension_member in extension_zip_ref.infolist():
ext_fn = extension_member.filename.split("/")[-1]
if ext_fn.startswith("cert-add-on") and ext_fn.endswith("ubuntu-64.zip"):
binary_zipfile = extension_zip_ref.read(extension_member.filename)
with zipfile.ZipFile(BytesIO(binary_zipfile), "r") as binary_zip_ref:
for binary_member in binary_zip_ref.infolist():
fn = binary_member.filename.split("/")[-1]
if fn.startswith("org.tizen.common.cert") and fn.endswith(
".jar"
):
jarfile = binary_zip_ref.read(binary_member.filename)
with zipfile.ZipFile(BytesIO(jarfile), "r") as jar_ref:
for member in jar_ref.infolist():
fn = member.filename.split("/")[-1]
if member.filename.endswith(
".cer"
) or member.filename.endswith(".crt"):
member.filename = fn
jar_ref.extract(member, "ca")
if member.filename.endswith("SigninDialog.class"):
constants = get_constants(
BytesIO(jar_ref.read(member.filename))
)
for i, constant in enumerate(constants):
try:
if constant in variables:
variables[constant] = constants[
i + 1
]
except:
pass
# Cache the configuration if it was successfully retrieved
if variables["SERVICE_ID"] and variables["loginUrl"]:
save_config_cache(variables)
else:
logging.error("Failed to get complete configuration")
return variables
def read_bytes(fh, count: int) -> bytes:
"""Read specified number of bytes from file handle"""
return fh.read(count)
def bytes_to_int(b: bytes) -> int:
"""Convert bytes to integer based on length"""
if len(b) == 1:
return b[0]
elif len(b) == 2:
return struct.unpack(">H", b)[0]
elif len(b) == 4:
return struct.unpack(">I", b)[0]
elif len(b) == 8:
return struct.unpack(">Q", b)[0]
raise ValueError(f"Cannot convert {len(b)} bytes to int")
def parse_utf8(fh) -> str:
"""Parse UTF8 constant"""
length = bytes_to_int(read_bytes(fh, 2))
return read_bytes(fh, length).decode("utf-8")
def parse_constant(fh, tag: int) -> Tuple[Optional[str], bool]:
"""
Parse a single constant based on its tag.
Returns a tuple of (value, takes_two_slots) where takes_two_slots indicates
if this constant takes up two slots in the constant pool (true for LONG and DOUBLE).
"""
if tag == 0: # Padding entry
return None, False
elif tag == 1: # CONSTANT_Utf8
return parse_utf8(fh), False
elif tag in (3, 4): # CONSTANT_Integer or CONSTANT_Float
val = bytes_to_int(read_bytes(fh, 4))
return str(val), False
elif tag in (5, 6): # CONSTANT_Long or CONSTANT_Double
val = bytes_to_int(read_bytes(fh, 8))
return str(val), True # These types take up two slots
elif tag == 7: # CONSTANT_Class
# Skip index
read_bytes(fh, 2)
return None, False
elif tag == 8: # CONSTANT_String
# Skip string index
read_bytes(fh, 2)
return None, False
elif tag in (
9,
10,
11,
): # CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref
# Skip class and name/type indices
read_bytes(fh, 4)
return None, False
elif tag == 12: # CONSTANT_NameAndType
# Skip name and descriptor indices
read_bytes(fh, 4)
return None, False
else:
raise ValueError(f"Unknown constant tag: {tag}")
def get_constants(class_bytes: BytesIO) -> List[str]:
"""
Parse a Java class file from bytes and return a list of its constants as strings.
Only includes actual constant values (strings, numbers), not structural constants.
Args:
class_bytes (BytesIO): BytesIO object containing the Java class file contents
Returns:
list[str]: List of constants as strings
"""
constants = []
# Skip magic number
read_bytes(class_bytes, 4)
# Skip version info
read_bytes(class_bytes, 4)
# Read constant pool count
const_count = bytes_to_int(read_bytes(class_bytes, 2)) - 1
# Parse constants
i = 0
while i < const_count:
tag = bytes_to_int(read_bytes(class_bytes, 1))
value, takes_two_slots = parse_constant(class_bytes, tag)
if value is not None:
constants.append(value)
if takes_two_slots:
i += 1 # Skip the next slot as it's used for padding
i += 1
return constants
# #Summary of commands
# # Get access token here:
# # https://account.samsung.com/accounts/TDC/signInGate?clientId=<SERVICE_ID>&tokenType=TOKEN
# # Find the CA certificates here:
# # https://gitlab.com/andreas-mausch/moonwatch/-/tree/master/certificates
# # Author certificate
# openssl genrsa -out author.key.pem 2048
# openssl rsa -in author.key.pem -outform PEM -pubout -out author.key.pem.pub
# openssl req -new -key author.key.pem -out author.csr -subj "/CN=gear-certificate@protonmail.com"
# curl -v -X POST https://dev.tizen.samsung.com:443/apis/v2/authors -F access_token=<ACCESS_TOKEN> -F user_id=<USER_ID> -F csr=@author.csr --output author.crt
# cat author.crt ca/gear_test_author_CA.cer > author-and-ca.crt
# openssl pkcs12 -export -out author.p12 -inkey author.key.pem -in author-and-ca.crt -name usercertificate
# # Distributor certificate
# openssl genrsa -out distributor.key.pem 2048
# openssl rsa -in distributor.key.pem -outform PEM -pubout -out distributor.key.pem.pub
# openssl req -new -key distributor.key.pem -out distributor.csr -subj "/CN=TizenSDK" -addext "subjectAltName = URI:URN:tizen:packageid=,URI:URN:tizen:deviceid=<DEVICE_ID>"
# curl -v -X POST https://dev.tizen.samsung.com:443/apis/v2/distributors -F access_token=<ACCESS_TOKEN> -F user_id=<USER_ID> -F privilege_level=Public -F developer_type=Individual -F csr=@distributor.csr --output distributor.crt
# cat distributor.crt ca/samsung_tizen_dev_public2.crt > distributor-and-ca.crt
# openssl pkcs12 -export -out distributor.p12 -inkey distributor.key.pem -in distributor-and-ca.crt -name usercertificate
# Generating certificates for TV (VD) looks a bit different:
# # Author certificate VD
# openssl req -new -key author.key.pem -out author.csr -subj "/CN=<EMAIL_OR_NAME>"
# curl -v -X POST https://dev.tizen.samsung.com:443/apis/v2/authors -F access_token=<ACCESS_TOKEN> -F user_id=<USER_ID> -F platform=VD -F csr=@author.csr --output author.crt
# cat author.crt ca/vd_tizen_dev_author_ca.cer > author-and-ca.crt
# openssl pkcs12 -export -out author.p12 -inkey author.key.pem -in author-and-ca.crt -name usercertificate -legacy
# # Distributor certificate VD
# openssl req -new -key distributor.key.pem -out distributor.csr -subj "/CN=TizenSDK/emailAddress=<EMAIL>" -addext "subjectAltName = URI:URN:tizen:packageid=,URI:URN:tizen:deviceid=<DEVICE_ID>"
# curl -v -X POST https://dev.tizen.samsung.com:443/apis/v2/distributors -F access_token=<ACCESS_TOKEN> -F user_id=<USER_ID> -F platform=VD -F privilege_level=Public -F developer_type=Individual -F csr=@distributor.csr --output distributor.crt
# cat distributor.crt ca/vd_tizen_dev_public2.crt > distributor-and-ca.crt
# openssl pkcs12 -export -out distributor.p12 -inkey distributor.key.pem -in distributor-and-ca.crt -name usercertificate -legacy