; we initialize some alists to '( (#f . #f) ) so we can operate on them with
; alist-set!

; The new format is:
; attestation: ("message" signatures)
; signature: (signer-id time sig)
; sig: "<<time testor-id message>sha1>+priv-signer"

; To do: talk to inter-yenta to fetch needed information

(yreq "Utilities/yenta-utils")

(define (attestation-exists? att attestations)
  (already-exists? att attestations))

(define (make-signature message testor-id)
  (let* ((signature-time (current-time))
	 (signed (format #f "~s ~A ~A" signature-time testor-id
			 message))
	 (key (ssl:der-string->rsa-private-key *local-yenta-priv-key*))
	 (result (list *local-yenta-id* signature-time
		       (ssl:rsa-sign-sha1-hash signed key))))
    (ssl:free-rsa! key)
    result))

(define (test-signature message testor-id signature)
  (let* ((signed (format #f "~s ~A ~A"
			 (cadr signature)
			 testor-id
			 message))
	 (key-string (identity:get-key (car signature)))
	 (key (if key-string
		  (ssl:der-string->rsa-public-key key-string)
		  #f))
	 (result (cond ((not key) 'maybe)
		       ((not (ssl:rsa-verify-sha1-hash signed
						      (caddr signature)
						      key))
			'no)
		       (else 'yes))))
    (when key
      (ssl:free-rsa! key))
    (inc! *ctr:sigs-verified*)
    result))

(define (trust-signer signer message testor)
  (if (equal? signer *local-yenta-id*)
      'yes
      (if (equal? signer testor)
	  'no
	  'yes)))
; for now we trust everyone to sign everything for other people, but not for
; themselves. We also trust ourselves always.

(define (better-of crit1 crit2)
  (cond ((eq? crit1 'yes) 'yes)
	((eq? crit2 'yes) 'yes)
	((eq? crit1 'maybe) 'maybe)
	(else crit2)))

(define (worse-of crit1 crit2)
  (cond ((eq? crit1 'no) 'no)
	((eq? crit2 'no) 'no)
	((eq? crit1 'maybe) 'maybe)
	(else crit2)))

(define (test-attestation attestation testor)
  (let ((self-check (if (equal? testor (caar (cadr attestation))) ;self-sig?
			(test-signature (car attestation) testor
					(caadr attestation))
			'no)))
    (if (not (eq? self-check 'no))
	(do ((result
	      'no
	      (let* ((signature (car lst))
		     (signer (trust-signer (car signature) (car attestation) 
					   testor))
		     (signed (if (not (eq? signer 'no))
				 (test-signature (car attestation)
						 testor signature)
				 'no)))
		(better-of result (worse-of signer signed))))
	     (lst (cadr attestation) (cdr lst)))
	    ((or (null? lst) (eq? result 'yes)) (worse-of result self-check)))
	'no)))
; If the first signature is known to be bad, returns 'no
; If the first signature is unknown, returns 'maybe if any signature may be
; correct and has a possibly trusted signer.
; Otherwise,
; If there's a signature which we know is correct, and whose signer we trust
; to sign this attestation, returns 'yes.
; Otherwise, if there's a signature which we don't know is incorrect, and
; whose signer we may trust, returns 'maybe.
; Otherwise, returns 'no.

(define (add-signature! att signature)
  ; note that the self-signature isn't done here, so alist-set! is fine.
  (alist-set! (car signature)
	      (cdr signature) (cadr att)))

(define (add-attestation! message)
  (inc! *ctr:atts-made*)
  (stats:log-event ':attestation-created)
  (identity:add-attestation! *local-yenta-id*
			     (list message
				   (list (make-signature message
							 *local-yenta-id*)))))

(define (delete-attestation! message)
  (inc! *ctr:atts-removed*)
  (identity:delete-attestation! *local-yenta-id* message))

;(define (delete-attestation! message)
;  (inc! *ctr:atts-removed*)
;  (stats:log-event ':attestation-deleted)
;  (if (attestation-exists? message *attestations*)
;      (set! *attestations* (alist-remove message *attestations*))
;      "Attestation does not exist"))

(define (receive-signature! testor message signature)
  (let ((att (identity:get-attestation testor message)))
    (cond ((and att
		(eq? 'yes (test-signature message testor signature)))
	   (inc! *ctr:sigs-received*)
	   (add-signature! att signature)
	   #t)
	  (t
	   #f))))

; If we don't have this attestation, never mind.
; If the signature doesn't check out, or we can't test it, never mind.
; Otherwise, add it. Returns #t if we added it, #f otherwise.
; Note that we may pass on signatures we don't add, because other people may
; know of the signer.

(define (sign-foreign! testor message)
  (let ((att (identity:get-attestation testor message)))
    (cond (att
	   (add-signature! att (make-signature message testor))
	   (inc! *ctr:sigs-made*)
	   #t)
	  (t
	   #f))))

(define (receive-attestation! testor attestation)
  (format-debug 0 "~&testor = ~S~&attestation = ~S~&equal test = ~S, test-signature = ~S~&"
		testor
		attestation
		(equal? testor (caar (cadr attestation)))
		(test-signature (car attestation) testor
				(caadr attestation)))
  (cond ((and (equal? testor (caar (cadr attestation))) ;self-sig?
	      (eq? (test-signature (car attestation) testor
				   (caadr attestation))
		   'yes))
	 (identity:add-attestation! testor attestation)
	 #t)
	(t
	 #f)))

;;; End of file.
