;;; digest.lisp ;;; ;;; Copyright (C) 2012 Mark Evenson ;;; $Id$ ;;; This program is free software; you can redistribute it and/or ;;; modify it under the terms of the GNU General Public License ;;; as published by the Free Software Foundation; either version 2 ;;; of the License, or (at your option) any later version. ;;; ;;; This program 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 this program; if not, write to the Free Software ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;; ;;; As a special exception, the copyright holders of this library give you ;;; permission to link this library with independent modules to produce an ;;; executable, regardless of the license terms of these independent ;;; modules, and to copy and distribute the resulting executable under ;;; terms of your choice, provided that you also meet, for each linked ;;; independent module, the terms and conditions of the license of that ;;; module. An independent module is a module which is not derived from ;;; or based on this library. If you modify this library, you may extend ;;; this exception to your version of the library, but you are not ;;; obligated to do so. If you do not wish to do so, delete this ;;; exception statement from your version. (require :java) (in-package :system) (defun asciify (digest) (format nil "~{~2,'0X~}" (mapcar (lambda (b) (if (< b 0) (+ 256 b) b)) (java::list-from-jarray digest)))) ;;;; Really needs to concatenate all input into a single source of ;;;; bytes, running digest over that concatentation. (defun sha256 (&rest paths-or-strings) ;;; XXX more than one arg is very broken. "Returned ASCIIfied representation of SHA256 digest of byte-based resource at PATHS-OR-STRINGs." (unless (and (null (rest paths-or-strings)) (pathnamep (first paths-or-strings))) (warn "Unaudited computation of cryptographic digest initiated.")) ;; TODO Need tests with some tool for verification (let ((first (first paths-or-strings)) (rest (rest paths-or-strings))) (concatenate 'string (when first (asciify (typecase first (pathname (digest first)) (string (digest first)) (null) (list (concatenate 'string (sha256 (first first)) (sha256 (rest first))))))) (when rest (sha256 rest))))) #+nil ;; Bugs out the compiler (defun sha256 (paths-or-strings) (labels ((walk (p-or-s) ((atom p-or-s) (typecase p-or-s (pathname (digest-path p-or-s)) (string (error "Somebody implement me please")))) ((cons p-or-s) (walk (first p-or-s) (rest p-or-s))))) (concatenate 'string (walk paths-or-strings)))) (defgeneric digest (resource &key (digest 'sha-256)) (:documentation "Digest byte based resource at RESOURCE.")) (defun digest-path (path) (asciify (digest path 'nio 'sha-256))) (defvar *digest-types* '((sha-1 . "SHA-1") (sha-256 . "SHA-256") (sha-512 . "SHA-512")) "Normalization of cryptographic digest naming.") ;;; Implementation (defconstant +byte-buffer-rewind+ (java:jmethod "java.nio.ByteBuffer" "rewind")) (defconstant +byte-buffer-get+ (java:jmethod "java.nio.ByteBuffer" "get" "[B" "int" "int")) (defconstant +digest-update+ (java:jmethod "java.security.MessageDigest" "update" "[B" "int" "int")) (defmethod digest ((url pathname) &key (digest 'sha-256)) (digest-nio url :digest digest)) (defun digest-nio (source &key (digest 'sha-256)) "Calculate digest with default of :SHA-256 pathname specified by URL. Returns an array of JVM primitive signed 8-bit bytes. Uses \"New I/O\" in JVM \"worse named API of all time\". *DIGEST-TYPES* controls the allowable digest types." (let* ((channel (typecase source (pathname (java:jcall "getChannel" (java:jnew "java.io.FileInputStream" (namestring source)))) (string (java:jstatic "newChannel" "java.nio.channels.Channels" (java:jnew "java.io.ByteArrayInputStream" (java:jcall "getBytes" source)))) (error "Typecase failed of object of type ~S." source))) (digest-type (cdr (assoc digest *digest-types*))) (digest (java:jstatic "getInstance" "java.security.MessageDigest" digest-type)) (length 8192) (buffer (java:jstatic "allocateDirect" "java.nio.ByteBuffer" length)) (array (java:jnew-array "byte" length))) (do ((read (java:jcall "read" channel buffer) (java:jcall "read" channel buffer))) ((not (> read 0))) (java:jcall +byte-buffer-rewind+ buffer) (java:jcall +byte-buffer-get+ buffer array 0 read) (java:jcall +byte-buffer-rewind+ buffer) (java:jcall +digest-update+ digest array 0 read)) (java:jcall "digest" digest))) (defmethod digest ((source string) &key (digest 'sha-256)) (digest-nio source :digest digest)) (export 'sha256 :system)