HeavyThing - X509.inc

Jeff Marrison

Table of functions

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; X509.inc: just enough X509 goods to deal with the TLS/SSH that I require
	;
	; some notes here:
	;
	; all of my certificate environments contain a private key, and
	; optionally certificates, and optionally public keys.
	;
	; as a result, we provide two ways to load 'em up as it were:
	; X509$new_pem: this reads a PEM file, which is expected to contain
	; ALL the goods (and is how I deploy them in my webserver enviros)
	; e.g. private key along with certificate/intermediates.
	;
	; X509$new_ssh: this will read what is normally /etc/ssh/ssh_host_rsa_key
	; and you can specify alternate locations if you want
	; 
	; What this lets me do is access the guts of it decoded
	; without having to continually base64 decode/search/extract the bits
	; we are after
	;
	; probably not the nicest way to go about it, but serves my lightweight
	; purposes here nicely
	;

X509debug = 0


X509_privatekey_ofs = 0			; for rsa private keys
X509_dsaprivatekey_ofs = 8		; for dsa private keys
X509_pubkey_ofs = 16			; for ssh style rsa public keys (will be a buffer with the binary goods)
X509_dsapubkey_ofs = 24			; for ssh style dsa public keys ("")
X509_certificates_ofs = 32		; list$new or null (signed/certchain/etc)
X509_mtime_ofs = 40			; if we were loaded as a PEM, this is its mtime
X509_checktime_ofs = 48			; ""

X509_size = 56

if used X509$new | defined include_everything
	; no arguments, returns a heap$alloc'd X509 object that is all empty/null
	; don't call this directly, just do the two lines yourself, haha
falign
X509$new:
	prolog	X509$new
	mov	edi, X509_size
	call	heap$alloc_clear
	epilog

end if

if used X509$destroy | defined include_everything
	; single argument in rdi: X509 object to fully destroy
	; cleans up all allocated objects/lists, and heap$free's the X509 object itself
falign
X509$destroy:
	prolog	X509$destroy
	push	rdi
	cmp	qword [rdi+X509_privatekey_ofs], 0
	je	.noprivatekey
	mov	rdi, [rdi+X509_privatekey_ofs]
	call	X509$rsaprivate_destroy
calign
.noprivatekey:
	mov	rdi, [rsp]
	cmp	qword [rdi+X509_dsaprivatekey_ofs], 0
	je	.nodsaprivatekey
	mov	rdi, [rdi+X509_dsaprivatekey_ofs]
	call	X509$dsaprivate_destroy
calign
.nodsaprivatekey:
	mov	rdi, [rsp]
	cmp	qword [rdi+X509_certificates_ofs], 0
	je	.nocertificates
	mov	rdi, [rdi+X509_certificates_ofs]
	mov	rsi, .certclear
	call	list$clear
	mov	rdi, [rsp]
	mov	rdi, [rdi+X509_certificates_ofs]
	call	heap$free
calign
.nocertificates:
	mov	rdi, [rsp]
	cmp	qword [rdi+X509_pubkey_ofs], 0
	je	.nopubkey
	mov	rdi, [rdi+X509_pubkey_ofs]
	call	buffer$destroy
calign
.nopubkey:
	mov	rdi, [rsp]
	cmp	qword [rdi+X509_dsapubkey_ofs], 0
	je	.nodsapubkey
	mov	rdi, [rdi+X509_dsapubkey_ofs]
	call	buffer$destroy
calign
.nodsapubkey:
	pop	rdi
	call	heap$free_clear		; clear probably not necessary here, but for good measure
	epilog
falign
.certclear:
	; called with rdi == our certificate
	push	rbx
	mov	rbx, rdi
	mov	rdi, [rdi+X509cert_der_ofs]
	call	heap$free
	mov	rdi, [rbx+X509cert_serial_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_signature_r_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_signature_s_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_issuercn_ofs]
	call	heap$free
	mov	rdi, [rbx+X509cert_validafter_ofs]
	call	heap$free
	mov	rdi, [rbx+X509cert_validuntil_ofs]
	call	heap$free
	mov	rdi, [rbx+X509cert_subjectcn_ofs]
	call	heap$free
	mov	rdi, [rbx+X509cert_public_n_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_public_e_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_public_p_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_public_q_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_public_g_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_public_dsa_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+X509cert_altnames_ofs]
	mov	rsi, heap$free
	call	list$clear
	mov	rdi, [rbx+X509cert_altnames_ofs]
	call	heap$free
	mov	rdi, [rbx+X509cert_ocspuri_ofs]
	test	rdi, rdi
	jz	.noocspuri
	call	heap$free
.noocspuri:
if used X509$ocsp | defined include_everything
	mov	rdi, [rbx+X509cert_ocspresponse_ofs]
	test	rdi, rdi
	jz	.noocsp
	call	buffer$destroy
.noocsp:
	mov	rdi, [rbx+X509cert_ocsprequest_ofs]
	test	rdi, rdi
	jz	.noocsprequest
	call	buffer$destroy
.noocsprequest:
	mov	rdi, [rbx+X509cert_ocsphost_ofs]
	test	rdi, rdi
	jz	.noocsphost
	call	heap$free
.noocsphost:
	mov	rdi, [rbx+X509cert_ocsptimer_ofs]
	test	rdi, rdi
	jz	.noocsptimer
	call	epoll$timer_clear
.noocsptimer:
end if
	mov	rdi, [rbx+X509cert_caissuers_ofs]
	test	rdi, rdi
	jz	.noissuers
	call	heap$free
.noissuers:
	mov	rdi, rbx
	pop	rbx
	call	heap$free
	ret

end if




if used X509$ocsp | defined include_everything


globals
{
	; if this is set, then every time we receive a valid response, we'll call this
	X509$ocsp_hook	dq	0
}

	; single argument in rdi: an X509 object (with certificates)
	; we will propagate (per certificate) an oscpresponse buffer periodically, for use with OCSP Stapling (RFC2560)
falign
X509$ocsp:
	prolog	X509$ocsp
	mov	rdx, rdi
	mov	rdi, [rdi+X509_certificates_ofs]
	mov	rsi, .doit
	call	list$foreach_arg
	epilog
falign
.doit:
	; rdi == X509cert_* object, rsi == parent X509 object
	cmp	qword [rdi+X509cert_ocspuri_ofs], 0
	je	.nothingtodo
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	mov	r15, rsi

	; create our OCSP request for this certificate, this takes a bit of grunt, but we only have to do it once
	call	buffer$new
	mov	[rbx+X509cert_ocsprequest_ofs], rax
	mov	r12, rax

if X509_ocsp_sha256
	; we use SHA256, which is 32 bytes in length, so our total length is: 10 + seriallength + (32 + 2) * 2 + 9 + 6
	; 93 bytes + seriallength
	mov	r13d, [rbx+X509cert_seriallength_ofs]
	add	r13d, 93
else
	; we use SHA160, which is 20 bytes in length, so our total length is: 10 + seriallength + (20 + 2) * 2 + 5 + 6
	; 65 bytes + seriallength
	mov	r13d, [rbx+X509cert_seriallength_ofs]
	add	r13d, 65
end if

macro ocspseqpreface n* {
	mov	rdi, r12
	mov	esi, 0x30
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, r13d
	sub	esi, n
	call	buffer$append_byte
}
	ocspseqpreface 2
	ocspseqpreface 4
	ocspseqpreface 6
	ocspseqpreface 8
	ocspseqpreface 10

if X509_ocsp_sha256
	; next up, our AlgorithmIdentifier, which is 0x30 + 0x0d + 0x06 + 0x09 + 9 byte OID + 0x05 + 0x00
	mov	rdi, r12
	mov	esi, 0x30
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x0d
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x06
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x09
	call	buffer$append_byte
	mov	rdi, r12
	mov	rsi, .algoid
	mov	edx, 9
	call	buffer$append
	mov	rdi, r12
	mov	esi, 0x05
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x00
	call	buffer$append_byte
	
	; next up is 0x04 + 0x20
	mov	rdi, r12
	mov	esi, 0x04
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x20
	call	buffer$append_byte

	; and the hash of hte issuerdn is next, and we have that in this cert
	sub	rsp, sha256_state_size + 32
	mov	rdi, rsp
	call	sha256$init
	mov	eax, [rbx+X509cert_issuerdnstart_ofs]
	mov	edx, [rbx+X509cert_issuerdnlength_ofs]
	mov	rsi, [rbx+X509cert_der_ofs]
	mov	rdi, rsp
	add	rsi, rax
	call	sha256$update
	mov	rdi, rsp
	lea	rsi, [rsp+sha256_state_size]
	xor	edx, edx
	call	sha256$final
	mov	rdi, r12
	lea	rsi, [rsp+sha256_state_size]
	mov	edx, 32
	call	buffer$append

	; next up is 0x04 + 0x20
	mov	rdi, r12
	mov	esi, 0x04
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x20
	call	buffer$append_byte

else
	; next up, our AlgorithmIdentifier, which is 0x30 + 0x09 + 0x06 + 0x05 + 5 byte OID + 0x05 + 0x00
	mov	rdi, r12
	mov	esi, 0x30
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x09
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x06
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x05
	call	buffer$append_byte
	mov	rdi, r12
	mov	rsi, .algoid
	mov	edx, 5
	call	buffer$append
	mov	rdi, r12
	mov	esi, 0x05
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x00
	call	buffer$append_byte
	
	; next up is 0x04 + 0x14
	mov	rdi, r12
	mov	esi, 0x04
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x14
	call	buffer$append_byte

	; and the hash of hte issuerdn is next, and we have that in this cert
	sub	rsp, sha160_state_size + 32
	mov	rdi, rsp
	call	sha160$init
	mov	eax, [rbx+X509cert_issuerdnstart_ofs]
	mov	edx, [rbx+X509cert_issuerdnlength_ofs]
	mov	rsi, [rbx+X509cert_der_ofs]
	mov	rdi, rsp
	add	rsi, rax
	call	sha160$update
	mov	rdi, rsp
	lea	rsi, [rsp+sha160_state_size]
	xor	edx, edx
	call	sha160$final
	mov	rdi, r12
	lea	rsi, [rsp+sha160_state_size]
	mov	edx, 20
	call	buffer$append

	; next up is 0x04 + 0x14
	mov	rdi, r12
	mov	esi, 0x04
	call	buffer$append_byte
	mov	rdi, r12
	mov	esi, 0x14
	call	buffer$append_byte


end if

	; next up is our hash of the issuerpublickey, which is considerably more complicated

	; so the first option is: this issuer exists as another certificate in our certificates list itself
	; [rbx+X509cert_issuercn_ofs] string is what we are looking for in the X509cert_subjectcn_ofs of the certificates list
	mov	rdi, [r15+X509_certificates_ofs]
	mov	r13, [rdi+_list_first_ofs]
	; we know there is at least one certificate here
calign
.issuersearch:
	mov	rdx, [r13+_list_valueofs]
	mov	rdi, [rbx+X509cert_issuercn_ofs]
	mov	rsi, [rdx+X509cert_subjectcn_ofs]
	call	string$equals
	test	eax, eax
	jnz	.issuersearch_found
	mov	r13, [r13+_list_nextofs]
	test	r13, r13
	jnz	.issuersearch
	; if we made it to here, we were unable to locate the issuer in our own certificate list
	; so, we need a string replacement with ' ' -> '_', and then add '.pem' to the end of the name we are after
	; and see if it exists in /etc/ssl/certs
	mov	rdi, [rbx+X509cert_issuercn_ofs]
	mov	rsi, .spacestr
	mov	rdx, .underscorestr
	call	string$replace
	mov	r13, rax
	mov	rdi, rax
	mov	rsi, .pemfilestr
	call	string$concat
	mov	rdi, r13
	mov	r13, rax
	call	heap$free
	mov	rdi, .etcsslcerts
	mov	rsi, r13
	call	string$concat
	mov	rdi, r13
	mov	r13, rax
	call	heap$free

if X509debug
	mov	rdi, r13
	call	string$to_stdoutln
end if

	mov	rdi, r13
	call	X509$new_pem
	test	rax, rax
	jz	.issuer_pemfile_error
	mov	rdi, r13
	mov	r13, rax
	call	heap$free
	; so, we have an X509 object sitting in r13, make sure it has a certificates list, and that said list is non-empty, then search for our original
	; issuercn
	mov	rdi, [r13+X509_certificates_ofs]
	test	rdi, rdi
	jz	.issuer_x509_error
	mov	r14, [rdi+_list_first_ofs]
calign
.issuersearch2:
	mov	rdx, [r14+_list_valueofs]
	mov	rdi, [rbx+X509cert_issuercn_ofs]
	mov	rsi, [rdx+X509cert_subjectcn_ofs]
	call	string$equals
	test	eax, eax
	jnz	.issuersearch_found2
	mov	r14, [r14+_list_nextofs]
	test	r14, r14
	jnz	.issuersearch2
calign
.issuer_x509_error:
	; if we made it to here, no deal
	mov	rdi, r13
	call	X509$destroy
	; destroy our buffer in r12 and clear rbx's ocsprequest
	mov	rdi, r12
	call	buffer$destroy
	mov	qword [rbx+X509cert_ocsprequest_ofs], 0
if X509_ocsp_sha256
	add	rsp, sha256_state_size + 32
else
	add	rsp, sha160_state_size + 32
end if
	pop	r15 r14 r13 r12 rbx
	ret
calign
.issuer_pemfile_error:
	mov	rdi, r13
	call	heap$free
	; destroy our buffer in r12 and clear rbx's ocsprequest
	mov	rdi, r12
	call	buffer$destroy
	mov	qword [rbx+X509cert_ocsprequest_ofs], 0
if X509_ocsp_sha256
	add	rsp, sha256_state_size + 32
else
	add	rsp, sha160_state_size + 32
end if
	pop	r15 r14 r13 r12 rbx
	ret
