Crypt-Bear

 view release on metacpan or  search on metacpan

src/x509/x509_minimal.t0  view on Meta::CPAN

\ Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
\
\ Permission is hereby granted, free of charge, to any person obtaining 
\ a copy of this software and associated documentation files (the
\ "Software"), to deal in the Software without restriction, including
\ without limitation the rights to use, copy, modify, merge, publish,
\ distribute, sublicense, and/or sell copies of the Software, and to
\ permit persons to whom the Software is furnished to do so, subject to
\ the following conditions:
\
\ The above copyright notice and this permission notice shall be 
\ included in all copies or substantial portions of the Software.
\
\ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
\ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
\ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
\ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
\ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
\ SOFTWARE.

preamble {

#include "inner.h"

/*
 * Implementation Notes
 * --------------------
 *
 * The C code pushes the data by chunks; all decoding is done in the
 * T0 code. The cert_length value is set to the certificate length when
 * a new certificate is started; the T0 code picks it up as outer limit,
 * and decoding functions use it to ensure that no attempt is made at
 * reading past it. The T0 code also checks that once the certificate is
 * decoded, there are no trailing bytes.
 *
 * The T0 code sets cert_length to 0 when the certificate is fully
 * decoded.
 *
 * The C code must still perform two checks:
 *
 *  -- If the certificate length is 0, then the T0 code will not be
 *  invoked at all. This invalid condition must thus be reported by the
 *  C code.
 *
 *  -- When reaching the end of certificate, the C code must verify that
 *  the certificate length has been set to 0, thereby signaling that
 *  the T0 code properly decoded a certificate.
 *
 * Processing of a chain works in the following way:
 *
 *  -- The error flag is set to a non-zero value when validation is
 *  finished. The value is either BR_ERR_X509_OK (validation is
 *  successful) or another non-zero error code. When a non-zero error
 *  code is obtained, the remaining bytes in the current certificate and
 *  the subsequent certificates (if any) are completely ignored.
 *
 *  -- Each certificate is decoded in due course, with the following
 *  "interesting points":
 *
 *     -- Start of the TBS: the multihash engine is reset and activated.
 *
 *     -- Start of the issuer DN: the secondary hash engine is started,
 *     to process the encoded issuer DN.
 *
 *     -- End of the issuer DN: the secondary hash engine is stopped. The
 *     resulting hash value is computed and then copied into the
 *     next_dn_hash[] buffer.
 *
 *     -- Start of the subject DN: the secondary hash engine is started,
 *     to process the encoded subject DN.
 *
 *     -- For the EE certificate only: the Common Name, if any, is matched
 *     against the expected server name.
 *
 *     -- End of the subject DN: the secondary hash engine is stopped. The
 *     resulting hash value is computed into the pad. It is then processed:
 *
 *        -- If this is the EE certificate, then the hash is ignored
 *        (except for direct trust processing, see later; the hash is
 *        simply left in current_dn_hash[]).
 *
 *        -- Otherwise, the hashed subject DN is compared with the saved
 *        hash value (in saved_dn_hash[]). They must match.
 *
 *     Either way, the next_dn_hash[] value is then copied into the
 *     saved_dn_hash[] value. Thus, at that point, saved_dn_hash[]
 *     contains the hash of the issuer DN for the current certificate,
 *     and current_dn_hash[] contains the hash of the subject DN for the
 *     current certificate.
 *
 *     -- Public key: it is decoded into the cert_pkey[] buffer. Unknown
 *     key types are reported at that point.
 *
 *        -- If this is the EE certificate, then the key type is compared
 *        with the expected key type (initialization parameter). The public
 *        key data is copied to ee_pkey_data[]. The key and hashed subject
 *        DN are also compared with the "direct trust" keys; if the key
 *        and DN are matched, then validation ends with a success.
 *
 *        -- Otherwise, the saved signature (cert_sig[]) is verified
 *        against the saved TBS hash (tbs_hash[]) and that freshly
 *        decoded public key. Failure here ends validation with an error.
 *
 *     -- Extensions: extension values are processed in due order.
 *
 *        -- Basic Constraints: for all certificates except EE, must be
 *        present, indicate a CA, and have a path length compatible with
 *        the chain length so far.
 *
 *        -- Key Usage: for the EE, if present, must allow signatures
 *        or encryption/key exchange, as required for the cipher suite.
 *        For non-EE, if present, must have the "certificate sign" bit.
 *
 *        -- Subject Alt Name: for the EE, dNSName names are matched
 *        against the server name. Ignored for non-EE.
 *
 *        -- Authority Key Identifier, Subject Key Identifier, Issuer
 *        Alt Name, Subject Directory Attributes, CRL Distribution Points
 *        Freshest CRL, Authority Info Access and Subject Info Access
 *        extensions are always ignored: they either contain only
 *        informative data, or they relate to revocation processing, which
 *        we explicitly do not support.
 *
 *        -- All other extensions are ignored if non-critical. If a
 *        critical extension other than the ones above is encountered,
 *        then a failure is reported.
 *
 *     -- End of the TBS: the multihash engine is stopped.
 *
 *     -- Signature algorithm: the signature algorithm on the
 *     certificate is decoded. A failure is reported if that algorithm
 *     is unknown. The hashed TBS corresponding to the signature hash
 *     function is computed and stored in tbs_hash[] (if not supported,
 *     then a failure is reported). The hash OID and length are stored
 *     in cert_sig_hash_oid and cert_sig_hash_len.
 *
 *     -- Signature value: the signature value is copied into the
 *     cert_sig[] array.
 *
 *     -- Certificate end: the hashed issuer DN (saved_dn_hash[]) is
 *     looked up in the trust store (CA trust anchors only); for all
 *     that match, the signature (cert_sig[]) is verified against the
 *     anchor public key (hashed TBS is in tbs_hash[]). If one of these
 *     signatures is valid, then validation ends with a success.
 *
 *  -- If the chain end is reached without obtaining a validation success,
 *  then validation is reported as failed.
 */

#if BR_USE_UNIX_TIME
#include <time.h>
#endif

#if BR_USE_WIN32_TIME
#include <windows.h>
#endif

/*
 * The T0 compiler will produce these prototypes declarations in the
 * header.
 *
void br_x509_minimal_init_main(void *ctx);
void br_x509_minimal_run(void *ctx);
 */

/* see bearssl_x509.h */
void
br_x509_minimal_init(br_x509_minimal_context *ctx,
	const br_hash_class *dn_hash_impl,
	const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num)
{
	memset(ctx, 0, sizeof *ctx);
	ctx->vtable = &br_x509_minimal_vtable;
	ctx->dn_hash_impl = dn_hash_impl;
	ctx->trust_anchors = trust_anchors;
	ctx->trust_anchors_num = trust_anchors_num;
}

static void
xm_start_chain(const br_x509_class **ctx, const char *server_name)
{
	br_x509_minimal_context *cc;
	size_t u;

	cc = (br_x509_minimal_context *)(void *)ctx;
	for (u = 0; u < cc->num_name_elts; u ++) {
		cc->name_elts[u].status = 0;
		cc->name_elts[u].buf[0] = 0;
	}
	memset(&cc->pkey, 0, sizeof cc->pkey);
	cc->num_certs = 0;

src/x509/x509_minimal.t0  view on Meta::CPAN

	read-tag case
		\ UTF8String
		12 of check-primitive read-value-UTF8 endof
		\ NumericString
		18 of check-primitive read-value-latin1 endof
		\ PrintableString
		19 of check-primitive read-value-latin1 endof
		\ TeletexString
		20 of check-primitive read-value-latin1 endof
		\ IA5String
		22 of check-primitive read-value-latin1 endof
		\ BMPString
		30 of check-primitive read-value-UTF16 endof
		2drop read-length-skip 0 0
	endcase ;

\ Read a DN for the EE. The normalized DN hash is computed and stored in the
\ current_dn_hash.
\ Name elements are gathered. Also, the Common Name is matched against the
\ intended server name.
\ Returned value is true (-1) if the CN matches the intended server name,
\ false (0) otherwise.
: read-DN-EE ( lim -- lim bool )
	\ Flag will be set to true if there is a CN and it matches the
	\ intended server name.
	0 { eename-matches }

	\ Activate DN hashing.
	start-dn-hash

	\ Parse the DN structure: it is a SEQUENCE of SET of
	\ AttributeTypeAndValue. Each AttributeTypeAndValue is a
	\ SEQUENCE { OBJECT IDENTIFIER, ANY }.
	read-sequence-open
	begin
		dup while

		read-tag 0x11 check-tag-constructed read-length-open-elt
		dup ifnot ERR_X509_BAD_DN fail then
		begin
			dup while

			read-sequence-open

			\ Read the OID. If the OID could not be read (too
			\ long) then the first pad byte will be 0.
			read-OID drop

			\ If it is the Common Name then we'll need to
			\ match it against the intended server name (if
			\ applicable).
			id-at-commonName eqOID { isCN }

			\ Get offset for reception buffer for that element
			\ (or -1).
			0 offset-name-element { offbuf }

			\ Try to read the value as a string.
			read-string

			\ If the value could be decoded as a string,
			\ copy it and/or match it, as appropriate.
			dup isCN and if
				match-server-name if
					-1 >eename-matches
				then
			then
			offbuf copy-name-element

			\ Close the SEQUENCE
			close-elt

		repeat
		close-elt
	repeat
	close-elt

	\ Compute DN hash and deactivate DN hashing.
	compute-dn-hash

	\ Return the CN match flag.
	eename-matches ;

\ Check the provided validity range against the current (or configured)
\ date and time ("na" = notAfter, "nb = notBefore). Returned value:
\   -1   current date/time is before the notBefore date
\    0   current date/time is within the allowed range
\   +1   current date/time is after the notAfter range
\ If the current date/time is not available, then this function triggers a
\ failure and does not return.
cc: check-validity-range ( na-days na-seconds nb-days nb-seconds -- int ) {
	uint32_t nbs = T0_POP();
	uint32_t nbd = T0_POP();
	uint32_t nas = T0_POP();
	uint32_t nad = T0_POP();
	int r;
	if (CTX->itime != 0) {
		r = CTX->itime(CTX->itime_ctx, nbd, nbs, nad, nas);
		if (r < -1 || r > 1) {
			CTX->err = BR_ERR_X509_TIME_UNKNOWN;
			T0_CO();
		}
	} else {
		uint32_t vd = CTX->days;
		uint32_t vs = CTX->seconds;
		if (vd == 0 && vs == 0) {
#if BR_USE_UNIX_TIME
			time_t x = time(NULL);

			vd = (uint32_t)(x / 86400) + 719528;
			vs = (uint32_t)(x % 86400);
#elif BR_USE_WIN32_TIME
			FILETIME ft;
			uint64_t x;

			GetSystemTimeAsFileTime(&ft);
			x = ((uint64_t)ft.dwHighDateTime << 32)
				+ (uint64_t)ft.dwLowDateTime;
			x = (x / 10000000);
			vd = (uint32_t)(x / 86400) + 584754;
			vs = (uint32_t)(x % 86400);

src/x509/x509_minimal.t0  view on Meta::CPAN

			if (!eqbigint(CTX->pkey.key.rsa.n,
				CTX->pkey.key.rsa.nlen,
				ta->pkey.key.rsa.n,
				ta->pkey.key.rsa.nlen)
				|| !eqbigint(CTX->pkey.key.rsa.e,
				CTX->pkey.key.rsa.elen,
				ta->pkey.key.rsa.e,
				ta->pkey.key.rsa.elen))
			{
				continue;
			}
			break;

		case BR_KEYTYPE_EC:
			if (CTX->pkey.key.ec.curve != ta->pkey.key.ec.curve
				|| CTX->pkey.key.ec.qlen != ta->pkey.key.ec.qlen
				|| memcmp(CTX->pkey.key.ec.q,
					ta->pkey.key.ec.q,
					ta->pkey.key.ec.qlen) != 0)
			{
				continue;
			}
			break;

		default:
			continue;
		}

		/*
		 * Direct trust match!
		 */
		CTX->err = BR_ERR_X509_OK;
		T0_CO();
	}
}

\ Check the signature on the certificate with regards to all trusted CA.
\ We use the issuer hash (in saved_dn_hash[]) as CA identifier.
cc: check-trust-anchor-CA ( -- ) {
	size_t u;

	for (u = 0; u < CTX->trust_anchors_num; u ++) {
		const br_x509_trust_anchor *ta;
		unsigned char hashed_DN[64];

		ta = &CTX->trust_anchors[u];
		if (!(ta->flags & BR_X509_TA_CA)) {
			continue;
		}
		hash_dn(CTX, ta->dn.data, ta->dn.len, hashed_DN);
		if (memcmp(hashed_DN, CTX->saved_dn_hash, DNHASH_LEN)) {
			continue;
		}
		if (verify_signature(CTX, &ta->pkey) == 0) {
			CTX->err = BR_ERR_X509_OK;
			T0_CO();
		}
	}
}

\ Verify RSA signature. This uses the public key that was just decoded
\ into CTX->pkey_data; the modulus and exponent length are provided as
\ parameters. The resulting hash value is compared with the one in
\ tbs_hash. Returned value is 0 on success, or a non-zero error code.
cc: do-rsa-vrfy ( nlen elen -- err ) {
	size_t elen = T0_POP();
	size_t nlen = T0_POP();
	br_x509_pkey pk;

	pk.key_type = BR_KEYTYPE_RSA;
	pk.key.rsa.n = CTX->pkey_data;
	pk.key.rsa.nlen = nlen;
	pk.key.rsa.e = CTX->pkey_data + nlen;
	pk.key.rsa.elen = elen;
	T0_PUSH(verify_signature(CTX, &pk));
}

\ Verify ECDSA signature. This uses the public key that was just decoded
\ into CTX->pkey_dayta; the curve ID and public point length are provided
\ as parameters. The hash value in tbs_hash is used. Returned value is 0
\ on success, or non-zero error code.
cc: do-ecdsa-vrfy ( curve qlen -- err ) {
	size_t qlen = T0_POP();
	int curve = T0_POP();
	br_x509_pkey pk;

	pk.key_type = BR_KEYTYPE_EC;
	pk.key.ec.curve = curve;
	pk.key.ec.q = CTX->pkey_data;
	pk.key.ec.qlen = qlen;
	T0_PUSH(verify_signature(CTX, &pk));
}

cc: print-bytes ( addr len -- ) {
	extern int printf(const char *fmt, ...);
	size_t len = T0_POP();
	unsigned char *buf = (unsigned char *)CTX + T0_POP();
	size_t u;

	for (u = 0; u < len; u ++) {
		printf("%02X", buf[u]);
	}
}

cc: printOID ( -- ) {
	extern int printf(const char *fmt, ...);
	size_t u, len;

	len = CTX->pad[0];
	if (len == 0) {
		printf("*");
		T0_RET();
	}
	printf("%u.%u", CTX->pad[1] / 40, CTX->pad[1] % 40);
	u = 2;
	while (u <= len) {
		unsigned long ul;

		ul = 0;
		for (;;) {
			int x;

			if (u > len) {
				printf("BAD");
				T0_RET();
			}
			x = CTX->pad[u ++];
			ul = (ul << 7) + (x & 0x7F);
			if (!(x & 0x80)) {
				break;
			}
		}
		printf(".%lu", ul);
	}
}

\ Extensions with specific processing.
OID: basicConstraints      2.5.29.19



( run in 2.074 seconds using v1.01-cache-2.11-cpan-98e64b0badf )