summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Stradling <rob@comodo.com>2017-01-10 12:37:32 +0000
committerRob Stradling <rob@comodo.com>2017-01-10 12:37:32 +0000
commitd547fa56769298eb82b440883450dd331b33176c (patch)
tree02144a1c8d58aec12b4550f265eb91e1e8344918
parente9547e9292bca0d1c0ab293358dd96ad0c41f536 (diff)
downloadcertwatch_db-d547fa56769298eb82b440883450dd331b33176c.zip
certwatch_db-d547fa56769298eb82b440883450dd331b33176c.tar.gz
certwatch_db-d547fa56769298eb82b440883450dd331b33176c.tar.bz2
gen-add-chain tool.
-rw-r--r--enumerate_chains.fnc128
-rw-r--r--generate_add_chain_body.fnc72
-rw-r--r--web_apis.fnc29
3 files changed, 228 insertions, 1 deletions
diff --git a/enumerate_chains.fnc b/enumerate_chains.fnc
new file mode 100644
index 0000000..0cee5e1
--- /dev/null
+++ b/enumerate_chains.fnc
@@ -0,0 +1,128 @@
+CREATE OR REPLACE FUNCTION enumerate_chains(
+ cert_id certificate.ID%TYPE,
+ must_be_time_valid boolean DEFAULT TRUE,
+ trust_ctx_id trust_context.ID%TYPE DEFAULT NULL,
+ trust_purp_id trust_purpose.ID%TYPE DEFAULT NULL,
+ only_one_chain boolean DEFAULT FALSE,
+ max_ca_repeats integer DEFAULT 0,
+ certchain_so_far integer[] DEFAULT NULL,
+ cachain_so_far integer[] DEFAULT NULL
+) RETURNS SETOF integer[]
+AS $$
+DECLARE
+ t_certificate certificate.CERTIFICATE%TYPE;
+ t_issuerCAID certificate.ISSUER_CA_ID%TYPE;
+ t_caID ca.ID%TYPE;
+ t_caChainSoFar integer[];
+ t_certChainSoFar integer[];
+ l_issuer RECORD;
+ l_chain RECORD;
+ t_count integer;
+BEGIN
+ SELECT c.CERTIFICATE, c.ISSUER_CA_ID
+ INTO t_certificate, t_issuerCAID
+ FROM certificate c
+ WHERE c.ID = cert_id;
+ IF t_certificate IS NULL THEN
+ RETURN;
+ END IF;
+
+ IF trust_ctx_id IS NOT NULL THEN
+ SELECT count(*)
+ FROM root_trust_purpose rtp
+ INTO t_count
+ WHERE rtp.CERTIFICATE_ID = cert_id
+ AND rtp.TRUST_CONTEXT_ID = trust_ctx_id
+ AND rtp.TRUST_PURPOSE_ID = coalesce(trust_purp_id, rtp.TRUST_PURPOSE_ID);
+ IF t_count > 0 THEN
+ t_certChainSoFar := array_append(certchain_so_far, cert_id);
+ RETURN NEXT t_certChainSoFar;
+ RETURN;
+ END IF;
+ END IF;
+
+ -- If this is a CA Certificate, check if the CA has already appeared in the
+ -- chain.
+ SELECT cac.CA_ID
+ INTO t_caID
+ FROM ca_certificate cac
+ WHERE cac.CERTIFICATE_ID = cert_id;
+ IF t_caID IS NOT NULL THEN
+ IF t_caID = t_issuerCAID THEN
+ -- Avoid untrusted, self-signed CA certificate loops!
+ RETURN;
+ ELSIF (cachain_so_far IS NOT NULL)
+ AND (cachain_so_far @> ARRAY[t_caID]) THEN
+ -- Avoid (too many) cross-certification loops!
+ IF array_length(cachain_so_far, 1) - array_length(array_remove(cachain_so_far, t_caID), 1) > max_ca_repeats THEN
+ RETURN;
+ END IF;
+ END IF;
+ t_caChainSoFar := array_append(cachain_so_far, t_caID);
+ END IF;
+
+ -- Enforce any Basic Constraints pathLenConstraint in this certificate.
+ IF (COALESCE(x509_getPathLenConstraint(t_certificate)::bigint,
+ array_length(certchain_so_far, 1)) + 1)
+ < array_length(certchain_so_far, 1) THEN
+ RETURN;
+ END IF;
+
+ -- Enforce a maximum path length of 20 certificates.
+ IF array_length(certchain_so_far, 1) >= 20 THEN
+ -- Append -1 to show that the maximum length has been reached.
+ RETURN NEXT array_append(certchain_so_far, -1);
+ RETURN;
+ END IF;
+
+ -- Output this chain.
+ t_certChainSoFar := array_append(certchain_so_far, cert_id);
+
+ -- Loop through every matching issuer CA certificate.
+ FOR l_issuer IN (
+ SELECT cac.CERTIFICATE_ID, cac.CA_ID
+ FROM certificate c, ca, ca_certificate cac
+ WHERE c.ID = cert_id
+ AND c.ISSUER_CA_ID = ca.ID
+ AND ca.PUBLIC_KEY != E'\\x00'
+ AND ca.ID = cac.CA_ID
+ ORDER BY ca.ID DESC
+ ) LOOP
+ IF (trust_ctx_id IS NOT NULL) OR (trust_purp_id IS NOT NULL) THEN
+ SELECT COUNT(*)
+ INTO t_count
+ FROM ca_trust_purpose ctp
+ WHERE ctp.CA_ID = l_issuer.CA_ID
+ AND ctp.TRUST_CONTEXT_ID = COALESCE(trust_ctx_id,
+ ctp.TRUST_CONTEXT_ID)
+ AND ctp.TRUST_PURPOSE_ID = COALESCE(trust_purp_id,
+ ctp.TRUST_PURPOSE_ID)
+ AND ctp.IS_TIME_VALID >= COALESCE(must_be_time_valid, FALSE);
+ IF (t_count > 0) AND (trust_purp_id >= 100) THEN -- EV Server Authentication.
+ -- EV Server Authentication must also be trusted for Server Authentication.
+ SELECT COUNT(*)
+ INTO t_count
+ FROM ca_trust_purpose ctp
+ WHERE ctp.CA_ID = l_issuer.CA_ID
+ AND ctp.TRUST_CONTEXT_ID = COALESCE(trust_ctx_id,
+ ctp.TRUST_CONTEXT_ID)
+ AND ctp.TRUST_PURPOSE_ID IN (1, 30); -- Server Authentication, SGC.
+ END IF;
+ ELSE
+ t_count := 1;
+ END IF;
+ IF t_count > 0 THEN
+ FOR l_chain IN (
+ SELECT enumerate_chains(l_issuer.CERTIFICATE_ID, must_be_time_valid,
+ trust_ctx_id, trust_purp_id, only_one_chain,
+ max_ca_repeats, t_certChainSoFar, t_caChainSoFar)
+ ) LOOP
+ RETURN NEXT l_chain.enumerate_chains;
+ IF only_one_chain THEN
+ RETURN;
+ END IF;
+ END LOOP;
+ END IF;
+ END LOOP;
+END;
+$$ LANGUAGE plpgsql;
diff --git a/generate_add_chain_body.fnc b/generate_add_chain_body.fnc
new file mode 100644
index 0000000..3e14968
--- /dev/null
+++ b/generate_add_chain_body.fnc
@@ -0,0 +1,72 @@
+CREATE OR REPLACE FUNCTION generate_add_chain_body(
+ cert_data certificate.CERTIFICATE%TYPE
+) RETURNS text
+AS $$
+DECLARE
+ t_issuerCAID certificate.ISSUER_CA_ID%TYPE;
+ t_certChain integer[];
+ t_output text;
+ t_hexCertificate text;
+ l_ca RECORD;
+ l_caCert RECORD;
+BEGIN
+ FOR l_ca IN (
+ SELECT *
+ FROM ca
+ WHERE ca.NAME = x509_issuerName(cert_data)
+ AND ca.PUBLIC_KEY != E'\\x00'
+ ORDER BY octet_length(PUBLIC_KEY) DESC
+ ) LOOP
+ IF x509_verify(cert_data, l_ca.PUBLIC_KEY) THEN
+ t_issuerCAID := l_ca.ID;
+ EXIT;
+ END IF;
+ END LOOP;
+ IF t_issuerCAID IS NULL THEN
+ RETURN NULL;
+ END IF;
+
+ FOR l_caCert IN (
+ SELECT cac.CERTIFICATE_ID
+ FROM ca_certificate cac
+ WHERE cac.CA_ID = t_issuerCAID
+ ORDER BY cac.CERTIFICATE_ID
+ LIMIT 1
+ ) LOOP
+ SELECT enumerate_chains(l_caCert.CERTIFICATE_ID, TRUE, 5, NULL)
+ INTO t_certChain;
+ EXIT WHEN FOUND;
+ SELECT enumerate_chains(l_caCert.CERTIFICATE_ID, TRUE, 1, NULL)
+ INTO t_certChain;
+ EXIT WHEN FOUND;
+ SELECT enumerate_chains(l_caCert.CERTIFICATE_ID, TRUE, 12, NULL)
+ INTO t_certChain;
+ EXIT WHEN FOUND;
+ SELECT enumerate_chains(l_caCert.CERTIFICATE_ID, FALSE, 5, NULL)
+ INTO t_certChain;
+ EXIT WHEN FOUND;
+ SELECT enumerate_chains(l_caCert.CERTIFICATE_ID, FALSE, 1, NULL)
+ INTO t_certChain;
+ EXIT WHEN FOUND;
+ SELECT enumerate_chains(l_caCert.CERTIFICATE_ID, FALSE, 12, NULL)
+ INTO t_certChain;
+ EXIT WHEN FOUND;
+ END LOOP;
+
+ IF (t_certChain IS NULL) OR (array_length(t_certChain, 1) = 0) THEN
+ RETURN NULL;
+ END IF;
+
+ t_output := '{"chain":["' || replace(encode(cert_data, 'base64'), chr(10), '') || '"';
+
+ FOR l_certNo IN 1..array_length(t_certChain, 1) LOOP
+ SELECT replace(encode(c.CERTIFICATE, 'base64'), chr(10), '')
+ INTO t_hexCertificate
+ FROM certificate c
+ WHERE c.ID = t_certChain[l_certNo];
+ t_output := t_output || ',"' || t_hexCertificate || '"';
+ END LOOP;
+
+ RETURN t_output || ']}';
+END;
+$$ LANGUAGE plpgsql;
diff --git a/web_apis.fnc b/web_apis.fnc
index 62b9c2f..2ead4a7 100644
--- a/web_apis.fnc
+++ b/web_apis.fnc
@@ -218,7 +218,7 @@ BEGIN
IF t_outputType = '' THEN
t_outputType := 'html';
END IF;
- IF lower(t_outputType) IN ('forum', 'mozilla-disclosures', 'redacted-precertificates') THEN
+ IF lower(t_outputType) IN ('forum', 'gen-add-chain', 'mozilla-disclosures', 'redacted-precertificates') THEN
t_type := lower(t_outputType);
t_title := t_type;
t_outputType := 'html';
@@ -852,6 +852,33 @@ BEGIN
' || t_temp || '
</TABLE>';
+ ELSIF t_type = 'gen-add-chain' THEN
+ t_temp := get_parameter('b64cert', paramNames, paramValues);
+ IF t_temp IS NULL THEN
+ t_output := t_output ||
+'<BR><BR>1. Enter a base64 encoded certificate.
+<BR><BR>2. Press the button to generate JSON that you can then submit to a log''s /ct/v1/add-chain API.
+<BR>(crt.sh will discover the trust chain for you).
+<BR><BR><FORM>
+ <TEXTAREA name="b64cert" rows=25 cols=64></TEXTAREA>
+ <BR><BR><INPUT type="submit" class="button" value="Generate JSON">
+</FORM>
+<BR><BR><SPAN class="small">Please note: This tool currently finds chains that are trusted by the Mozilla and/or Microsoft and/or Apple root programs.
+<BR>FIXME: Look at each log''s /ct/v1/get-roots instead</SPAN>';
+ ELSE
+ t_certificate := decode(
+ replace(replace(t_temp, '-----BEGIN CERTIFICATE-----', ''), '-----END CERTIFICATE-----', ''),
+ 'base64'
+ );
+
+ RETURN
+'[BEGIN_HEADERS]
+Content-Disposition: attachment; filename="add-chain-' || encode(digest(t_certificate, 'sha256'), 'hex') || '.json"
+Content-Type: application/json
+[END_HEADERS]
+' || generate_add_chain_body(t_certificate);
+ END IF;
+
ELSIF t_type = 'mozilla-disclosures' THEN
t_output := t_output ||
' <SPAN class="whiteongrey">Mozilla Disclosures</SPAN>