calign
.issuersearch_found:
	; X509cert object at [r13+_list_valueofs] contains the public key we were looking for
	mov	rcx, [r13+_list_valueofs]

	mov	eax, [rcx+X509cert_publickeystart_ofs]
	mov	edx, [rcx+X509cert_publickeylength_ofs]
	mov	rsi, [rcx+X509cert_der_ofs]
	mov	rdi, rsp
	add	rsi, rax
if X509_ocsp_sha256
	call	sha256$update
	mov	rdi, rsp
	lea	rsi, [rsp+sha256_state_size]
	xor	edx, edx
	call	sha256$final
	mov	rdi, r12
	lea	rsi, [rsp+sha256_state_size]
	mov	edx, 32
	call	buffer$append

	add	rsp, sha256_state_size + 32
else
	call	sha160$update
	mov	rdi, rsp
	lea	rsi, [rsp+sha160_state_size]
	xor	edx, edx
	call	sha160$final
	mov	rdi, r12
	lea	rsi, [rsp+sha160_state_size]
	mov	edx, 20
	call	buffer$append
	
	add	rsp, sha160_state_size + 32
end if
	jmp	.doserial

calign
.issuersearch_found2:
	; X509cert object at [r14+_list_valueofs] contains the public key we were looking for
	mov	rcx, [r14+_list_valueofs]
	
	mov	eax, [rcx+X509cert_publickeystart_ofs]
	mov	edx, [rcx+X509cert_publickeylength_ofs]
	mov	rsi, [rcx+X509cert_der_ofs]
	mov	rdi, rsp
	add	rsi, rax
if X509_ocsp_sha256
	call	sha256$update
	mov	rdi, rsp
	lea	rsi, [rsp+sha256_state_size]
	xor	edx, edx
	call	sha256$final
	mov	rdi, r12
	lea	rsi, [rsp+sha256_state_size]
	mov	edx, 32
	call	buffer$append

	add	rsp, sha256_state_size + 32
else
	call	sha160$update
	mov	rdi, rsp
	lea	rsi, [rsp+sha160_state_size]
	xor	edx, edx
	call	sha160$final
	mov	rdi, r12
	lea	rsi, [rsp+sha160_state_size]
	mov	edx, 20
	call	buffer$append

	add	rsp, sha160_state_size + 32
end if

	mov	rdi, r13
	call	X509$destroy
	; fallthrough to doserial
calign
.doserial:
	; last but not least, we need the serial from rbx
	mov	eax, [rbx+X509cert_serialstart_ofs]
	mov	edx, [rbx+X509cert_seriallength_ofs]
	mov	rsi, [rbx+X509cert_der_ofs]
	add	rsi, rax
	mov	rdi, r12
	call	buffer$append

if X509debug
	mov	rdi, .ocspreqmsg
	call	string$to_stdoutln
	mov	rdi, [r12+buffer_itself_ofs]
	mov	rsi, [r12+buffer_length_ofs]
	call	string$from_bintohex
	push	rax
	mov	rdi, rax
	call	string$to_stdoutln
	pop	rdi
	call	heap$free
end if

	; so now, we have a DER encoded OCSP request sitting in r12/[rbx+X509cert_ocsprequest_ofs]
	; next thing we have to do is actually fire up the initial OCSP request
	
	; so, we could use our existing webclient layer to do it for us, but that is _way_ overkill
	; for what we need, so to keep it as lightweight as possible, we'll do the DNS/epoll action
	; ourselves directly from in here
	xor	edi, edi
	mov	rsi, [rbx+X509cert_ocspuri_ofs]
	call	url$new
	test	rax, rax
	jz	.ocspuri_error
	mov	r13, rax
	mov	rdi, [rax+url_host_ofs]
	call	string$copy
	mov	[rbx+X509cert_ocsphost_ofs], rax
	mov	rdi, r13
	call	url$destroy
if X509_ocsp_syslog
	mov	rdi, .initialsyslogpreface
	mov	rsi, [rbx+X509cert_ocspuri_ofs]
	call	string$concat
	mov	r13, rax
	mov	edi, log_notice
	mov	rsi, rax
	call	syslog
	mov	rdi, r13
	call	heap$free
end if
	; dns is next
	mov	rdi, [rbx+X509cert_ocsphost_ofs]
	mov	rsi, .dns_success
	mov	rdx, .dns_failure
	mov	rcx, rbx
	call	dns$lookup_ipv4
	pop	r15 r14 r13 r12 rbx
	ret
if X509_ocsp_syslog
cleartext .initialsyslogpreface, 'OCSP request to '
end if
calign
.ocspuri_error:
	mov	rdi, r12
	call	buffer$destroy
	mov	qword [rbx+X509cert_ocsprequest_ofs], 0
	pop	r15 r14 r13 r12 rbx
	ret
	
calign
.nothingtodo:
	ret
cleartext .spacestr, ' '
cleartext .underscorestr, '_'
cleartext .pemfilestr, '.pem'
cleartext .etcsslcerts, '/etc/ssl/certs/'
if X509debug
cleartext .ocspreqmsg, 'OCSP Request DER Buffer:'
end if
dalign
.algoid:
if X509_ocsp_sha256
	db	0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
else
	db	0x2b, 0x0e, 0x03, 0x02, 0x1a
end if

	; sha160: 0x2b, 0x0e, 0x03, 0x02, 0x1a
	; sha224: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04
	; sha256: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
	; sha384: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02
	; sha512: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03


dalign
.ocsp_outbound_vtable:
	dq	epoll$destroy, epoll$clone, .ocsp_outbound_connected, epoll$send, .ocsp_outbound_receive, .ocsp_outbound_error, io$timeout
falign
.dns_success:
	; despite being declared inline here, this is a standalone function
	; rdi == host string that we passed to dns, rsi == our X509 object, rdx == result
	; we need to issue our connect
	push	rsi rdx

if X509debug
	call	string$to_stdoutln
	mov	rdi, [rsp]
	mov	esi, 16
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	call	string$to_stdoutln
	pop	rdi
	call	heap$free

	mov	rdi, .ocspdnssuccess
	call	string$to_stdoutln
end if
	mov	rdi, .ocsp_outbound_vtable
	mov	esi, 8
	call	epoll$new
	pop	rdx rsi
	mov	[rax+epoll_base_size], rsi
	; setup our remote address
	sub	rsp, sockaddr_in_size + 8
	mov	[rsp+sockaddr_in_size], rsi
	mov	ecx, 80
	mov	word [rsp], 2		; AF_INET/PF_INET
	xchg	ch, cl
	mov	word [rsp+2], cx	; htons(port)
	mov	dword [rsp+4], edx
	mov	qword [rsp+8], 0
	mov	rdi, rsp
	mov	esi, sockaddr_in_size
	mov	rdx, rax
	call	epoll$outbound
	; in the event that _failed miserably, we need to do a retry all over again
	test	eax, eax
	jz	.dns_success_connectfailed
	add	rsp, sockaddr_in_size + 8
	ret
calign
.dns_success_connectfailed:
if X509debug
	mov	rdi, .ocspconnectfail
	call	string$to_stdoutln
end if
	mov	edi, io_base_size + 8
	call	heap$alloc_clear
	mov	rdx, [rsp+sockaddr_in_size]
	mov	qword [rax], .retry_vtable
	mov	[rax+io_base_size], rdx
	mov	edi, X509_ocsp_retry
	mov	rsi, rax
	call	epoll$timer_new
	add	rsp, sockaddr_in_size + 8
	ret
if X509debug
cleartext .ocspdnssuccess, 'OCSP DNS lookup succeeded'
cleartext .ocspconnectfail, 'OCSP outbound connection failed.'
cleartext .ocspdnsfailure, 'OCSP DNS lookup failed: '
end if
dalign
.retry_vtable:
	dq	io$destroy, io$clone, io$connected, io$send, io$receive, io$error, .dns_retry
falign
.dns_failure:
if X509debug
	push	rsi rdi
	mov	rdi, .ocspdnsfailure
	call	string$to_stdout
	pop	rdi
	call	string$to_stdoutln
	pop	rsi
end if
	; despite being declared inline here, this is a standalone function
	; we need to retry again every 5 minutes, or until we are destroyed
	push	rsi
	mov	edi, io_base_size + 8
	call	heap$alloc_clear
	pop	rdx
	mov	qword [rax], .retry_vtable
	mov	[rax+io_base_size], rdx
	mov	edi, X509_ocsp_retry
	mov	rsi, rax
	push	rdx
	call	epoll$timer_new
	pop	rdx
	mov	[rdx+X509cert_ocsptimer_ofs], rax
	ret
falign
.dns_retry:
if X509debug
	push	rdi
	mov	rdi, .dnsretry1
	call	string$to_stdout
	mov	rdi, [rsp]
	mov	r8, [rdi+io_base_size]
	mov	rdi, [r8+X509cert_ocsphost_ofs]
	call	string$to_stdoutln
	mov	rdi, [rsp]
end if
	; object in rdi == our simple io object, at which io_base_size is our X509 pointer
	mov	r9, rdi
	mov	r8, [rdi+io_base_size]
	mov	rdi, [r8+X509cert_ocsphost_ofs]
	mov	rsi, .dns_success
	mov	rdx, .dns_failure
	mov	rcx, r8
	mov	qword [r8+X509cert_ocsptimer_ofs], 0
	call	dns$lookup_ipv4
if X509debug
	mov	rdi, .dnsretry2
	call	string$to_stdout
	mov	rdi, [rsp]
	mov	r8, [rdi+io_base_size]
	mov	rdi, [r8+X509cert_ocsphost_ofs]
	call	string$to_stdoutln
	mov	rdi, [rsp]

	pop	rdi
end if
	mov	eax, 1		; kill off our io object
	ret
if X509debug
cleartext .dnsretry1, '--------- .dns_retry for host: '
cleartext .dnsretry2, '********* after dns$lookup_ipv4, host is: '
end if
falign
.ocsp_outbound_error:
	; again, despite this being declared inline, this is a standalone function also
	; create a new retry timer
	push	qword [rdi+epoll_base_size]	; our X509cert object

if X509_ocsp_syslog
	mov	rdx, [rdi+epoll_base_size]
	mov	rdi, .errorsyslogpreface
	mov	rsi, [rdx+X509cert_ocspuri_ofs]
	call	string$concat
	push	rax
	mov	edi, log_notice
	mov	rsi, rax
	call	syslog
	pop	rdi
	call	heap$free
end if
	mov	edi, io_base_size + 8
	call	heap$alloc_clear
	pop	rdx
	mov	qword [rax], .retry_vtable
	mov	[rax+io_base_size], rdx
	mov	edi, X509_ocsp_retry
	mov	rsi, rax
	push	rdx
	call	epoll$timer_new
	pop	rdx
	mov	[rdx+X509cert_ocsptimer_ofs], rax
	ret
if X509_ocsp_syslog
cleartext .errorsyslogpreface, 'OCSP fetch ERROR from '
cleartext .connectsyslogpreface1, 'OCSP fd: '
cleartext .connectsyslogpreface2, ' fetching from '
end if
falign
.ocsp_outbound_connected:
	; again despite this being declared inline, this is a standalone function also
	; rdi == our epoll object, [rdi+epoll_base_size] is our X509cert object
	push	rbx r12 r13 r14
	; we need to construct a mimelike object for our OCSP request
	mov	rbx, rdi
	mov	r12, [rdi+epoll_base_size]
	call	mimelike$new
	mov	r13, rax

	mov	rdi, rax
	mov	rsi, mimelike$connection
	mov	rdx, .close
	call	mimelike$setheader

	mov	rdi, r13
	mov	rsi, .host
	mov	rdx, [r12+X509cert_ocsphost_ofs]
	call	mimelike$setheader

	mov	rsi, [r12+X509cert_ocspuri_ofs]
	xor	edi, edi
	call	url$new
	mov	r14, rax

