зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1346359 - Script to submit transparency certs to CT r=rail
MozReview-Commit-ID: F8uPgoJTFr --HG-- extra : rebase_source : d45e3db6ff3836057cb3934d2e8d2956f715c8c8
This commit is contained in:
Родитель
81ad69be7e
Коммит
b698283963
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import struct
|
||||
import base64
|
||||
|
||||
class SignedCertificateTimestamp:
|
||||
"""
|
||||
Represents a Signed Certificate Timestamp from a Certificate Transparency
|
||||
log, which is how the log indicates that it has seen and logged a
|
||||
certificate. The format for SCTs in RFC 6962 is as follows:
|
||||
|
||||
struct {
|
||||
Version sct_version;
|
||||
LogID id;
|
||||
uint64 timestamp;
|
||||
CtExtensions extensions;
|
||||
digitally-signed struct {
|
||||
Version sct_version;
|
||||
SignatureType signature_type = certificate_timestamp;
|
||||
uint64 timestamp;
|
||||
LogEntryType entry_type;
|
||||
select(entry_type) {
|
||||
case x509_entry: ASN.1Cert;
|
||||
case precert_entry: PreCert;
|
||||
} signed_entry;
|
||||
CtExtensions extensions;
|
||||
};
|
||||
} SignedCertificateTimestamp;
|
||||
|
||||
Here, the "digitally-signed" is just a fixed struct encoding the algorithm
|
||||
and signature:
|
||||
|
||||
struct {
|
||||
SignatureAndHashAlgorithm algorithm;
|
||||
opaque signature<0..2^16-1>;
|
||||
} DigitallySigned;
|
||||
|
||||
In other words the whole serialized SCT comprises:
|
||||
|
||||
- 1 octet of version = v1 = resp["sct_version"]
|
||||
- 32 octets of LogID = resp["id"]
|
||||
- 8 octets of timestamp = resp["timestamp"]
|
||||
- 2 octets of extensions length + resp["extensions"]
|
||||
- 2+2+N octets of signature
|
||||
|
||||
These are built from RFC 6962 API responses, which are encoded in JSON
|
||||
object of the following form:
|
||||
|
||||
{
|
||||
"sct_version": 0,
|
||||
"id": "...",
|
||||
"timestamp": ...,
|
||||
"extensions": "",
|
||||
"signature": "...",
|
||||
}
|
||||
|
||||
The "signature" field contains the whole DigitallySigned struct.
|
||||
"""
|
||||
|
||||
# We only support SCTs from RFC 6962 logs
|
||||
SCT_VERSION = 0
|
||||
|
||||
def __init__(self, response_json=None):
|
||||
self.version = SignedCertificateTimestamp.SCT_VERSION
|
||||
|
||||
if response_json is not None:
|
||||
if response_json['sct_version'] is not SignedCertificateTimestamp.SCT_VERSION:
|
||||
raise Exception('Incorrect version for SCT')
|
||||
|
||||
self.id = base64.b64decode(response_json['id'])
|
||||
self.timestamp = response_json['timestamp']
|
||||
self.signature = base64.b64decode(response_json['signature'])
|
||||
|
||||
self.extensions = b''
|
||||
if 'extensions' in response_json:
|
||||
self.extensions = base64.b64decode(response_json['extensions'])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_rfc6962(serialized):
|
||||
start = 0
|
||||
read = 1 + 32 + 8
|
||||
if len(serialized) < start + read:
|
||||
raise Exception('SCT too short for version, log ID, and timestamp')
|
||||
version, = struct.unpack('B', serialized[0])
|
||||
log_id = serialized[1:1+32]
|
||||
timestamp, = struct.unpack('!Q', serialized[1+32:1+32+8])
|
||||
start += read
|
||||
|
||||
if version is not SignedCertificateTimestamp.SCT_VERSION:
|
||||
raise Exception('Incorrect version for SCT')
|
||||
|
||||
read = 2
|
||||
if len(serialized) < start + read:
|
||||
raise Exception('SCT too short for extension length')
|
||||
ext_len, = struct.unpack('!H', serialized[start:start+read])
|
||||
start += read
|
||||
|
||||
read = ext_len
|
||||
if len(serialized) < start + read:
|
||||
raise Exception('SCT too short for extensions')
|
||||
extensions = serialized[start:read]
|
||||
start += read
|
||||
|
||||
read = 4
|
||||
if len(serialized) < start + read:
|
||||
raise Exception('SCT too short for signature header')
|
||||
alg, sig_len, = struct.unpack('!HH', serialized[start:start+read])
|
||||
start += read
|
||||
|
||||
read = sig_len
|
||||
if len(serialized) < start + read:
|
||||
raise Exception('SCT too short for signature')
|
||||
sig = serialized[start:start+read]
|
||||
|
||||
sct = SignedCertificateTimestamp()
|
||||
sct.id = log_id
|
||||
sct.timestamp = timestamp
|
||||
sct.extensions = extensions
|
||||
sct.signature = struct.pack('!HH', alg, sig_len) + sig
|
||||
return sct
|
||||
|
||||
|
||||
def to_rfc6962(self):
|
||||
version = struct.pack("B", self.version)
|
||||
timestamp = struct.pack("!Q", self.timestamp)
|
||||
ext_len = struct.pack("!H", len(self.extensions))
|
||||
|
||||
return version + self.id + timestamp + \
|
||||
ext_len + self.extensions + self.signature
|
|
@ -0,0 +1,81 @@
|
|||
import os
|
||||
import sys
|
||||
import base64
|
||||
from OpenSSL import crypto
|
||||
|
||||
sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
|
||||
|
||||
from mozharness.base.script import BaseScript
|
||||
from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
|
||||
from mozharness.mozilla.signed_certificate_timestamp import SignedCertificateTimestamp
|
||||
|
||||
|
||||
class CTSubmitter(BaseScript, VirtualenvMixin):
|
||||
config_options = virtualenv_config_options
|
||||
|
||||
config_options = [
|
||||
[["--chain"], {
|
||||
"dest": "chain",
|
||||
"help": "URL from which to download the cert chain to be submitted to CT (in PEM format)"
|
||||
}],
|
||||
[["--log"], {
|
||||
"dest": "log",
|
||||
"help": "URL for the log to which the chain should be submitted"
|
||||
}],
|
||||
[["--sct"], {
|
||||
"dest": "sct",
|
||||
"help": "File where the SCT from the log should be written"
|
||||
}],
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
BaseScript.__init__(self,
|
||||
config_options=self.config_options,
|
||||
config={
|
||||
"virtualenv_modules": [
|
||||
"pem",
|
||||
"redo",
|
||||
"requests",
|
||||
],
|
||||
"virtualenv_path": "venv",
|
||||
},
|
||||
require_config_file=False,
|
||||
all_actions=["add-chain"],
|
||||
default_actions=["add-chain"],
|
||||
)
|
||||
|
||||
self.chain_url = self.config["chain"]
|
||||
self.log_url = self.config["log"]
|
||||
self.sct_filename = self.config["sct"]
|
||||
|
||||
def add_chain(self):
|
||||
from redo import retry
|
||||
import requests
|
||||
import pem
|
||||
|
||||
def get_chain():
|
||||
r = requests.get(self.chain_url)
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
chain = retry(get_chain)
|
||||
|
||||
req = { "chain": [] }
|
||||
chain = pem.parse(chain)
|
||||
for i in range(len(chain)):
|
||||
cert = crypto.load_certificate(crypto.FILETYPE_PEM, str(chain[i]))
|
||||
der = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
|
||||
req["chain"].append(base64.b64encode(der))
|
||||
|
||||
def post_chain():
|
||||
r = requests.post(self.log_url + '/ct/v1/add-chain', json=req)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
resp = retry(post_chain)
|
||||
sct = SignedCertificateTimestamp(resp)
|
||||
self.write_to_file(self.sct_filename, sct.to_rfc6962())
|
||||
|
||||
if __name__ == "__main__":
|
||||
myScript = CTSubmitter()
|
||||
myScript.run_and_exit()
|
|
@ -0,0 +1,31 @@
|
|||
import unittest
|
||||
import struct
|
||||
from mozharness.mozilla.signed_certificate_timestamp import SignedCertificateTimestamp
|
||||
|
||||
log_id = 'pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA='.decode('base64')
|
||||
timestamp = 1483206164907
|
||||
signature = 'BAMARzBFAiEAsyJov/LF1DIxurR+6xkxP/ZJzb3whHQ+1+PrJNuXfnoCIG28p1XRxkQqRprnCIDDBniKbJngig/NQnIEQ5VZOYG+'.decode('base64')
|
||||
|
||||
json_sct = {
|
||||
'sct_version': 0,
|
||||
'id': log_id.encode('base64'),
|
||||
'timestamp': timestamp,
|
||||
'signature': signature.encode('base64'),
|
||||
}
|
||||
|
||||
hex_timestamp = struct.pack('!Q', timestamp).encode('hex')
|
||||
hex_sct = '00' + log_id.encode('hex') + hex_timestamp + '0000' + signature.encode('hex')
|
||||
binary_sct = hex_sct.decode('hex')
|
||||
|
||||
class TestSignedCertificateTimestamp(unittest.TestCase):
|
||||
def testEncode(self):
|
||||
sct = SignedCertificateTimestamp(json_sct)
|
||||
self.assertEquals(sct.to_rfc6962(), binary_sct)
|
||||
|
||||
def testDecode(self):
|
||||
sct = SignedCertificateTimestamp.from_rfc6962(binary_sct)
|
||||
|
||||
self.assertEquals(sct.version, 0)
|
||||
self.assertEquals(sct.id, log_id)
|
||||
self.assertEquals(sct.timestamp, timestamp)
|
||||
self.assertEquals(sct.signature, signature)
|
Загрузка…
Ссылка в новой задаче