diff options
author | Rob Stradling <rob@comodo.com> | 2017-01-10 12:37:32 +0000 |
---|---|---|
committer | Rob Stradling <rob@comodo.com> | 2017-01-10 12:37:32 +0000 |
commit | d547fa56769298eb82b440883450dd331b33176c (patch) | |
tree | 02144a1c8d58aec12b4550f265eb91e1e8344918 | |
parent | e9547e9292bca0d1c0ab293358dd96ad0c41f536 (diff) | |
download | certwatch_db-d547fa56769298eb82b440883450dd331b33176c.zip certwatch_db-d547fa56769298eb82b440883450dd331b33176c.tar.gz certwatch_db-d547fa56769298eb82b440883450dd331b33176c.tar.bz2 |
gen-add-chain tool.
-rw-r--r-- | enumerate_chains.fnc | 128 | ||||
-rw-r--r-- | generate_add_chain_body.fnc | 72 | ||||
-rw-r--r-- | web_apis.fnc | 29 |
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> |