if X509_ocsp_syslog
	mov	edi, [rbx+epoll_fd_ofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, .connectsyslogpreface1
	mov	rsi, rax
	call	string$concat
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rdi, [rsp]
	mov	rsi, .connectsyslogpreface2
	call	string$concat
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rdi, [rsp]
	mov	rsi, [r12+X509cert_ocspuri_ofs]
	call	string$concat
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	edi, log_notice
	mov	rsi, [rsp]
	call	syslog
	pop	rdi
	call	heap$free
end if

	mov	rdi, r14
	call	url$topreface
	mov	rdi, r14
	mov	r14, rax
	call	url$destroy
	
	; turn it into a valid HTTP POST preface
	mov	rdi, .postpreface
	mov	rsi, r14
	call	string$concat
	mov	rdi, r14
	mov	r14, rax
	call	heap$free

	mov	rdi, r14
	mov	rsi, .prefaceend
	call	string$concat
	mov	rdi, r14
	mov	r14, rax
	call	heap$free

	mov	rdi, r13
	mov	rsi, r14
	call	mimelike$setpreface_nocopy

	mov	rdi, r13
	mov	rsi, mimelike$contenttype
	mov	rdx, .ctype
	call	mimelike$setheader

if defined X509_add_content_transfer_encoding
	mov	rdi, r13
	mov	rsi, mimelike$contenttransferencoding
	mov	rdx, mimelike$binary
	call	mimelike$setheader
end if

	mov	rcx, [r12+X509cert_ocsprequest_ofs]
	mov	rdi, [rcx+buffer_length_ofs]
	mov	esi, 10
	call	string$from_unsigned
	mov	rdi, r13
	mov	rsi, mimelike$contentlength
	mov	rdx, rax
	call	mimelike$setheader_novaluecopy

	; set the body
	mov	rcx, [r12+X509cert_ocsprequest_ofs]
	mov	rdi, r13
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	mimelike$setbody

	mov	rdi, r13
	xor	esi, esi
	call	mimelike$compose

if X509debug
	mov	rdi, .ocspsend
	call	string$to_stdoutln
	mov	rcx, [r13+mimelike_xmitbody_ofs]
	mov	eax, syscall_write
	mov	edi, 1
	mov	rsi, [rcx+buffer_itself_ofs]
	; mov	rdx, [rcx+buffer_length_ofs]
	mov	rdx, [r13+mimelike_hdrlen_ofs]
	syscall
end if
	mov	rcx, [r13+mimelike_xmitbody_ofs]
	mov	rdi, rbx
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	mov	rcx, [rdi]
	call	qword [rcx+io_vsend]

	mov	rdi, r13
	call	mimelike$destroy

if X509debug
	mov	rdi, .ocspsend2
	call	string$to_stdout
	mov	rdi, [r12+X509cert_ocsphost_ofs]
	call	string$to_stdoutln
end if

	pop	r14 r13 r12 rbx
	ret
cleartext .close, 'close'
cleartext .host, 'Host'
cleartext .postpreface, 'POST '
cleartext .prefaceend, ' HTTP/1.1'
cleartext .ctype, 'application/ocsp-request'
if X509debug
cleartext .ocspsend, 'SENDING OCSP HTTP REQUEST:'
cleartext .ocspsend2, '  AFTER REQUEST SEND, ocsphost is: '
cleartext .ocspreceive, 'RECEIVED OCSP RESPONSE:'
cleartext .ocspreceive2, '.ocsp_outbound_receive, ocsphost is: '
cleartext .ocspreceive3, '  after complete receive, ocsphost is: '
end if


falign
.ocsp_outbound_receive:
	; same with this one, standalone, called for our ocsp_outbound connection
	push	rbx r12 r13
	mov	rbx, rdi
	mov	r12, [rdi+epoll_base_size]	; our X509cert object

if X509debug
	mov	rdi, .ocspreceive2
	call	string$to_stdout
	mov	rdi, [r12+X509cert_ocsphost_ofs]
	call	string$to_stdoutln
end if

	; so we got back a reply, which may have been a partial reply and that we need to wait
	; for more data to arrive
	mov	r8, [rbx+epoll_inbuf_ofs]
	xor	edx, edx
	mov	ecx, 1
	mov	rdi, [r8+buffer_itself_ofs]
	mov	rsi, [r8+buffer_length_ofs]
	xor	r8d, r8d
	call	mimelike$new_parse
	test	rax, rax
	jz	.ocsp_outbound_receive_needmore
	; otherwise the parse was successful, so now we need to copy the body from this to our ocspresponse X509cert buffer
	mov	r13, rax
	cmp	qword [r12+X509cert_ocspresponse_ofs], 0
	jne	.ocsp_outbound_receive_nonewbuffer
	call	buffer$new
	mov	[r12+X509cert_ocspresponse_ofs], rax
calign
.ocsp_outbound_receive_nonewbuffer:

if X509debug
	mov	rdi, .ocspreceive
	call	string$to_stdoutln
	mov	rcx, [rbx+epoll_inbuf_ofs]
	mov	eax, syscall_write
	mov	edi, 1
	mov	rsi, [rcx+buffer_itself_ofs]
	; mov	rdx, [rcx+buffer_length_ofs]
	mov	rdx, [r13+mimelike_hdrlen_ofs]
	syscall
end if

	mov	rdi, [r12+X509cert_ocspresponse_ofs]
	call	buffer$reset
	mov	rcx, [r13+mimelike_body_ofs]
	mov	rdi, [r12+X509cert_ocspresponse_ofs]
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	buffer$append

	cmp	qword [X509$ocsp_hook], 0
	je	.ocsp_outbound_receive_skiphook
	mov	rdi, r12
	call	qword [X509$ocsp_hook]
calign
.ocsp_outbound_receive_skiphook:

	; destroy the mimelike object
	mov	rdi, r13
	call	mimelike$destroy

	; we need to schedule a new timer so that our OCSP goods get updated periodically

	; create a new refresh timer
	mov	edi, io_base_size + 8
	call	heap$alloc_clear
	mov	rdx, r12
	mov	qword [rax], .retry_vtable
	mov	[rax+io_base_size], rdx
	mov	edi, X509_ocsp_refresh
	mov	rsi, rax
if X509debug
	mov	r13, rax
end if
	call	epoll$timer_new
	mov	[r12+X509cert_ocsptimer_ofs], rax

if X509debug
	mov	rdi, .refreshtimerdebug
	call	string$to_stdout
	mov	rdi, [r12+X509cert_ocsptimer_ofs]
	mov	esi, 16
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	call	string$to_stdout
	pop	rdi
	call	heap$free
	mov	rdi, .refreshtimerdebug2
	call	string$to_stdout
	mov	rdi, r13
	mov	esi, 16
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	call	string$to_stdoutln
	pop	rdi
	call	heap$free
end if
	
if X509debug
	mov	rdi, .ocspreceive3
	call	string$to_stdout
	mov	rdi, [r12+X509cert_ocsphost_ofs]
	call	string$to_stdoutln
end if


	mov	eax, 1		; go ahead and kill us off
	pop	r13 r12 rbx
	ret
if X509debug
cleartext .refreshtimerdebug, 'Created new refresh timer object at: '
cleartext .refreshtimerdebug2, ' with our io object at: '
end if
calign
.ocsp_outbound_receive_needmore:
	pop	r13 r12 rbx
	xor	eax, eax	; don't kill our connection just yet
	ret



end if





dsaprivate_p_ofs = 0			; bigint
dsaprivate_q_ofs = 8			; bigint
dsaprivate_g_ofs = 16			; bigint
dsaprivate_y_ofs = 24			; bigint (dsa public)
dsaprivate_x_ofs = 32			; bigint (dsa private)

dsaprivate_size = 40


if used X509$dsaprivate_destroy | defined include_everything
	; single argument in rdi: our dsaprivate object
falign
X509$dsaprivate_destroy:
	prolog	X509$dsaprivate_destroy
	push	rbx
	mov	rbx, rdi
	mov	rdi, [rbx+dsaprivate_p_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+dsaprivate_q_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+dsaprivate_g_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+dsaprivate_y_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+dsaprivate_x_ofs]
	call	bigint$destroy_clear
	mov	rdi, rbx
	call	heap$free_clear
	pop	rbx
	epilog
end if


macro asn1_tag {
	; assumed that r12 == buffer, r13 == _remaining bytes_ left in r12 (which we'll decrement as we consume them)
	; this will populate the tag itself in eax, raw tag in ecx, length in r8d, and update r13/r12 as we go
	; if we were all jacked up, or an error occurred, etc, eax will be -1
	; we mash r8, r9, r10, set eax, ecx
	local	.length,.kakked,.done
	cmp	r13, 2
	jle	.kakked
	movzx	eax, byte [r12]
	mov	ecx, eax
	and	eax, 0x1f
	add	r12, 1
	sub	r13, 1
	; decode the length as well
	movzx	r8d, byte [r12]
	add	r12, 1
	sub	r13, 1
	test	r8d, 0x80
	; length in r8d is fine
	jz	.done
	and	r8d, 0x7f	; length bytes
	test	r8d, r8d
	jz	.done
	mov	r9d, r8d
	xor	r8d, r8d
calign
.length:
	test	r13, r13
	jz	.kakked
	movzx	r10d, byte [r12]
	add	r12, 1
	sub	r13, 1
	shl	r8, 8
	or	r8, r10
	sub	r9d, 1
	jnz	.length
	jmp	.done
calign
.kakked:
	mov	eax, -1
calign
.done:
}



if used X509$add_dsaprivatekey | defined include_everything
	; three arguments: rdi == X509 object to add to, rsi == DER encoded buffer, rdx == length of same
	; returns bool in eax as to whether we succeeded or not
falign
X509$add_dsaprivatekey:
	prolog	X509$add_dsaprivatekey
	sub	rsp, 64
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], rdx
	; we need some of our callee-saves
	mov	[rsp+24], rbx
	mov	[rsp+32], r12
	mov	[rsp+40], r13
	mov	rbx, rdi
	mov	r12, rsi
	mov	r13, rdx
	call	list$new
	mov	[rsp+48], rax		; our "undo" list in case we have to unwind all this, makes it much cleaner
	mov	edi, dsaprivate_size
	call	heap$alloc_clear
	mov	[rsp+56], rax		; our dsaprivate object

	; first things first, we expect a sequence
	call	.gettag
	cmp	eax, 0x10		; we need it to start iwth a sequence, or die.
	jne	.kakked
	; version is first up
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	; openssl seems to produce version == 0
	cmp	r8d, 1
	jne	.kakked
	test	r13, r13
	jz	.kakked			; no more data left?
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; we aren't presently checking the actual value of our single byte version here... though everything i have it is zero
	; cmp	eax, 0
	; ja	.kakked

	; p is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	; otherwise, we should have a [large-ish] integer sitting here
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	[rdi+dsaprivate_p_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	rdi, [rdi+dsaprivate_p_ofs]
	call	bigint$debug
end if

	; q is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	[rdi+dsaprivate_q_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	rdi, [rdi+dsaprivate_q_ofs]
	call	bigint$debug
end if

	; g is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked	
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	[rdi+dsaprivate_g_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	rdi, [rdi+dsaprivate_g_ofs]
	call	bigint$debug
end if

	; y is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked	
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	[rdi+dsaprivate_y_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	rdi, [rdi+dsaprivate_y_ofs]
	call	bigint$debug
end if

	; last but not least, x
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked	
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	[rdi+dsaprivate_x_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our dsaprivate object
	mov	rdi, [rdi+dsaprivate_x_ofs]
	call	bigint$debug
end if

	mov	rdi, [rsp+48]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rsp+48]
	call	heap$free_clear
	
	; last but not least, we need to add our rsaprivate object to our X509 object
	mov	rdi, [rsp]
	mov	rsi, [rsp+56]
	mov	[rdi+X509_dsaprivatekey_ofs], rsi

	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]
	add	rsp, 64
	mov	eax, 1			; success.
	epilog
calign
.kakked:
	mov	rdi, [rsp+48]
	mov	rsi, bigint$destroy_clear
	call	list$clear
	mov	rdi, [rsp+48]
	call	heap$free_clear
	mov	rdi, [rsp+56]
	call	heap$free_clear

	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]
	add	rsp, 64
	xor	eax, eax		; fail.
	epilog
falign
.gettag:
	asn1_tag
	ret


end if




rsaprivate_n_ofs = 0			; bigint
rsaprivate_e_ofs = 8			; bigint
rsaprivate_d_ofs = 16			; bigint
rsaprivate_p_ofs = 24			; bigint
rsaprivate_q_ofs = 32			; bigint
rsaprivate_dmodp_ofs = 40		; bigint
rsaprivate_dmodq_ofs = 48		; bigint
rsaprivate_invqmodp_ofs = 56		; bigint
rsaprivate_x_ofs = 64			; bigint
rsaprivate_y_ofs = 72
rsaprivate_z_ofs = 80
rsaprivate_other_ofs = 88		; list

rsaprivate_size = 96

rsaprivate_ri_ofs = 0			; bigint
rsaprivate_di_ofs = 8			; bigint
rsaprivate_ti_ofs = 16			; bigint

rsaprivate_other_size = 24

if used X509$rsaprivate_destroy | defined include_everything

	; single argument in rdi: our rsaprivate object
falign
X509$rsaprivate_destroy:
	prolog	X509$rsaprivate_destroy
	push	rbx
	mov	rbx, rdi
	; all must be valid by nature except for other, which is optional
	mov	rdi, [rbx+rsaprivate_n_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+rsaprivate_e_ofs]
	call	bigint$destroy
	mov	rdi, [rbx+rsaprivate_d_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+rsaprivate_p_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+rsaprivate_q_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+rsaprivate_dmodp_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+rsaprivate_dmodq_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+rsaprivate_invqmodp_ofs]
	call	bigint$destroy_clear
	mov	rdi, [rbx+rsaprivate_x_ofs]
	call	bigint$destroy
	mov	rsi, [rbx+rsaprivate_y_ofs]
	call	bigint$destroy
	mov	rsi, [rbx+rsaprivate_z_ofs]
	call	bigint$destroy
	cmp	qword [rbx+rsaprivate_other_ofs], 0
	je	.no_other
	mov	rdi, [rbx+rsaprivate_other_ofs]
	mov	rsi, .otherclear
	call	list$clear
	mov	rdi, [rbx+rsaprivate_other_ofs]
	call	heap$free_clear			; clear not necessary here but for good measure
calign
.no_other:
	mov	rdi, rbx
	call	heap$free_clear			; clear not necessary here but for good measure
	pop	rbx
	epilog
calign
.otherclear:
	; rdi is our other item
	push	r15
	mov	r15, rdi
	mov	rdi, [r15+rsaprivate_ri_ofs]
	call	bigint$destroy_clear
	mov	rdi, [r15+rsaprivate_di_ofs]
	call	bigint$destroy_clear
	mov	rdi, [r15+rsaprivate_ti_ofs]
	call	bigint$destroy_clear
	pop	rdi
	call	heap$free_clear			; clear not necessary here but for good measure
	ret
end if




if used X509$add_privatekey | defined include_everything
	; three arguments: rdi == X509 object to add to, rsi == DER encoded buffer, rdx == length of same
	; returns bool in eax as to whether we succeeded or not
falign
X509$add_privatekey:
	prolog	X509$add_privatekey
	sub	rsp, 64
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], rdx
	; we need some of our callee-saves
	mov	[rsp+24], rbx
	mov	[rsp+32], r12
	mov	[rsp+40], r13
	mov	r12, rsi
	mov	r13, rdx
	call	list$new
	mov	[rsp+48], rax		; our "undo" list in case we have to unwind all this, makes it much cleaner
	mov	edi, rsaprivate_size
	call	heap$alloc_clear
	mov	rbx, rax

	; from RFC 3447

	; first things first, we expect a sequence
	call	.gettag
	cmp	eax, 0x10		; we need it to start iwth a sequence, or die.
	jne	.kakked
	; version is first up
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	; now, its length... hmmm I say:
	; because the RFC says 0 or 1 only, the lenght MUST be one byte
	cmp	r8d, 1
	jne	.kakked
	test	r13, r13
	jz	.kakked			; no more data left?
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	cmp	eax, 1
	ja	.kakked
	; n (modulus) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	; otherwise, we should have a [large-ish] integer sitting here
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_n_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rbx+rsaprivate_n_ofs]
	call	bigint$debug
end if

	; e (publicExponent) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_e_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rbx+rsaprivate_e_ofs]
	call	bigint$debug
