Coverage report: /home/ellis/comp/ext/ironclad/src/ciphers/cipher.lisp
Kind | Covered | All | % |
expression | 67 | 416 | 16.1 |
branch | 1 | 64 | 1.6 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;;; cipher.lisp -- generic functions for symmetric encryption
5
((mode :initarg :mode :accessor mode)
6
(mode-name :reader mode-name)
7
(initialized-p :initform nil :accessor initialized-p)))
9
;;; Block ciphers are denoted by the use of the {8,16,32,64,128}-byte-block-mixin.
10
(defclass stream-cipher (cipher)
13
(defmethod encrypt ((cipher cipher) plaintext ciphertext &key (plaintext-start 0) plaintext-end (ciphertext-start 0) handle-final-block &allow-other-keys)
14
(check-type plaintext vector)
15
(let ((plaintext-end (or plaintext-end (length plaintext))))
16
(funcall (slot-value (mode cipher) 'encrypt-function)
18
plaintext-start plaintext-end ciphertext-start
21
(defmethod decrypt ((cipher cipher) ciphertext plaintext &key (ciphertext-start 0) ciphertext-end (plaintext-start 0) handle-final-block &allow-other-keys)
22
(check-type ciphertext vector)
23
(let ((ciphertext-end (or ciphertext-end (length ciphertext))))
24
(funcall (slot-value (mode cipher) 'decrypt-function)
26
ciphertext-start ciphertext-end
27
plaintext-start handle-final-block)))
29
(defun encrypt-in-place (cipher text &key (start 0) end)
30
(encrypt cipher text text
31
:plaintext-start start :plaintext-end end
32
:ciphertext-start start))
34
(defun decrypt-in-place (cipher text &key (start 0) end)
35
(decrypt cipher text text
36
:ciphertext-start start :ciphertext-end end
37
:plaintext-start start))
39
;;; utilities for wordwise fetches and stores
41
;;; we attempt to make this as efficient as possible. the first check we
42
;;; do is to see whether or not the range
43
;;; [INITIAL-OFFSET, INITIAL-OFFSET + BLOCK-SIZE) is within the bounds of
44
;;; the array. if not, then we do the fetches as normal. if so, then we
45
;;; can either (DECLARE (SAFETY 0)) to avoid the bounds-checking on the
46
;;; fetches, or we can do full-word fetches if INITIAL-OFFSET is
47
;;; word-addressable and the implementation supports it.
49
(defmacro with-words (((&rest word-vars) array initial-offset
50
&key (size 4) (big-endian t))
52
(let ((ref-sym (ubref-fun-name (* size 8) big-endian))
53
(n-bytes (* (length word-vars) size)))
54
(flet ((generate-fetches (n-fetches)
55
(loop for offset from 0 by size below (* n-fetches size)
56
collect `(,ref-sym ,array (+ ,initial-offset ,offset)))))
57
`(multiple-value-bind ,word-vars (let ((length (length ,array)))
59
((<= ,initial-offset (- length ,n-bytes))
60
,(if (and (member :sbcl *features*)
62
(or (and big-endian (member :big-endian *features*))
63
(and (not big-endian) (member :little-endian *features*))))
64
`(if (logtest ,initial-offset (1- ,size))
66
(locally (declare (optimize (safety 0)))
67
(values ,@(generate-fetches (length word-vars))))
68
(let ((word-offset (truncate ,initial-offset 4)))
70
,@(loop for offset from 0 below (length word-vars)
71
collect `(sb-kernel:%vector-raw-bits ,array (+ word-offset ,offset))))))
72
`(locally (declare (optimize (safety 0)))
73
(values ,@(generate-fetches (length word-vars))))))
75
(values ,@(generate-fetches (length word-vars))))))
76
(declare (type (unsigned-byte ,(* size 8)) ,@word-vars))
77
(macrolet ((store-words (buffer buffer-offset &rest word-vars)
78
(loop for word-var in word-vars
79
for offset from 0 by ,size
80
collect `(setf (,',ref-sym ,buffer (+ ,buffer-offset ,offset)) ,word-var)
82
finally (return `(progn ,@stores)))))
85
(defmacro with-words (((&rest word-vars) array initial-offset
86
&key (size 4) (big-endian t))
88
(let ((ref-sym (ubref-fun-name (* size 8) big-endian)))
89
(loop for word-var in word-vars
90
for offset from 0 by size
91
collect `(,word-var (,ref-sym ,array (+ ,initial-offset ,offset)))
93
finally (return `(macrolet ((store-words (buffer buffer-offset &rest word-vars)
94
(loop for word-var in word-vars
95
for offset from 0 by ,size
96
collect `(setf (,',ref-sym ,buffer (+ ,buffer-offset ,offset)) ,word-var)
98
finally (return `(progn ,@stores)))))
100
(declare (type (unsigned-byte ,(* size 8)) ,@word-vars))
103
;;; mixins for dispatching
104
(defclass 8-byte-block-mixin ()
107
(defclass 16-byte-block-mixin ()
110
(defclass 32-byte-block-mixin ()
113
(defclass 64-byte-block-mixin ()
116
(defclass 128-byte-block-mixin ()
121
;;; the idea behind this is that one only has to implement encryption
122
;;; and decryption of a block for a particular cipher (and perhaps
123
;;; some key generation) and then "define" the cipher with some
124
;;; parameters. necessary interface functions will be auto-generated
127
;;; possible things to go in INITARGS
129
;;; * (:encrypt-function #'cipher-encrypt-block)
130
;;; * (:decrypt-function #'cipher-decrypt-block)
131
;;; * (:key-length (:fixed &rest lengths))
132
;;; * (:key-length (:variable low high increment))
133
;;; * (:constructor #'create-cipher-context)
134
(defmacro defcipher (name &rest initargs)
135
(%defcipher name initargs))
137
;;; KLUDGE: we add the blocksize to these two forms so that we can declare
138
;;; the type of the *-START parameters correctly. That is, good Lisp
139
;;; implementations will see that references into the plaintext and
140
;;; ciphertext can never overflow into bignum land; shorter code should
141
;;; then be generated. This is a kludge, however, because we're putting
142
;;; the blocksize in three different places: once in the encryptor, once
143
;;; in the decryptor, and once in the DEFCIPHER form. It would be nice
144
;;; if there was one single place to put everything.
145
(defmacro define-block-encryptor (algorithm blocksize &body body)
146
`(defun ,(symbolicate algorithm '#:-encrypt-block)
147
(context plaintext plaintext-start ciphertext ciphertext-start)
148
(declare (optimize (speed 3) (debug 0) (space 0)))
149
(declare (type simple-octet-vector plaintext ciphertext)
150
(type (integer 0 ,(- array-dimension-limit blocksize))
151
plaintext-start ciphertext-start))
154
(defmacro define-block-decryptor (algorithm blocksize &body body)
155
`(defun ,(symbolicate algorithm '#:-decrypt-block)
156
(context ciphertext ciphertext-start plaintext plaintext-start)
157
(declare (optimize (speed 3) (debug 0) (space 0)))
158
(declare (type simple-octet-vector ciphertext plaintext)
159
(type (integer 0 ,(- array-dimension-limit blocksize))
160
ciphertext-start plaintext-start))
163
(defmacro define-stream-cryptor (algorithm &body body)
164
`(defun ,(symbolicate algorithm '#:-crypt)
165
(context plaintext plaintext-start ciphertext ciphertext-start length)
166
(declare (optimize (speed 3) (debug 0) (space 0)))
167
(declare (type simple-octet-vector plaintext ciphertext))
168
(declare (type index plaintext-start ciphertext-start length))
171
;; Catch various errors.
172
(defmethod verify-key (cipher key)
173
;; check the key first
175
(error 'key-not-supplied :cipher cipher))
176
(unless (typep key '(vector (unsigned-byte 8)))
177
(error 'type-error :datum key :expected-type '(vector (unsigned-byte 8))))
178
;; hmmm, the key looks OK. what about the cipher?
179
(unless (member cipher (list-all-ciphers))
180
(error 'unsupported-cipher :name cipher)))
182
(defmethod schedule-key :before ((cipher cipher) key)
183
(verify-key cipher key))
186
(defclass cipher-info ()
187
((class-name :reader %class-name :initarg :class-name)
188
(name :reader cipher :initarg :cipher)
189
(block-length :reader %block-length :initarg :block-length)
190
(key-lengths :reader %key-lengths :initarg :key-lengths)))
192
(defmethod print-object ((object cipher-info) stream)
193
(print-unreadable-object (object stream :type t)
194
(format stream "~A" (cipher object))))
196
(defun %find-cipher (name)
198
(let ((name (massage-symbol name)))
199
(and name (get name '%cipher-info)))))
201
(defun (setf %find-cipher) (cipher-info name)
202
(setf (get (massage-symbol name) '%cipher-info) cipher-info))
204
(defmethod key-lengths (cipher)
205
(let ((cipher-info (%find-cipher cipher)))
206
(and cipher-info (%key-lengths cipher-info))))
208
(defmethod key-lengths ((cipher cipher))
209
(key-lengths (class-name (class-of cipher))))
211
(defmethod block-length ((cipher symbol))
212
(let ((cipher-info (%find-cipher (massage-symbol cipher))))
213
(and cipher-info (%block-length cipher-info))))
215
(defmethod block-length ((cipher cipher))
216
(block-length (class-name (class-of cipher))))
218
(defmethod block-length ((cipher 8-byte-block-mixin))
221
(defmethod block-length ((cipher 16-byte-block-mixin))
224
(defmethod block-length ((cipher 32-byte-block-mixin))
227
(defmethod block-length ((cipher 64-byte-block-mixin))
230
(defmethod block-length ((cipher 128-byte-block-mixin))
233
(defun list-all-ciphers ()
234
(loop for symbol being each external-symbol of (find-package :ironclad)
235
if (%find-cipher symbol)
236
collect (intern (symbol-name symbol) :keyword) into ciphers
237
finally (return (sort ciphers #'string<))))
239
(defun cipher-supported-p (name)
240
"Return T if the cipher NAME is supported as an argument to MAKE-CIPHER."
241
(not (null (%find-cipher name))))
243
(defun acceptable-key-lengths* (key-length-spec)
244
(ecase (car key-length-spec)
245
(:fixed (loop for length in (cdr key-length-spec)
246
collect `(= length ,length) into forms
247
finally (return `(or ,@forms))))
248
(:variable (destructuring-bind (low high increment) (cdr key-length-spec)
250
`(<= ,low length ,high)
251
;; Punt. It'd be a weird cipher implemented otherwise.
252
(error 'ironclad-error :format-control "Need to implement the (/= INCREMENT 1) case"))))))
254
(defun acceptable-key-lengths (key-length-spec)
255
(ecase (car key-length-spec)
256
(:fixed (cdr key-length-spec))
257
(:variable (destructuring-bind (low high increment) (cdr key-length-spec)
258
(loop for i from low to high by increment
261
(defun generate-key-verifier-methods (name key-length-spec)
262
(let ((acceptable-key-lengths (acceptable-key-lengths key-length-spec)))
263
`(defmethod verify-key ((cipher ,name) (key vector))
264
(check-type key (array (unsigned-byte 8) (*)))
265
(let ((length (length key)))
267
(,(acceptable-key-lengths* key-length-spec) (copy-seq key))
268
(t (error 'invalid-key-length
270
:accepted-lengths ',acceptable-key-lengths)))))))
272
(defun generate-common-cipher-methods (name block-length key-length-spec)
274
;; make sure we pass in valid keys
275
,(generate-key-verifier-methods name key-length-spec)
276
(setf (%find-cipher ',name)
277
(make-instance 'cipher-info
280
:block-length ,block-length
281
:key-lengths ',(acceptable-key-lengths key-length-spec)))))
283
(defun generate-block-cipher-forms (name key-length-spec
284
encrypt-function decrypt-function)
285
(declare (ignorable key-length-spec))
287
(defmethod encrypt-function ((cipher ,name))
289
(defmethod decrypt-function ((cipher ,name))
290
#',decrypt-function)))
292
(defun generate-stream-cipher-forms (name key-length-spec crypt-function)
293
(declare (ignorable key-length-spec))
295
(defmethod encrypt-function ((cipher ,name))
297
(defmethod decrypt-function ((cipher ,name))
300
(defun %defcipher (name initargs)
301
(let ((encrypt-function nil)
302
(decrypt-function nil)
306
(key-length-spec nil)
308
(declare (ignorable constructor))
309
(loop for (arg value) in initargs
312
(if (not encrypt-function)
313
(setf encrypt-function value)
314
(error 'ironclad-error :format-control "Specified :ENCRYPT-FUNCTION multiple times.")))
316
(if (not decrypt-function)
317
(setf decrypt-function value)
318
(error 'ironclad-error :format-control "Specified :DECRYPT-FUNCTION multiple times.")))
320
(if (not crypt-function)
321
(setf crypt-function value)
322
(error 'ironclad-error :format-control "Specified :CRYPT-FUNCTION multiple times.")))
328
(error 'ironclad-error :format-control "Specified :BLOCK-LENGTH multiple times."))
329
((or (not (integerp value))
331
(error 'ironclad-error :format-control ":BLOCK-LENGTH must be a positive, integral number."))
333
(setf block-length value))))
337
(error 'ironclad-error :format-control "Specified :KEY-LENGTH multiple times."))
339
(error 'ironclad-error :format-control ":KEY-LENGTH value must be a list."))
340
((and (not (eq :fixed (car value)))
341
(not (eq :variable (car value))))
342
(error 'ironclad-error :format-control "First element of :KEY-LENGTH spec must be either :FIXED or :VARIABLE."))
343
((eq :fixed (car value))
345
(every #'integerp (cdr value))
346
(every #'plusp (cdr value)))
347
(setf key-length-spec value)
348
;;; FIXME: better error message
349
(error 'ironclad-error :format-control "bad :FIXED specification for :KEY-LENGTH.")))
350
((eq :variable (car value))
351
(if (and (null (nthcdr 4 value))
352
(every #'integerp (cdr value))
353
(every #'plusp (cdr value))
354
(< (cadr value) (caddr value)))
355
(setf key-length-spec value)
356
(error 'ironclad-error :format-control "bad :VARIABLE specification for :KEY-LENGTH."))))))
358
((and (eq mode :block) key-length-spec encrypt-function decrypt-function)
361
,(generate-common-cipher-methods name block-length key-length-spec)
362
,(generate-block-cipher-forms name key-length-spec
363
encrypt-function decrypt-function))))
364
((and (eq mode :stream) crypt-function key-length-spec)
367
,(generate-common-cipher-methods name 1 key-length-spec)
368
,(generate-stream-cipher-forms name key-length-spec crypt-function))))
370
(error 'ironclad-error :format-control "Didn't specify all required fields for DEFCIPHER"))))))