end if

	; d (privateExponent) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked	
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_d_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rbx+rsaprivate_d_ofs]
	call	bigint$debug
end if
	; p (prime1) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_p_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
if X509debug
	mov	rdi, [rbx+rsaprivate_p_ofs]
	call	bigint$debug
end if

	; q (prime2) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_q_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
if X509debug
	mov	rdi, [rbx+rsaprivate_q_ofs]
	call	bigint$debug
end if

	; dmodp (exponent1) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_dmodp_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
if X509debug
	mov	rdi, [rbx+rsaprivate_dmodp_ofs]
	call	bigint$debug
end if

	; dmodq (exponent2) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_dmodq_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
if X509debug
	mov	rdi, [rbx+rsaprivate_dmodq_ofs]
	call	bigint$debug
end if

	; invqmodp (coefficient) is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	[rbx+rsaprivate_invqmodp_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
if X509debug
	mov	rdi, [rbx+rsaprivate_invqmodp_ofs]
	call	bigint$debug
end if

	; create two monty objects for our rsaprivate goods
	mov	rdi, [rbx+rsaprivate_dmodq_ofs]
	mov	rsi, [rbx+rsaprivate_q_ofs]
	call	monty$new
	mov	rdi, [rbx+rsaprivate_q_ofs]
	mov	[rdi+bigint_monty_powmod_ofs], rax

	mov	rdi, [rbx+rsaprivate_dmodp_ofs]
	mov	rsi, [rbx+rsaprivate_p_ofs]
	call	monty$new
	mov	rdi, [rbx+rsaprivate_p_ofs]
	mov	[rdi+bigint_monty_powmod_ofs], rax

	; so, RFC says there might be more goods after here, but none of my RSA private keys contain anything
	; ... call this a todo for now
	mov	rdi, [rsp+48]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rsp+48]
	call	heap$free

	; create our x, y, and z working integers
	call	bigint$new
	mov	[rbx+rsaprivate_x_ofs], rax
	call	bigint$new
	mov	[rbx+rsaprivate_y_ofs], rax
	call	bigint$new
	mov	[rbx+rsaprivate_z_ofs], rax
	
	; last but not least, we need to add our rsaprivate object to our X509 object
	mov	rdi, [rsp]
	mov	[rdi+X509_privatekey_ofs], rbx

	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]
	add	rsp, 64
	mov	eax, 1			; success.
	epilog
calign
.kakked:
	mov	rdi, [rsp+48]
	mov	rsi, bigint$destroy_clear
	call	list$clear
	mov	rdi, [rsp+48]
	call	heap$free
	mov	rdi, [rsp+56]
	call	heap$free_clear

	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]
	add	rsp, 64
	xor	eax, eax		; fail.
	epilog
falign
.gettag:
	asn1_tag
	ret

end if

; we are by no means attempting to be a full fledged X509/OpenSSL replacement here
; all we are interested in are the fields required to do the deed, haha

; all of my certificates are rsaEncryption certificates... so this provides for same

X509cert_der_ofs = 0			; allocated buffer that holds a _copy_ of what is signed
X509cert_derlen_ofs = 8			; length of that section as a normal integer
X509cert_serial_ofs = 16		; bigint
X509cert_signature_ofs = 24		; this will be an int from one of the constants (the TYPE of signature)
X509cert_signature_r_ofs = 32		; if the signature is a DSA, we get r and s bigints
X509cert_signature_s_ofs = 40		; ""
X509cert_issuercn_ofs = 48		; string (we only care about the CN of the Name items)
X509cert_validafter_ofs = 56		; string (in YYMMDDHH24MISS with a trailing Z on it)
X509cert_validuntil_ofs = 64		; ""
X509cert_subjectcn_ofs = 72		; string (we only care about the CN of the Name items)
X509cert_public_n_ofs = 80		; bigint (rsa)
X509cert_public_e_ofs = 88		; bigint (rsa)
X509cert_public_p_ofs = 96		; bigint (dsa)
X509cert_public_q_ofs = 104		; bigint (dsa)
X509cert_public_g_ofs = 112		; bigint (dsa)
X509cert_public_dsa_ofs = 120		; bigint (dsa)
X509cert_altnames_ofs = 128		; list (of string)
X509cert_ocspuri_ofs = 136		; string
X509cert_caissuers_ofs = 144		; string

if used X509$ocsp | defined include_everything

X509cert_ocspresponse_ofs = 152		; buffer (or null)
X509cert_serialstart_ofs = 160		; dword offset to start of serialNumber (including tag/length)
X509cert_seriallength_ofs = 164		; dword length of serialNumber (so that we don't have to reencode it)
X509cert_issuerdnstart_ofs = 168	; dword offset to start of issuerDN (including tag/length)
X509cert_issuerdnlength_ofs = 172	; dword length of issuerDN
X509cert_publickeystart_ofs = 176	; dword offset to start of public key (excluding tag and length)
X509cert_publickeylength_ofs = 180	; dword length of public key (excluding tag and length)
X509cert_ocsprequest_ofs = 184		; buffer
X509cert_ocsphost_ofs = 192		; string hostname of ocspuri
X509cert_ocsptimer_ofs = 200		; pointer to an epoll timer object, or null if none is current
X509cert_signaturevalue_ofs = 208	; bytes, length determined by the signature type/algo

X509cert_size = 208 + 512		; we stick the signature straight into the X509cert object

else

X509cert_signaturevalue_ofs = 152	; bytes, length determined by the signature type/algo

X509cert_size = 152 + 512		; we stick the signature straight into the X509cert object

end if


if used X509$add_certificate | defined include_everything
	; three arguments: rdi == X509 object to add to, rsi == DER encoded buffer, rdx == length of same
	; returns bool in eax as to whether we succeeded or not
falign
X509$add_certificate:
	prolog	X509$add_certificate
	sub	rsp, 96
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], rdx
	; we need some of our callee-saves
	mov	[rsp+24], rbx
	mov	[rsp+32], r12
	mov	[rsp+40], r13
	mov	rbx, rdi
	mov	r12, rsi
	mov	r13, rdx
	cmp	qword [rdi+X509_certificates_ofs], 0
	jne	.listokay
	call	list$new
	mov	[rbx+X509_certificates_ofs], rax
calign
.listokay:
	call	list$new
	mov	[rsp+48], rax		; our "bigint undo" list in case we have to unwind all this, makes it much cleaner
	mov	edi, X509cert_size
	call	heap$alloc_clear
	mov	[rsp+56], rax		; our X509cert object

	call	list$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_altnames_ofs], rax

	call	list$new
	mov	[rsp+64], rax		; our "string undo" list in case we have to unwind all this, makes it much cleaner

	; make a full copy of our goods
	mov	rdi, r12
	mov	rsi, r13
	call	heap$alloc_blockcopy
	mov	rdx, [rsp+56]		; our X509cert object
	mov	[rdx+X509cert_der_ofs], rax
	mov	[rdx+X509cert_derlen_ofs], r13
	mov	rdi, [rsp+64]
	mov	rsi, rax
	call	list$push_back		; our "string undo" just calls heap$free_clear for everything in it, which is fine for this

	; from RFC 2459

	; first things first, we expect a sequence
	call	.gettag
	cmp	eax, 0x10		; we need it to start iwth a sequence, or die.
	jne	.kakked
	; next is our certificate sequence
	push	r12			; save the offset to the start of our sequence, because we'll need a copy of it
	call	.gettag
	pop	rdi			; start of our sequence in the buffer
	cmp	eax, 0x10		; this too must be a sequence, or die.
	jne	.kakked

	; mov	rdx, [rsp+56]		; our X509cert object
	; mov	[rdx+X509cert_derlen_ofs], r8
	; so at this point, we have the length of our der component saved
	; mov	rsi, r8
	; call	heap$alloc_blockcopy
	; mov	rdi, [rsp+56]		; our X509cert object
	; mov	[rdi+X509cert_der_ofs], rax
	; mov	rdi, [rsp+64]
	; mov	rsi, rax
	; call	list$push_back		; our "string undo" just calls heap$free_clear for everything in it, which is fine for this

	; version is first up
	call	.gettag
	cmp	ecx, 0xa0		; constructed | context_specific, type 0
	jne	.checkserial
	; otherwise, if we call gettag again, we should get an integer with length one
	call	.gettag
	cmp	eax, 0x2		; integer or die
	jne	.kakked
	cmp	r8d, 1			; length 1 or die
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; version must be less than or equal to 2
	cmp	eax, 2
	ja	.kakked
	; version is okay
	; serialNumber is next, it most likely fits in a 64 bit integer, but will be encoded as a variable length bigendian integer
if used X509$ocsp | defined include_everything
	; calculate the offset to our serial number
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	mov	[rdi+X509cert_serialstart_ofs], eax
end if
	call	.gettag
calign
.checkserial:
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_serial_ofs], rax
if used X509$ocsp | defined include_everything
	; calculate the length, including the tag, of our serial number
	mov	rcx, r12
	sub	rcx, [rsp+8]
	sub	ecx, [rdi+X509cert_serialstart_ofs]
	mov	[rdi+X509cert_seriallength_ofs], ecx
end if
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our X509cert object
	mov	rdi, [rdi+X509cert_serial_ofs]
	call	bigint$debug
end if

	; next up is the signature, which is another sequence that contains an object identifier, then parameters
	call	.gettag
	cmp	eax, 0x10		; make sure it is a sequence, or die
	jne	.kakked

	call	.gettag
	cmp	eax, 0x6		; make sure it is an OID, or die.
	jne	.kakked
	cmp	r13, r8
	jb	.kakked			; make sure we have enough room left

	; if, and only if we are camped out on DSA-SHA1, we'll have a length of 7 bytes instead of 9
	cmp	r8d, 7
	je	.dsasigalg_sha1

	cmp	r8d, 9			; make sure its length is 9 bytes or die (noting here DSA-SHA1 handled above)
	jne	.kakked
	
	; determine whether it is an RSA or DSA signature algorithm
	mov	rax, [r12]
	cmp	rax, [.sigalgpreface+8]
	je	.dsasigalg
	cmp	rax, [.sigalgpreface]
	jne	.kakked			; not RSA or DSA, something weird and not in the wild for SSL, die
	
	; otherwise, we are camped out on an RSA signature algorithm, swallow the first 8 bytes that we already compared, and check the last byte
	mov	rdi, [rsp+56]		; our X509cert object
	add	r12, 8
	sub	r13, 8
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; we support precisely 4 RSA sigalgs
	xor	ecx, ecx		; use this as our spot
	mov	edx, 0x01		; RSA/SHA-160
	mov	r8d, 0x02		; RSA/SHA-256
	mov	r9d, 0x03		; RSA/SHA-384
	mov	r10d, 0x04		; RSA/SHA-512
	cmp	eax, 0x05
	cmove	ecx, edx
	cmp	eax, 0x0b
	cmove	ecx, r8d
	cmp	eax, 0x0c
	cmove	ecx, r9d
	cmp	eax, 0x0d
	cmove	ecx, r10d

	test	ecx, ecx
	jz	.kakked			; not one of the 4 we support
	; otherwise, we can use our constant in ecx as our sigalg
	mov	[rdi+X509cert_signature_ofs], rcx

	; now, we need precisely one NULL, no optional sigalgo parameters are present
	call	.gettag
	cmp	eax, 0x05		; NULL
	jne	.kakked
	test	r8d, r8d
	jnz	.kakked			; length must be zero for NULL

	; create two empty bigints for r & s even though they won't be used
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_signature_r_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_signature_s_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	jmp	.sigalgokay
calign
.dsasigalg_sha1:
	mov	rdi, [rsp+56]		; our X509cert object
	add	r12, 6
	sub	r13, 6
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; make sure it is a 3, or die
	cmp	eax, 3
	jne	.kakked			; not sitting on DSA-SHA1
	mov	qword [rdi+X509cert_signature_ofs], 0x05	; DSA/SHA-160
	jmp	.dsasigalg_okay
calign
.dsasigalg:
	mov	rdi, [rsp+56]		; our X509cert object
	add	r12, 8
	sub	r13, 8
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; we support precisely 2 DSA sigalgs (of the longer form anyway)
	xor	ecx, ecx		; use this as our spot
	mov	edx, 0x06		; DSA/SHA-224
	mov	r8d, 0x07		; DSA/SHA-256
	cmp	eax, 0x01
	cmove	ecx, edx
	cmp	eax, 0x02
	cmove	ecx, r8d
	test	ecx, ecx
	jz	.kakked			; not one of the 2 we support
	; otherwise, we can use our constant in ecx as our sigalg
	mov	[rdi+X509cert_signature_ofs], rcx
calign
.dsasigalg_okay:
	; RFC2459 section 7.2.2 says parameters are specifically omitted
	; create our r and s beforehand
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_signature_r_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_signature_s_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

calign
.sigalgokay:
	; issuer is next

if used X509$ocsp | defined include_everything
	; in the event we have to hash the issuer name, we need to store the start and end offsets
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	mov	[rdi+X509cert_issuerdnstart_ofs], eax
end if

	call	.gettag
	cmp	eax, 0x10		; sequence or die
	jne	.kakked

	mov	rdi, r8			; length of the name sequence
	call	.getCN
	test	rax, rax
	jnz	.sigalgokay_CNokay
	; google returns us a certificate with no CN present... :-(
	call	string$new
calign
.sigalgokay_CNokay:
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_issuercn_ofs], rax
	mov	rdi, [rsp+64]		; our string undo list
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_issuercn_ofs]
	call	string$to_stdoutln
end if

if used X509$ocsp | defined include_everything
	; figure out the length of the complete issuerDN
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	sub	eax, [rdi+X509cert_issuerdnstart_ofs]
	mov	[rdi+X509cert_issuerdnlength_ofs], eax
end if
	
	; next up: validity sequence
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	jne	.kakked

	call	.gettag
	cmp	eax, 0x17		; UTC TIME OR DIE, we should _not_ get GENERALIZED TIME here...
	jne	.kakked
	cmp	r13, r8
	jb	.kakked			; not enough data to continue
	; else, create a string out of our current value
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	string$from_utf8
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_validafter_ofs], rax
	mov	rdi, [rsp+64]
	mov	rsi, rax
	call	list$push_back
	
if X509debug
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_validafter_ofs]
	call	string$to_stdoutln
end if

	; and one more for the valid until
	call	.gettag
	cmp	eax, 0x17		; UTC TIME OR DIE, we should _not_ get GENERALIZED TIME here...
	jne	.kakked
	cmp	r13, r8
	jb	.kakked			; not enough data to continue
	; else, create a string out of our current value
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	string$from_utf8
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_validuntil_ofs], rax
	mov	rdi, [rsp+64]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_validuntil_ofs]
	call	string$to_stdoutln
end if

	; next up: subject
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	jne	.kakked

	mov	rdi, r8
	call	.getCN
	test	rax, rax
	jz	.kakked
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_subjectcn_ofs], rax
	mov	rdi, [rsp+64]		; our string undo list
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_subjectcn_ofs]
	call	string$to_stdoutln
end if
	
	; next up: subjectPublicKeyInfo
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	jne	.kakked

	; subjectPublicKeyInfo AlgorithmIdentifier is next, which is also a sequence
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	jne	.kakked

	; OID is next
	; we support precisely 3 public key types here (well, 2, but one is an alternate)
	; 1.2.840.113549.1.1.1 -- RSA
	; 2.5.8.1.1 - RSA
	; 1.2.840.10040.4.1 -- DSA
	call	.gettag
	cmp	eax, 0x6		; OID or die
	jne	.kakked
	cmp	r13, r8
	jb	.kakked			; not enough data to proceed
	cmp	r8d, 9
	ja	.kakked
	je	.rsa9
	cmp	r8d, 4
	jb	.kakked
	je	.rsa4
	cmp	r8d, 7
	jne	.kakked
	; dsa7
	mov	rax, [r12]
	shl	rax, 8			; we are only interseted in 7 bytes, topmost on littleendian load is last, so we discard it
	cmp	rax, [.dsapublic]
	jne	.kakked
	; otherwise, we are indeed sitting on a dsa public key
	add	r12, r8
	sub	r13, r8

if used X509$ocsp | defined include_everything
	; record the start offset of the public key info excluding tag and length
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	mov	[rdi+X509cert_publickeystart_ofs], eax
end if

	; so, if params are present, our following tag should be a sequence, and if it is a bitstring, then the CA's parameters apply and not this one
	call	.gettag
	cmp	eax, 0x03		; bit_string? the dsa public key is encoded as an integer inside the bit string
	je	.dsanoparams
	cmp	eax, 0x10		; sequence or die
	jne	.kakked
	; otherwise, we have 3 parameters for the DSA goods

	; p is first
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_public_p_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our X509cert object
	mov	rdi, [rdi+X509cert_public_p_ofs]
	call	bigint$debug
end if

	; q is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_public_q_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our X509cert object
	mov	rdi, [rdi+X509cert_public_q_ofs]
	call	bigint$debug
end if

	; last but not least, g
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_public_g_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our X509cert object
	mov	rdi, [rdi+X509cert_public_g_ofs]
	call	bigint$debug
end if

	; we also need to zero-populate the rsa side n and e
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_n_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_e_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	jmp	.dsapublickeytag
calign
.dsanoparams:
	; no params were specified for the dsa public key, but we need clear bigints in all five
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_p_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_q_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_g_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_n_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_e_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	mov	eax, 0x3
	jmp	.dsadopublickey		; tag already acquired here
calign
.rsa9:
	; length of the OID was 9, so check to make sure it matches .rsapublic
	mov	rax, [r12]
	cmp	rax, [.rsapublic]
	jne	.kakked
	add	r12, 8
	sub	r13, 8
	movzx	eax, byte [r12]
	cmp	eax, 1
	jne	.kakked
	add	r12, 1
	sub	r13, 1
	; else, it matches, there should be a NULL after this (cuz we are still in AlgorithmIdentifier)
	call	.gettag
	cmp	eax, 0x5
	jne	.kakked
	jmp	.rsapublickeytag
calign
.rsa4:
	mov	eax, [r12]
	cmp	eax, [.rsapublicalt]
	jne	.kakked
	add	r12, 4
	sub	r13, 4
	; fallthrough to rsapublickeytag
calign
.rsapublickeytag:
	; it appears that our public key is nestled inside a BIT STRING
	call	.gettag
	cmp	eax, 0x3		; BIT STRING or die
	jne	.kakked
	cmp	r13, r8
	jb	.kakked			; not enough data to proceed

	add	r12, 1
	sub	r13, 1

if used X509$ocsp | defined include_everything
	; record the start offset of the public key info excluding tag and length
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	mov	[rdi+X509cert_publickeystart_ofs], eax
end if

	call	.gettag
	cmp	eax, 0x10		; sequence or die

	; we should have n and e integers
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_public_n_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our X509cert object
	mov	rdi, [rdi+X509cert_public_n_ofs]
	call	bigint$debug
end if

	; e is next
	call	.gettag
	cmp	eax, 0x2		; integer
	jne	.kakked
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked			; enough data left?
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_public_e_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]		; our X509cert object
	mov	rdi, [rdi+X509cert_public_e_ofs]
	call	bigint$debug
end if

	; populate the dsa ones with zero-but-valid bigints
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_p_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_q_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_g_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back
	
	call	bigint$new
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_public_dsa_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

	jmp	.publickeydone

calign
.dsapublickeytag:
	; TODO: redo this, we really need the length carried forward from dsanoparams, or at the very least, another check above for length minimums
	call	.gettag
.dsadopublickey:
	cmp	eax, 0x3		; bit_string or die
	jne	.kakked
	add	r12, 1
	sub	r13, 1			; skip the bit_string preface octet

if used X509$ocsp | defined include_everything
	; record the start offset of the public key info excluding tag and length
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	mov	[rdi+X509cert_publickeystart_ofs], eax
end if

	call	.gettag
	cmp	eax, 0x2		; integer or die
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jz	.kakked
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$new_encoded
	mov	rdi, [rsp+56]		; our X509cert object
	mov	[rdi+X509cert_public_dsa_ofs], rax
	mov	rdi, [rsp+48]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]
	mov	rdi, [rdi+X509cert_public_dsa_ofs]
	call	bigint$debug
end if

	; fallthrough to .publickeydone
calign
.publickeydone:

if used X509$ocsp | defined include_everything
	; record the length of our publickeyinfo goods
	mov	rdi, [rsp+56]
	mov	rax, r12
	sub	rax, [rsp+8]
	sub	eax, [rdi+X509cert_publickeystart_ofs]
	mov	[rdi+X509cert_publickeylength_ofs], eax
end if

	call	.gettag
	; so, for our optional issueruniqueid and optional subject uniqueid
	; if the class is only context_specific and our type is 1
	; or if the class is context_specific and our type is 2
	; then we can skip over them
	; then, if class is constructed | context_specific and type is 3, then extensions is a bit string,
	; and otherwise, we are done and need to bailout to the signature algorithm
	cmp	r13, r8
	jb	.kakked

	; so easy check first, fi this is an object id, jump straight to the sigalgo
	cmp	eax, 0x10
	je	.sigalgo_seqtagpresent
	
	mov	edx, ecx
	and	edx, 0xa0
	cmp	edx, 0xa0
	je	.extensionsonly
	; otherwise, we must have one of the other optionals, skip them as we aren't interested
	add	r12, r8
	sub	r13, r8
	jmp	.publickeydone
calign
.extensionsonly:
	cmp	eax, 3
	jne	.kakked

	; why isn't this one prefaced by a leading octet like the others? ahh, maybe that is only for primitives or something
	; nfi

	; and now we expect a sequence of all our extensions... 
	call	.gettag
	cmp	eax, 0x10
	jne	.kakked

	; otherwise, we need to save our length so we know when to bailout, because
	; immediately following this extension list, is the signature algorithm
	; which is also a sequence folloewd by an OID
	mov	rdx, r13
	sub	rdx, r8
	mov	[rsp+72], rdx

	cmp	r13, r8
	jb	.kakked
calign
.extensionloop:
	cmp	r13, [rsp+72]
	jbe	.sigalgo

	; each extension is a SEQUENCE
	call	.gettag
	cmp	eax, 0x10
	jne	.kakked

	call	.gettag
	cmp	eax, 0x6
	jne	.kakked
	cmp	r13, r8
	jb	.kakked

	; Sooooooooooooooooooooo, our extensions we recognize are:
	; oid length 3:
	; 2.5.29.14	X509v3 SubjectKeyIdentifier				0x0e1d5500
	; 2.5.29.15	X509v3 KeyUsage						0x0f1d5500
	; 2.5.29.17	X509v3 SubjectAlternativeName				0x111d5500
	; 2.5.29.18	X509v3 IssuerAlternativeName				0x121d5500
	; 2.5.29.19	X509v3 BasicConstraints					0x131d5500
	; 2.5.29.20	X509v3 CRLNumber					0x141d5500
	; 2.5.20.21	X509v3 ReasonCode					0x151d5500
	; 2.5.20.23	X509v3 HoldInstructionCode				0x171d5500
	; 2.5.20.24	X509v3 InvalidityDate					0x181d5500
	; 2.5.20.31	X509v3 CRLDistributionPoints				0x1f1d5500
	; 2.5.20.32	X509v3 CertificatePolicies				0x201d5500
	; 2.5.20.35	X509v3 AuthorityKeyIdentifier				0x231d5500
	; 2.5.20.36	X509v3 PolicyConstraints				0x241d5500
	; 2.5.20.37	X509v3 ExtendedKeyUsage					0x251d5500
	; oid length 4:
	; 2.5.29.32.0	X509v3 AnyPolicy					0x00201d55
	; oid length 8:
	; 1.3.6.1.5.5.7.1.1	PKIX	AuthorityInformationAccess		0x010107050501062b
	; all of these are not extensions as such but part of the authorityinformationaccess
	; 1.3.6.1.5.5.7.3.1	PKIX	ServerAuth				0x010307050501062b
	; 1.3.6.1.5.5.7.3.2	PKIX	ClientAuth				0x020307050501062b
	; 1.3.6.1.5.5.7.3.3	PKIX	CodeSigning				0x030307050501062b
	; 1.3.6.1.5.5.7.3.4	PKIX	EmailProtection				0x040307050501062b
	; 1.3.6.1.5.5.7.3.5	PKIX	IPsecEndSystem				0x050307050501062b
	; 1.3.6.1.5.5.7.3.6	PKIX	IPsecTunnel				0x060307050501062b
	; 1.3.6.1.5.5.7.3.7	PKIX	IPsecUser				0x070307050501062b
	; 1.3.6.1.5.5.7.3.8	PKIX	TimeStamping				0x080307050501062b
	; 1.3.6.1.5.5.7.3.9	PKIX	OCSPSigning				0x090307050501062b
	; 1.3.6.1.5.5.7.48.1	PKIX	OCSP					0x013007050501062b
	; 1.3.6.1.5.5.7.48.2	PKIX	CAIssuers				0x023007050501062b
	; 1.3.6.1.5.5.7.8.5	PKIX	XMPPAddr				0x050807050501062b
	; oid length 9:
	; 1.3.6.1.5.5.7.48.1.1	PKIX	OCSP BasicResponse			same as OSCP but with a trailing 0x01 byte after it

	cmp	r8d, 3
	je	.extension3
	cmp	r8d, 4
	je	.extension4
	cmp	r8d, 8
	je	.extension8
	cmp	r8d, 9
	je	.extension9
calign
.extension_skip:
	; otherwise, we need to SKIP it (because it isn't necessarily an error to receive ones we don't recognize, UNLESS the critical
	; flag is specified)
	add	r12, r8
	sub	r13, r8
	; next one better be a bool
	call	.gettag
	cmp	eax, 0x1		; bool?
	jne	.extension_skip_nocrit
	cmp	r8d, 1
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	cmp	byte [r12], 0
	jne	.kakked			; critical set for an extension that we don't understand == outta here
	add	r12, 1
	sub	r13, 1
	call	.gettag
calign
.extension_skip_nocrit:
	cmp	eax, 0x4
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	add	r12, r8
	sub	r13, r8
	jmp	.extensionloop
calign
.extension_skip_ignorecrit:
	; used for ones we recognized but don't really care about [yet]
	add	r12, r8
	sub	r13, r8
	; next one is either a bool _or_ the value
	call	.gettag
	cmp	eax, 0x1
	jne	.extension_skip_ignorecrit_nobool
	cmp	r8d, 1
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	; this section we are ignoring whether it is crit
	; cmp	byte [r12], 0
	; jne	.kakked			; critical set for an extension that we don't understand == outta here
	add	r12, 1
	sub	r13, 1
	call	.gettag
calign
.extension_skip_ignorecrit_nobool:
	cmp	eax, 0x4
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	add	r12, r8
	sub	r13, r8
	jmp	.extensionloop
calign
.extension3:
	; 3 byte OID length, so we'll grab a dword and shl it by 8
	mov	eax, [r12]
	shl	eax, 8
	cmp	eax, 0x0e1d5500		; SubjectKeyIdentifier
	je	.extension_skip_ignorecrit
	cmp	eax, 0x0f1d5500		; KeyUsage
	je	.extension_skip_ignorecrit
	cmp	eax, 0x111d5500		; SubjectAlternativeName
	je	.extSubjectAlternativeName
	cmp	eax, 0x121d5500		; IssuerAlternativeName
	je	.extension_skip_ignorecrit
	cmp	eax, 0x131d5500		; BasicConstraints
	je	.extension_skip_ignorecrit
	cmp	eax, 0x141d5500		; CRLNumber
	je	.extension_skip_ignorecrit
	cmp	eax, 0x151d5500		; ReasonCode
	je	.extension_skip_ignorecrit
	cmp	eax, 0x171d5500		; HoldInstructionCode
	je	.extension_skip_ignorecrit
	cmp	eax, 0x181d5500		; InvalidityDate
	je	.extension_skip_ignorecrit
	cmp	eax, 0x1f1d5500		; CRLDistributionPoints
	je	.extension_skip_ignorecrit
	cmp	eax, 0x201d5500		; CertificatePolicies
	je	.extension_skip_ignorecrit
	cmp	eax, 0x231d5500		; AuthorityKeyIdentifier
	je	.extension_skip_ignorecrit
	cmp	eax, 0x241d5500		; PolicyConstraints
	je	.extension_skip_ignorecrit
	cmp	eax, 0x251d5500		; ExtendedKeyUsage
	je	.extension_skip_ignorecrit
	jmp	.extension_skip
calign
.extension4:
	mov	eax, [r12]
	cmp	eax, 0x00201d55		; AnyPolicy
	je	.extension_skip_ignorecrit
	jmp	.extension_skip
dalign
.AuthorityInformationAccess:	dq	0x010107050501062b
.ServerAuth:			dq	0x010307050501062b
.ClientAuth:			dq	0x020307050501062b
.CodeSigning:			dq	0x030307050501062b
.EmailProtection:		dq	0x040307050501062b
.IPsecEndSystem:		dq	0x050307050501062b
.IPsecTunnel:			dq	0x060307050501062b
.IPsecUser:			dq	0x070307050501062b
.TimeStamping:			dq	0x080307050501062b
.OCSPSigning:			dq	0x090307050501062b
.OCSP:				dq	0x013007050501062b
.CAIssuers:			dq	0x023007050501062b
.XMPPAddr:			dq	0x050807050501062b
calign
.extension8:
	mov	rax, [r12]
	cmp	rax, [.AuthorityInformationAccess]
	je	.extAuthorityInformationAccess
if defined authinfoaccessbits
	cmp	rax, [.ServerAuth]
	je	.extension_skip_ignorecrit
	cmp	rax, [.ClientAuth]
	je	.extension_skip_ignorecrit
	cmp	rax, [.CodeSigning]
	je	.extension_skip_ignorecrit
	cmp	rax, [.EmailProtection]
	je	.extension_skip_ignorecrit
	cmp	rax, [.IPsecEndSystem]
	je	.extension_skip_ignorecrit
	cmp	rax, [.IPsecTunnel]
	je	.extension_skip_ignorecrit
	cmp	rax, [.IPsecUser]
	je	.extension_skip_ignorecrit
	cmp	rax, [.TimeStamping]
	je	.extension_skip_ignorecrit
	cmp	rax, [.OCSPSigning]
	je	.extension_skip_ignorecrit
	cmp	rax, [.OCSP]
	je	.extension_skip_ignorecrit
	cmp	rax, [.XMPPAddr]
	je	.extension_skip_ignorecrit
end if
	jmp	.extension_skip
calign
.extension9:
	mov	rax, [r12]
	cmp	rax, [.OCSP]
	jne	.extension_skip
	movzx	eax, byte [r12+8]
	cmp	eax, 1
	jne	.extension_skip
	jmp	.extension_skip_ignorecrit

; and, the extensions we care about:
calign
.extSubjectAlternativeName:
	; skip our OID first
	add	r12, r8
	sub	r13, r8
	; next tag is the critical bool
	call	.gettag
	cmp	eax, 0x1
	jne	.extSubjectAlternativeName_nocrit
	cmp	r8d, 1
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	add	r12, 1
	sub	r13, 1
	; next tag is the octet string:
	call	.gettag
calign
.extSubjectAlternativeName_nocrit:
	cmp	eax, 0x4
	jne	.kakked			; OCTET STRING or die
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	; we need to store the length of this sequence so that we know when to bailout as well
	cmp	r13, r8
	jb	.kakked
	mov	rdx, r13
	sub	rdx, r8
	mov	[rsp+80], rdx
	; so now, we have to iterate over this sequence and extract all string variants
calign
.extSubjectAlternativeName_loop:
	cmp	r13, [rsp+80]
	jbe	.extensionloop
	call	.gettag
	test	eax, eax
	jz	.extSubjectAlternativeName_skip
	cmp	eax, 3
	je	.extSubjectAlternativeName_skip
	cmp	eax, 4
	je	.extSubjectAlternativeName_skip
	cmp	eax, 5
	je	.extSubjectAlternativeName_skip
	cmp	eax, 7
	je	.extSubjectAlternativeName_skip
	cmp	eax, 8
	jae	.extSubjectAlternativeName_skip
	; else, we have an extractable normal sorta string, grab it
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	string$from_utf8
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_altnames_ofs]
	mov	rsi, rax
	call	list$push_back
	jmp	.extSubjectAlternativeName_loop
calign
.extSubjectAlternativeName_skip:
	add	r12, r8
	sub	r13, r8
	jmp	.extSubjectAlternativeName_loop
calign
.extAuthorityInformationAccess:
	; skip our OID first
	add	r12, r8
	sub	r13, r8
	; next tag is the critical bool
	call	.gettag
	cmp	eax, 0x1
	jne	.extAuthorityInformationAccess_nocrit
	cmp	r8d, 1
	jne	.kakked
	cmp	r13, r8
	jb	.kakked
	add	r12, 1
	sub	r13, 1
	; next tag is the octet string:
	call	.gettag
calign
.extAuthorityInformationAccess_nocrit:
	cmp	eax, 0x4
	jne	.kakked			; OCTET STRING or die
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	; we need to store the length of this sequence so that we know when to bailout as well
	cmp	r13, r8
	jb	.kakked
	mov	rdx, r13
	sub	rdx, r8
	mov	[rsp+80], rdx
	; so now, we have to iterate over this sequence and extract all string variants
calign
.extAuthorityInformationAccess_loop:
	cmp	r13, [rsp+80]
	jbe	.extensionloop
	call	.gettag
	cmp	eax, 0x10		; sequence or die
	jne	.kakked
	call	.gettag
	cmp	eax, 0x6		; OID or die
	jne	.kakked
	mov	rax, [r12]
	cmp	rax, [.OCSP]
	je	.extAuthorityInformationAccess_OSCP
	cmp	rax, [.CAIssuers]
	je	.extAuthorityInformationAccess_CAIssuers
	; otherwise, skip this one entirely
	add	r12, r8
	sub	r13, r8
	; next one is a GeneralName
	call	.gettag
	add	r12, r8
	sub	r13, r8
	jmp	.extAuthorityInformationAccess_loop
calign
.extAuthorityInformationAccess_OSCP:
	add	r12, r8
	sub	r13, r8
	; next one is a GeneralName
	call	.gettag
	test	eax, eax
	jz	.extAuthorityInformationAccess_skip
	cmp	eax, 3
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 4
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 5
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 7
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 8
	jae	.extAuthorityInformationAccess_skip
	; otherwise, we have an OSCP string
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	string$from_utf8
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_ocspuri_ofs], rax
	mov	rdi, [rsp+64]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, .debugocspuri
	call	string$to_stdout
	mov	rdi, [rsp+56]
	mov	rdi, [rdi+X509cert_ocspuri_ofs]
	call	string$to_stdoutln
	jmp	.extAuthorityInformationAccess_loop
cleartext .debugocspuri, 'OCSP URI: '
else
	jmp	.extAuthorityInformationAccess_loop
end if
calign
.extAuthorityInformationAccess_skip:
	add	r12, r8
	sub	r13, r8
	jmp	.extAuthorityInformationAccess_loop
calign
.extAuthorityInformationAccess_CAIssuers:
	add	r12, r8
	sub	r13, r8
	; next one is a GeneralName
	call	.gettag
	test	eax, eax
	jz	.extAuthorityInformationAccess_skip
	cmp	eax, 3
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 4
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 5
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 7
	je	.extAuthorityInformationAccess_skip
	cmp	eax, 8
	jae	.extAuthorityInformationAccess_skip
	; otherwise, we have an OSCP string
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	string$from_utf8
	mov	rdi, [rsp+56]
	mov	[rdi+X509cert_caissuers_ofs], rax
	mov	rdi, [rsp+64]
	mov	rsi, rax
	call	list$push_back

if X509debug
	mov	rdi, [rsp+56]
	mov	rdi, [rdi+X509cert_caissuers_ofs]
	call	string$to_stdoutln
end if
	
	jmp	.extAuthorityInformationAccess_loop
calign
.sigalgo:
	; so if we made it to here, everything parsed okay for tbsCertificate
	; next in line is the signatureAlgorithm
	call	.gettag
	cmp	eax, 0x10		; make sure it is a sequence, or die
	jne	.kakked

.sigalgo_seqtagpresent:
	call	.gettag
	cmp	eax, 0x6		; make sure it is an OID, or die.
	jne	.kakked

	cmp	r13, r8
	jb	.kakked			; make sure we have enough room left

	cmp	r8d, 7
	je	.sigalgo_dsa7

	cmp	r8d, 9			; make sure its length is 9 bytes or die (noting here we are tossing DSA-SHA1 out on purpose)
	jne	.kakked
	
	; determine whether it is an RSA or DSA signature algorithm
	mov	rax, [r12]
	cmp	rax, [.sigalgpreface+8]
	je	.sigalgo_dsa
	cmp	rax, [.sigalgpreface]
	jne	.kakked			; not RSA or DSA, something weird and not in the wild for SSL, die
	
	; otherwise, we are camped out on an RSA signature algorithm, swallow the first 8 bytes that we already compared, and check the last byte
	mov	rdi, [rsp+56]		; our X509cert object
	add	r12, 8
	sub	r13, 8
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; we support precisely 4 RSA sigalgs
	xor	ecx, ecx		; use this as our spot
	mov	edx, 0x01		; RSA/SHA-160
	mov	r8d, 0x02		; RSA/SHA-256
	mov	r9d, 0x03		; RSA/SHA-384
	mov	r10d, 0x04		; RSA/SHA-512
	cmp	eax, 0x05
	cmove	ecx, edx
	cmp	eax, 0x0b
	cmove	ecx, r8d
	cmp	eax, 0x0c
	cmove	ecx, r9d
	cmp	eax, 0x0d
	cmove	ecx, r10d
	test	ecx, ecx
	jz	.kakked			; not one of the 4 we support
	; otherwise, we can use our constant in ecx as our sigalg
	cmp	ecx, dword [rdi+X509cert_signature_ofs]
	jne	.kakked			; must be the same one we had before

	; now, we need precisely one NULL
	call	.gettag
	cmp	eax, 0x05		; NULL
	jne	.kakked
	test	r8d, r8d
	jnz	.kakked			; length must be zero for NULL

	jmp	.signature
calign
.sigalgo_dsa7:
	mov	rdi, [rsp+56]		; our X509cert object
	add	r12, 6
	sub	r13, 6
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	cmp	eax, 3			; DSA/SHA-160
	jne	.kakked
	cmp	dword [rdi+X509cert_signature_ofs], 0x05
	jne	.kakked			; must be the same one we had before
	jmp	.sigalgo_dsa_okay

calign
.sigalgo_dsa:
	mov	rdi, [rsp+56]		; our X509cert object
	add	r12, 8
	sub	r13, 8
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; we support precisely 2 DSA sigalgs
	xor	ecx, ecx		; use this as our spot
	mov	edx, 0x06		; DSA/SHA-224
	mov	r8d, 0x07		; DSA/SHA-256
	cmp	eax, 0x01
	cmove	ecx, edx
	cmp	eax, 0x02
	cmove	ecx, r8d
	test	ecx, ecx
	jz	.kakked			; not one of the 2 we support
	; otherwise, we can use our constant in ecx as our sigalg
	cmp	ecx, dword [rdi+X509cert_signature_ofs]
	jne	.kakked			; must be the same one we had before
calign
.sigalgo_dsa_okay:
	; heh, ok so... the signature is encoded as a bit string, but is actually
	; an ASN1 sequence containing r and s inside it
	; for now, jump straight to the signature bit... TODO: breakout r and s
	jmp	.signature


	; read in r and s
	call	.gettag
	cmp	eax, 0x10
	jne	.kakked
	cmp	r13, r8
	jb	.kakked

	; r is first
	call	.gettag
	mov	rdi, [rsp+56]		; our X509cert object
	cmp	eax, 0x2		; integer
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, [rdi+X509cert_signature_r_ofs]
	mov	rsi, r12
	mov	rdx, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$set_encoded

if X509debug
	mov	rdi, [rsp+56]
	mov	rdi, [rdi+X509cert_signature_r_ofs]
	call	bigint$debug
end if

	; then s
	call	.gettag
	mov	rdi, [rsp+56]		; our X509cert object
	cmp	eax, 0x2		; integer
	test	r8d, r8d
	jz	.kakked
	cmp	r13, r8
	jb	.kakked
	mov	rdi, [rdi+X509cert_signature_s_ofs]
	mov	rsi, r12
	mov	rdx, r8
	add	r12, r8
	sub	r13, r8
	call	bigint$set_encoded

if X509debug
	mov	rdi, [rsp+56]
	mov	rdi, [rdi+X509cert_signature_s_ofs]
	call	bigint$debug
end if

calign
.signature:
	call	.gettag
	cmp	eax, 0x3		; bit string or die
	cmp	r13, r8
	jb	.kakked			; not enough data for it
	cmp	r8, 513
	ja	.kakked
	add	r12, 1			; skip the bit string preface byte
	sub	r13, 1			;
	sub	r8, 1

	mov	rcx, [rsp+56]
	lea	rdi, [rcx+X509cert_signaturevalue_ofs]
	mov	rsi, r12
	mov	rdx, r8
	call	memcpy

	; if there are trailing bytes, do we care? I don't think we do
	
	; cleanup our undo lists
	mov	rdi, [rsp+48]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rsp+48]
	call	heap$free_clear

	mov	rdi, [rsp+64]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rsp+64]
	call	heap$free_clear

	; last but not least, we need to add our rsaprivate object to our X509 object
	mov	rdi, [rbx+X509_certificates_ofs]
	mov	rsi, [rsp+56]
	call	list$push_back

	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]

	add	rsp, 96
	mov	eax, 1			; success.
	epilog
calign
.kakked:
	mov	rdi, [rsp+48]
	mov	rsi, bigint$destroy_clear
	call	list$clear
	mov	rdi, [rsp+48]
	call	heap$free_clear
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_altnames_ofs]
	mov	rsi, heap$free
	call	list$clear
	mov	rsi, [rsp+56]
	mov	rdi, [rsi+X509cert_altnames_ofs]
	call	heap$free

	mov	rdi, [rsp+56]
	call	heap$free_clear

	mov	rdi, [rsp+64]
	mov	rsi, heap$free_clear
	call	list$clear
	mov	rdi, [rsp+64]
	call	heap$free_clear

	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]
	add	rsp, 96
	xor	eax, eax		; fail.
	epilog
falign
.gettag:
	asn1_tag
	ret
falign
.getCN:
	; passed in rdi is the overall total length of the Name sequence itself, which is then comprised
	; of a SET OF AttributeTypeAndValue
	sub	rsp, 16
	cmp	r13, rdi
	jb	.getCN_failed		; not enough for our total sequence
	mov	rax, r13
	sub	rax, rdi
	mov	[rsp], rax		; our cutoff to stop our sequence searching
	mov	qword [rsp+8], 0	; our return string
calign
.getCN_loop:
	cmp	r13, [rsp]
	jbe	.getCN_return		; we got to the end of the name sequence

	call	.gettag
	cmp	eax, 0x11		; SET OF or die
	jne	.getCN_failed

	; SET OF AttributeTypeAndValue, AttributeTypeAndValue == SEQUENCE { OID, Value }
	; so, we need a SEQUENCE next
	call	.gettag
	cmp	eax, 0x10		; SEQUENCE or die
	jne	.getCN_failed

	; next up is our object id
	call	.gettag
	cmp	eax, 0x6		; OID or die
	jne	.getCN_failed

	cmp	r8d, 0x3		; length == 3 or its not an X520 object id
	jne	.getCN_loop_checkPKCS9
	cmp	r13, r8
	jb	.getCN_failed		; not enough data
	; first word at r12 must be 0x0455
	movzx	eax, word [r12]
	add	r12, 2
	sub	r13, 2
	cmp	eax, 0x455
	jne	.getCN_failed		; not an X520 object id
	movzx	eax, byte [r12]
	add	r12, 1
	sub	r13, 1
	; eax one of:
	; 03 == CommonName (the one we are after)
	; 04 == Surname
	; 05 == SerialNumber
	; 06 == Country
	; 07 == Locality
	; 08 == State
	; 0a == Organization
	; 0b == OrganizationalUnit
	; 0c == Title
	; 2a == GivenName
	; 2b == Initials
	; 2c == GenerationalQualifier
	; 2e == DNQualifier
	; 41 == Pseudonym
	cmp	eax, 0x03
	jb	.getCN_failed
	je	.getCN_extract
	cmp	eax, 0x09
	je	.getCN_failed
	jb	.getCN_skip
	cmp	eax, 0x0d
	jb	.getCN_skip
	cmp	eax, 0x2a
	je	.getCN_skip
	cmp	eax, 0x2b
	je	.getCN_skip
	cmp	eax, 0x2c
	je	.getCN_skip
	cmp	eax, 0x2e
	je	.getCN_skip
	cmp	eax, 0x41
	je	.getCN_skip

	; NOTE TO SELF: google gives me back a bunch of other X520 goods like postalAddress, (2.5.4.16, etc)
	; instead of puking here with a failure, we will default to just skip over ones we don't necessarily
	; recognize
	; jmp	.getCN_skip
	; jmp	.getCN_failed
	; fallthrough to skip
calign
.getCN_skip:
	; so, we got a recognized X520 OID, next is the value of same
	call	.gettag
	cmp	r13, r8
	jb	.getCN_failed
	add	r12, r8
	sub	r13, r8
	jmp	.getCN_loop
dalign
.PKCS9preface:
	db	0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09
calign
.getCN_loop_checkPKCS9:
	cmp	r8d, 0x9	; length == 9 or its not a PKCS9 object id
	jne	.getCN_failed
	; so we have 9 bytes at r12, match the first 8
	mov	rax, qword [.PKCS9preface]
	cmp	qword [r12], rax
	jne	.getCN_failed
	; otherwise, advance r12
	add	r12, 9
	sub	r13, 9
	jmp	.getCN_skip
calign
.getCN_extract:
	; we got the CN we are interested in, extract it and keep going
	call	.gettag
	; string type better be here
	cmp	r13, r8
	jb	.getCN_failed	; not enough data remains

	; for BMPString, they are 2-octet characters, for UniversalString, they are 4-octet characters, big-endian encoded
	cmp	eax, 0x13	; PRINTABLE STRING
	je	.getCN_extract_normal
	cmp	eax, 0x0c	; UTF8 STRING
	je	.getCN_extract_normal
	cmp	eax, 0x12	; NUMERIC STRING
	je	.getCN_extract_normal
	cmp	eax, 0x14	; T61 STRING
	je	.getCN_extract_normal
	cmp	eax, 0x15	; VIDEOTEX STRING
	je	.getCN_extract_normal
	cmp	eax, 0x16	; IA5 STRING
	je	.getCN_extract_normal
	cmp	eax, 0x19	; GRAPHIC STRING
	je	.getCN_extract_normal
	cmp	eax, 0x1a	; VISIBLE STRING
	je	.getCN_extract_normal
	cmp	eax, 0x1b	; GENERAL STRING
	je	.getCN_extract_normal
	cmp	eax, 0x1e	; BMP STRING
	je	.getCN_extract_ucs2
	cmp	eax, 0x1c	; UNIVERSAL STRING
	je	.getCN_extract_ucs4
	jmp	.getCN_failed
calign
.getCN_extract_normal:
	; just construct a normal string from whatever is here (which may not necessarily be right, but hey, it is safe to do it)
	mov	rdi, r12
	mov	rsi, r8
	add	r12, r8
	sub	r13, r8
	call	string$from_utf8
	mov	[rsp+8], rax
	jmp	.getCN_loop
calign
.getCN_extract_ucs2:
	; we need to byte flip however many words we have
	test	r8, 1
	jnz	.getCN_failed		; not divisible by 2
	; we can use below our current stackframe to do this
	mov	rdi, rsp
	sub	rdi, r8
	sub	rdi, r8			; x2 because our call to string will consume some stack as well
	mov	rsi, r8
	mov	rdx, rdi
calign
.getCN_extract_ucs2_loop:
	movzx	eax, word [r12]
	add	r12, 2
	sub	r13, 2
	xchg	ah, al
	mov	word [rdx], ax
	add	rdx, 2
	sub	r8, 2
	jnz	.getCN_extract_ucs2_loop
	call	string$from_utf16
	mov	[rsp+8], rax
	jmp	.getCN_loop
calign
.getCN_extract_ucs4:
	; we need to byte flip however many words we have
	test	r8, 3
	jnz	.getCN_failed		; not divisible by 4
	; we can use below our current stackframe to do this
	mov	rdi, rsp
	sub	rdi, r8
	sub	rdi, r8			; x2 because our call to string will consume some stack as well
	mov	rsi, r8
	mov	rdx, rdi
calign
.getCN_extract_ucs4_loop:
if use_movbe
	movbe	eax, [r12]
	add	r12, 4
	sub	r13, 4
else
	mov	eax, [r12]
	add	r12, 4
	sub	r13, 4
	bswap	eax
end if
	mov	[rdx], eax
	add	rdx, 4
	sub	r8, 4
	jnz	.getCN_extract_ucs4_loop
	call	string$from_utf32
	mov	[rsp+8], rax
	jmp	.getCN_loop
calign
.getCN_return:
	mov	rax, [rsp+8]		; if we indeed found a CN, then we set our return to it
	add	rsp, 16
	ret
calign
.getCN_failed:
	xor	eax, eax
	add	rsp, 16
	ret

dalign
.sigalgpreface:
	dq	0x01010df78648862a	; RSA, OID starts with: 1.2.840.113549.1.1.
	dq	0x0304036501488660	; DSA, OID starts with: 2.16.840.1.101.3.4.3.
.dsapublic:
	dq	0x010438ce48862a00	; DSA public key OID
.rsapublic:
	dq	0x01010df78648862a	; RSA public key OID (first 8 bytes), followed by a 0x01
.rsapublicalt:
	dd	0x01010855		; RSA alternate public key OID
	

end if

if used X509$new_ssh | defined include_everything
	; single argument: rdi == 0 == search /etc/ssh, else == string of directory name to search
	; returns bool in eax as to whether we found _everything_ we were looking for or not
falign
X509$new_ssh:
	prolog	X509$new_ssh
	mov	rsi, .defaultdir
	test	rdi, rdi
	cmovz	rdi, rsi
	sub	rsp, 48
	mov	[rsp], rdi		; save the source directory for re-use
	mov	edi, X509_size
	call	heap$alloc_clear
	mov	[rsp+8], rax		; our X509 return object if all goes well
	; we need a working buffer
	call	buffer$new
	mov	[rsp+24], rax
	; load up our private keys first
	mov	rdi, [rsp]
	mov	rsi, .rsakey
	call	string$concat
	mov	[rsp+16], rax
	mov	rdi, rax
	call	file$mtime
	test	eax, eax
	jz	.dsaload
	; otherwise, we got a mtime, so load up the file so we can parse it
	mov	rdi, [rsp+16]
	call	file$to_string
	mov	rdi, [rsp+16]
	mov	[rsp+16], rax
	call	heap$free		; get rid of our concatenated filename
	; so now we have the base64 version of our DER goodies (hopefully) sitting in [rsp+16]
	; parse it for the goods we are looking for
	xor	edx, edx
	mov	[rsp+32], rdx		; search position
calign
.psearch:
	mov	rdi, [rsp+16]
	mov	rsi, .rsaprivatekey
	mov	rdx, [rsp+32]
	call	string$indexof_ofs
	cmp	rax, -1
	je	.dsaload
	; rax contains the index of the start we are after
	mov	[rsp+40], rax
	mov	rdi, [rsp+16]
	mov	rsi, .rsaprivateend
	mov	rdx, rax
	call	string$indexof_ofs
	cmp	rax, -1
	je	.dsaload
	mov	[rsp+32], rax		; save the next search start location
	mov	rdi, [rsp+16]
	mov	rsi, [rsp+40]
	add	rsi, qword [.rsaprivatekey]
	mov	rdx, rax
	call	string$substring
	; so now we have a string representation of just the base64 encoded bit
	mov	[rsp+40], rax
	; it is assumed our working buffer is already clear/reset
	mov	rdi, [rsp+24]
	mov	rsi, rax
	xor	edx, edx
	call	buffer$append_base64decode
	mov	rcx, [rsp+24]		; working buffer
	mov	rdi, [rsp+8]		; X509 return object
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	X509$add_privatekey
	mov	rdi, [rsp+24]
	call	buffer$reset
	mov	rdi, [rsp+40]
	call	heap$free		; free our substring
	jmp	.psearch
calign
.dsaload:
	mov	rdi, [rsp+16]
	call	heap$free
	mov	rdi, [rsp]
	mov	rsi, .dsakey
	call	string$concat
	mov	[rsp+16], rax
	mov	rdi, rax
	call	file$mtime
	test	eax, eax
	jz	.csearch
	; otherwise, we got a mtime, so load up the file so we can parse it
	mov	rdi, [rsp+16]
	call	file$to_string
	mov	rdi, [rsp+16]
	mov	[rsp+16], rax
	call	heap$free		; get rid of our concatenated filename
	; so now we have the base64 version of our DER goodies (hopefully) sitting in [rsp+16]
	; parse it for the goods we are looking for
	xor	edx, edx
	mov	[rsp+32], rdx
calign
.dsearch:
	; dsa private keys next
	mov	rdi, [rsp+16]
	mov	rsi, .dsaprivatekey
	mov	rdx, [rsp+32]
	call	string$indexof_ofs
	cmp	rax, -1
	je	.csearch
	; rax contains the index of the start we are after
	mov	[rsp+40], rax
	mov	rdi, [rsp+16]
	mov	rsi, .dsaprivateend
	mov	rdx, rax
	call	string$indexof_ofs
	cmp	rax, -1
	je	.csearch
	mov	[rsp+32], rax		; save the next search start location
	mov	rdi, [rsp+16]
	mov	rsi, [rsp+40]
	add	rsi, qword [.dsaprivatekey]
	mov	rdx, rax
	call	string$substring
	; so now we have a string representation of just the base64 encoded bit
	mov	[rsp+40], rax
	; it is assumed our working buffer is already clear/reset
	mov	rdi, [rsp+24]
	mov	rsi, rax
	xor	edx, edx
	call	buffer$append_base64decode
	mov	rcx, [rsp+24]		; working buffer
	mov	rdi, [rsp+8]		; X509 return object
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	X509$add_dsaprivatekey
	mov	rdi, [rsp+24]
	call	buffer$reset
	mov	rdi, [rsp+40]
	call	heap$free		; free our substring
	jmp	.dsearch
calign
.csearch:
	mov	rdi, [rsp+16]
	call	heap$free
	mov	rdi, [rsp+8]
	mov	rsi, [rdi+X509_privatekey_ofs]
	or	rsi, [rdi+X509_dsaprivatekey_ofs]
	jz	.failed
	; otherwise, we need to load and parse the "special" ssh pub key format (why didn't they just use X509v3 like everything else?)

	mov	rdi, [rsp]
	mov	rsi, .rsapub
	call	string$concat
	mov	[rsp+16], rax
	mov	rdi, rax
	call	file$mtime
	test	eax, eax
	jz	.dpsearch
	call	buffer$new
	mov	rdi, [rsp+8]
	mov	[rdi+X509_pubkey_ofs], rax	; our buffer object to hold the goods
	mov	rdi, [rsp+16]
	call	file$to_string
	mov	rdi, [rsp+16]
	mov	[rsp+16], rax
	call	heap$free
	; so now we know that our base64 data is the second "column" (ssh-rsa AAAA... user@host)
	; and further, that the ssh-rsa is static, so get a substring of our goods starting at offset 8, up to the next space
	mov	rdi, [rsp+16]
	mov	esi, ' '
	mov	edx, 8
	call	string$indexof_charcode_ofs
	cmp	rax, 0
	jl	.dpsearch			; something went wrong
	mov	rdi, [rsp+16]
	mov	esi, 8
	mov	rdx, rax
	call	string$substring
	mov	rdi, [rsp+16]
	mov	[rsp+16], rax
	call	heap$free
if X509debug
	mov	rdi, .rsapubmsg
	call	string$to_stdout
	mov	rdi, [rsp+16]
	call	string$to_stdout
	mov	rdi, .rsapubmsg2
	call	string$to_stdoutln
end if
	; now we need our base64 decoded goods into our buffer
	mov	rdi, [rsp+8]
	mov	rdi, [rdi+X509_pubkey_ofs]
	mov	rsi, [rsp+16]
	xor	edx, edx
	call	buffer$append_base64decode
	; all good
calign
.dpsearch:
	mov	rdi, [rsp+16]
	call	heap$free
	mov	rdi, [rsp]
	mov	rsi, .dsapub
	call	string$concat
	mov	[rsp+16], rax
	mov	rdi, rax
	call	file$mtime
	test	eax, eax
	jz	.finalcheck
	call	buffer$new
	mov	rdi, [rsp+8]
	mov	[rdi+X509_dsapubkey_ofs], rax	; our buffer object to hold the goods
	mov	rdi, [rsp+16]
	call	file$to_string
	mov	rdi, [rsp+16]
	mov	[rsp+16], rax
	call	heap$free
	;
	mov	rdi, [rsp+16]
	mov	esi, ' '
	mov	edx, 8
	call	string$indexof_charcode_ofs
	cmp	rax, 0
	jl	.finalcheck
	mov	rdi, [rsp+16]
	mov	esi, 8
	mov	rdx, rax
	call	string$substring
	mov	rdi, [rsp+16]
	mov	[rsp+16], rax
	call	heap$free
	;
	mov	rdi, [rsp+8]
	mov	rdi, [rdi+X509_dsapubkey_ofs]
	mov	rsi, [rsp+16]
	xor	edx, edx
	call	buffer$append_base64decode
	; all good, fall through to the final check
calign
.finalcheck:
	; do simple sanity checking on the results (e.g. rsaprivate but no rsapub, dsaprivate but no dsapub)
	mov	rdi, [rsp+16]
	call	heap$free

	mov	rdi, [rsp+8]
	mov	rsi, [rdi+X509_privatekey_ofs]
	mov	rdx, [rdi+X509_dsaprivatekey_ofs]
	mov	rcx, [rdi+X509_pubkey_ofs]
	mov	r8, [rdi+X509_dsapubkey_ofs]
	
	test	rsi, rsi
	jz	.finalcheck_norsaprivate
	test	rcx, rcx
	jz	.failed				; got an rsaprivate, but no rsapub
calign
.finalcheck_norsaprivate:
	test	rdx, rdx
	jz	.finalcheck_nodsaprivate
	test	r8, r8
	jz	.failed				; got a dsaprivate but no dsapub
calign
.finalcheck_nodsaprivate:
	; otherwise, go ahead and let it ride
	mov	rdi, [rsp+24]
	call	buffer$destroy			; free our working buffer
	mov	rax, [rsp+8]			; our return
	add	rsp, 48
	epilog
calign
.failed:
	mov	rdi, [rsp+8]
	call	X509$destroy
	mov	rdi, [rsp+24]
	call	buffer$destroy
	xor	eax, eax
	add	rsp, 48
	epilog
if X509debug
cleartext .rsapubmsg, 'ssh_host_rsa_key.pub ['
cleartext .rsapubmsg2, ']'
end if
cleartext .defaultdir, '/etc/ssh'
cleartext .rsakey, '/ssh_host_rsa_key'
cleartext .rsapub, '/ssh_host_rsa_key.pub'
cleartext .dsakey, '/ssh_host_dsa_key'
cleartext .dsapub, '/ssh_host_dsa_key.pub'
cleartext .rsaprivatekey, '-----BEGIN RSA PRIVATE KEY-----'
cleartext .rsaprivateend, '-----END RSA PRIVATE KEY-----'
cleartext .dsaprivatekey, '-----BEGIN DSA PRIVATE KEY-----'
cleartext .dsaprivateend, '-----END DSA PRIVATE KEY-----'

end if


if used X509$new_pem | defined include_everything
	; single argument in rdi: filename of the PEM file
	; returns a new X509 object in rax, that may or may not be valid/complete
	; returns null in rax if PEM file empty/file error, or of nothing was found whatsoever
	; of interest in the given filename
falign
X509$new_pem:
	prolog	X509$new_pem
	sub	rsp, 48
	mov	[rsp], rdi
	call	file$mtime
	mov	[rsp+16], rax
	mov	rdi, [rsp]

	call	file$to_string
	mov	[rsp+8], rax

	mov	edi, X509_size
	call	heap$alloc_clear
	mov	[rsp], rax

	mov	rcx, [rsp+16]
	mov	rdx, [_epoll_tv_secs]
	mov	[rax+X509_mtime_ofs], rcx
	mov	[rax+X509_checktime_ofs], rdx

	; we need a working buffer
	call	buffer$new
	mov	[rsp+40], rax

	; private keys first
	xor	edx, edx
	mov	[rsp+16], rdx
calign
.psearch:
	mov	rdi, [rsp+8]
	mov	rsi, .rsaprivatekey
	mov	rdx, [rsp+16]
	call	string$indexof_ofs
	cmp	rax, -1
	je	.dsearch
	; rax contains the index of the start we are after
	mov	[rsp+24], rax
	mov	rdi, [rsp+8]
	mov	rsi, .rsaprivateend
	mov	rdx, rax
	call	string$indexof_ofs
	cmp	rax, -1
	je	.dsearch
	mov	[rsp+16], rax		; save the next search start location
	mov	rdi, [rsp+8]
	mov	rsi, [rsp+24]
	add	rsi, qword [.rsaprivatekey]
	mov	rdx, rax
	call	string$substring
	; so now we have a string representation of just the base64 encoded bit
	mov	[rsp+24], rax
	; it is assumed our working buffer is already clear/reset
	mov	rdi, [rsp+40]
	mov	rsi, rax
	xor	edx, edx
	call	buffer$append_base64decode
	mov	rcx, [rsp+40]		; working buffer
	mov	rdi, [rsp]		; X509 return object
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	X509$add_privatekey
	mov	rdi, [rsp+40]
	call	buffer$reset
	mov	rdi, [rsp+24]
	call	heap$free		; free our substring
	jmp	.psearch
calign
.dsearch:
	; dsa private keys next
	mov	rdi, [rsp+8]
	mov	rsi, .dsaprivatekey
	mov	rdx, [rsp+16]
	call	string$indexof_ofs
	cmp	rax, -1
	je	.csearch
	; rax contains the index of the start we are after
	mov	[rsp+24], rax
	mov	rdi, [rsp+8]
	mov	rsi, .dsaprivateend
	mov	rdx, rax
	call	string$indexof_ofs
	cmp	rax, -1
	je	.csearch
	mov	[rsp+16], rax		; save the next search start location
	mov	rdi, [rsp+8]
	mov	rsi, [rsp+24]
	add	rsi, qword [.dsaprivatekey]
	mov	rdx, rax
	call	string$substring
	; so now we have a string representation of just the base64 encoded bit
	mov	[rsp+24], rax
	; it is assumed our working buffer is already clear/reset
	mov	rdi, [rsp+40]
	mov	rsi, rax
	xor	edx, edx
	call	buffer$append_base64decode
	mov	rcx, [rsp+40]		; working buffer
	mov	rdi, [rsp]		; X509 return object
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	X509$add_dsaprivatekey
	mov	rdi, [rsp+40]
	call	buffer$reset
	mov	rdi, [rsp+24]
	call	heap$free		; free our substring
	jmp	.dsearch
calign
.csearch:
	; certificates next
	mov	rdi, [rsp+8]
	mov	rsi, .certificate
	mov	rdx, [rsp+16]
	call	string$indexof_ofs
	cmp	rax, -1
	je	.alldone
	; rax contains the index of the start we are after
	mov	[rsp+24], rax
	mov	rdi, [rsp+8]
	mov	rsi, .certificateend
	mov	rdx, rax
	call	string$indexof_ofs
	cmp	rax, -1
	je	.alldone
	mov	[rsp+16], rax		; save the next search start location
	mov	rdi, [rsp+8]
	mov	rsi, [rsp+24]
	add	rsi, qword [.certificate]
	mov	rdx, rax
	call	string$substring
	; so now we have a string representation of just the base64 encoded bit
	mov	[rsp+24], rax
	; it is assumed our working buffer is already clear/reset
	mov	rdi, [rsp+40]
	mov	rsi, rax
	xor	edx, edx
	call	buffer$append_base64decode
	mov	rcx, [rsp+40]		; working buffer
	mov	rdi, [rsp]		; X509 return object
	mov	rsi, [rcx+buffer_itself_ofs]
	mov	rdx, [rcx+buffer_length_ofs]
	call	X509$add_certificate
	mov	rdi, [rsp+40]
	call	buffer$reset
	mov	rdi, [rsp+24]
	call	heap$free		; free our substring
	jmp	.csearch
calign
.alldone:
	mov	rdi, [rsp+40]
	call	buffer$destroy

	mov	rdi, [rsp+8]
	call	heap$free

	mov	rax, [rsp]
	add	rsp, 48
	epilog
cleartext .rsaprivatekey, '-----BEGIN RSA PRIVATE KEY-----'
cleartext .rsaprivateend, '-----END RSA PRIVATE KEY-----'
cleartext .dsaprivatekey, '-----BEGIN DSA PRIVATE KEY-----'
cleartext .dsaprivateend, '-----END DSA PRIVATE KEY-----'
cleartext .certificate, '-----BEGIN CERTIFICATE-----'
cleartext .certificateend, '-----END CERTIFICATE-----'


end if





if used X509$append_der | defined include_everything
	; three arguments: rdi == json X509 object to append to, rsi == DER bytes, rdx == length of same
falign
X509$append_der:
	prolog	X509$append_der
	sub	rsp, 48
	xor	ecx, ecx
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], rdx
	mov	[rsp+32], rcx
	mov	[rsp+40], rcx
	call	string$new
	mov	rdi, rax
	call	json$newobject_nocopy
	mov	[rsp+24], rax		; our working object, with no name as yet
	mov	rsi, [rsp+8]
	mov	rdx, [rsp+16]
calign
.outerloop:
	cmp	rdx, 2
	jle	.eoc
	movzx	eax, byte [rsi]
	mov	ecx, eax
	and	eax, 0x1f
	add	rsi, 1
	sub	rdx, 1
	; decode the length as well
	movzx	r8d, byte [rsi]
	add	rsi, 1
	sub	rdx, 1
	test	r8d, 0x80
	; length in r8d is fine
	jz	.outerjump
	and	r8d, 0x7f	; length bytes
	test	r8d, r8d
	jz	.outerjump
	mov	r9d, r8d
	xor	r8d, r8d
.outerloop_length:
	test	rdx, rdx
	jz	.eoc
	movzx	r10d, byte [rsi]
	add	rsi, 1
	sub	rdx, 1
	shl	r8, 8
	or	r8, r10
	sub	r9d, 1
	jnz	.outerloop_length
	jmp	qword [rax*8+.asn1table]
calign
.outerjump:
	jmp	qword [rax*8+.asn1table]
calign
.boolean:

calign
.integer:

calign
.bitstring:

calign
.octetstring:

calign
.null:

calign
.objectid:

calign
.objectdesc:

calign
.external:

calign
.real:

calign
.enumerated:

calign
.utf8string:
	; outta here.
	add	rsp, 48
	epilog

calign
.sequence:
	mov	[rsp+24], r8		; store our length (even if it is indefinite)
	mov	dword [rsp+32], 1
	jmp	.outerloop

calign
.set:

calign
.numericstring:

calign
.printablestring:

calign
.t61string:

calign
.videotexstring:

calign
.ia5string:

calign
.utctime:

calign
.generalizedtime:

calign
.graphicstring:

calign
.visiblestring:

calign
.generalstring:

calign
.universalstring:

calign
.characterstring:

calign
.bmpstring:


calign
.eoc:
	; outta here.
	add	rsp, 48
	epilog
dalign
.asn1table:
	dq	.eoc, .boolean, .integer, .bitstring, .octetstring, .null, .objectid, .objectdesc, .external, .real, .enumerated, .eoc
	dq	.utf8string, .eoc, .eoc, .eoc, .sequence, .set, .numericstring, .printablestring, .t61string, .videotexstring, .ia5string
	dq	.utctime, .generalizedtime, .graphicstring, .visiblestring, .generalstring, .universalstring, .characterstring, .bmpstring
	dq	.eoc

end if