PROGRAM crypedt (INPUT, OUTPUT) ; { IDENT: V04-000 } {**************************************************************************** ;* * ;* COPYRIGHT (c) 1978, 1980, 1982, 1984 BY * ;* DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS. * ;* ALL RIGHTS RESERVED. * ;* * ;* THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED * ;* ONLY IN ACCORDANCE WITH THE TERMS OF SUCH LICENSE AND WITH THE * ;* INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR ANY OTHER * ;* COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY * ;* OTHER PERSON. NO TITLE TO AND OWNERSHIP OF THE SOFTWARE IS HEREBY * ;* TRANSFERRED. * ;* * ;* THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE * ;* AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT * ;* CORPORATION. * ;* * ;* DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE OR RELIABILITY OF ITS * ;* SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL. * ;* * ;* * ;**************************************************************************** ; ; Facility: ; ; VAX/VMS Data Encryption Facility ; ; Abstract: ; ; This module implements a text editor using EDTSHR to manipulate ; encrypted text files. This is written as a demonstration of ; the use of callable EDT and the ENCRYPT facility and not as ; a production program for use in a secure environment. ; ; Author: J. Eric Pollack ; ; Date: July 1983 ; ; Modified By: ; ; V05-001 PFM0014 14-Dec-1988 ; Fixed function call typo ; ; V01-001 TPR0000/JEP00008 3-Feb-1984 ; Correct missing input file error, Screen I/O. ; ; V01-001 JEP0007 6-Nov-1983 ; Correct decrypted record length handling. } { Design Discussion and limitations. This program implements an editor based on callable EDT which maintains encrypted input and output files. The callable EDT sharable image is used with this program intercepting all text file I/O operations and performing the associated decrypt or encrypt operations on each Input, Output, Include, Write, and Journal files may be encrypted. The EDT workfile, which is normally deleted upon exit, is not encrypted. The journal file is encrypted if and only if the input file is encrypted. The encryption of text files is handled on a record by record basis. The profile of an encrypted text file is similar to its cleartext counterpart so that some information about the cleartext may be obtained from record length, etc. An encrypted text file as generated by this editor is not compatible with the format created by the ENCRYPT/DECRYPT file command. Command parameters and qualifiers are the same as for the normal EDT editor. CRYPEDT assumes that EDT is running in change mode on a CRT type terminal. DCL Command definition: DEFINE VERB CRYPEDT image cryptoedt parameter p1,label=input,prompt="File",value(required,type=$infile) qualifier output, value(type=$outfile) qualifier read_only qualifier command, default,value(type=$infile) qualifier recover qualifier journal, default,value(type=$infile) qualifier create,default Pascal Command: $ PASCAL/NOOPTIMIZE CRYPEDT Linker Command: $ LINK/EXE=CRYPTOEDT/MAP=CRYPTOEDT CRYPEDT References for EDTSHR and ENCRYPT$_SHR are supplied from the system sharable library. } CONST max_linelength = 256+8 ; { maximum length of text line to encrypt } max_keyname = 128 ; { maximum length of keyname string } { Descriptor type and class codes } DSC$K_DTYPE_T = 14 ; { Text type } DSC$K_CLASS_VS = 11 ; { Varying string class } DSC$K_CLASS_D = 2 ; { Dynamic String class } DSC$K_CLASS_S = 1 ; { Static string class } { Stream ID codes} EDT$K_COMMAND_FILE = 1 ; EDT$K_INPUT_FILE = 2 ; EDT$K_OUTPUT_FILE = 3 ; EDT$K_JOURNAL_FILE = 4 ; EDT$K_INCLUDE_FILE = 5 ; EDT$K_WRITE_FILE = 6 ; max_streams = 6 ; { maximum i/o streams to handle } { File operation codes } EDT$K_OPEN_INPUT = 1 ; EDT$K_OPEN_OUTPUT_SEQ = 2 ; EDT$K_OPEN_OUTPUT_NOSEQ = 3 ; EDT$K_OPEN_IN_OUT = 4 ; EDT$K_GET = 5 ; EDT$K_PUT = 6 ; EDT$K_CLOSE_DEL = 7 ; EDT$K_CLOSE = 8 ; max_code = 8 ; { CLI status values } cli$_present = %X3FD19 ; cli$_defaulted = %X3FD21 ; cli$_absent = %X381F0 ; cli$_negated = %X381F8 ; rms$_eof = %X1827A ; ss$_normal = %X1 ; TYPE status = [LONG] INTEGER ; { Status returned from functions } Varying_string = VARYING [max_linelength] OF CHAR ; String_len = [WORD] 0..65535 ; Word_integer = [WORD] 0..65535 ; Options_mask = RECORD { EDT control mask } recover : [BIT,POS(0)] BOOLEAN ; command : [BIT,POS(1)] BOOLEAN ; nojournal : [BIT,POS(2)] BOOLEAN ; nooutput : [BIT,POS(3)] BOOLEAN ; nocommand : [BIT,POS(4)] BOOLEAN ; nocreate : [BIT,POS(5)] BOOLEAN ; fill : [BIT(32-6),POS(6)] BOOLEAN ; END; String_descriptor = RECORD { VAX Descriptor } String_length : string_len ; String_type : [BYTE] 0..255 ; String_class : [BYTE] 0..255 ; String_address : ^Varying_string ; END; Packed_record = RECORD { text record with control information for encryption } Cleartext_length : String_len ; Cleartext : ARRAY [1..max_linelength] OF CHAR ; END; VAR input_filename , output_filename , command_filename , journal_filename : VARYING[80] OF CHAR; edt_options_mask : options_mask ; { array of pointers to encrypt stream contexts } Stream_pointer : [VOLATILE] ARRAY [1..max_streams] OF INTEGER ; { names of i/o streams } Stream_action : [VOLATILE] ARRAY [1..max_code] OF VARYING[20] OF CHAR := ( 'reading' , 'writing', 'writing' , 'reading and writing' , 'error', 'error', 'error', 'error' ) ; { Define the inteface to callable EDT as documented in the EDT Reference Manual. } FUNCTION EDT$EDIT ( %DESCR infile : VARYING[$l1] OF CHAR ; %DESCR outfile : VARYING[$l2] OF CHAR ; %DESCR comfile : VARYING[$l3] OF CHAR ; %DESCR joufile : VARYING[$l4] OF CHAR ; options : Options_mask ; FUNCTION File_IO ( Code : INTEGER ; Stream : INTEGER ; VAR Srecord : String_descriptor; VAR Rhb : String_descriptor) : Status ; FUNCTION Work_IO : Status ; FUNCTION Xlate : Status ) : Status ; EXTERNAL; [ASYNCHRONOUS] FUNCTION EDT$FILEIO ( Code : INTEGER ; Stream : INTEGER ; VAR Srecord : String_descriptor; VAR Rhb : String_descriptor) : Status ; EXTERNAL; FUNCTION EDT$WORKIO : Status ; EXTERNAL; FUNCTION EDT$XLATE : Status ; EXTERNAL; { Define the call templates for various system and library functions used below. } { CLI interface -- Get parameter or qualifier value from DCL } [ASYNCHRONOUS,EXTERNAL(CLI$GET_VALUE)] FUNCTION $GETVALUE ( %STDESCR Key_descriptor : PACKED ARRAY [$l1..$u1:INTEGER] OF CHAR ; %DESCR Value_descriptor : VARYING [$u2] OF CHAR ) : Status ; EXTERNAL; { CLI interface -- Determine whether parameter or qualifer is present } [ASYNCHRONOUS,EXTERNAL(CLI$PRESENT)] FUNCTION $PRESENT ( %STDESCR Key_descriptor : PACKED ARRAY [$l1..$u1:INTEGER] OF CHAR):Status ; EXTERNAL; { String package -- Copy string } [ASYNCHRONOUS,EXTERNAL(STR$COPY_DX)] FUNCTION String_copy_desc_str ( %DESCR Dest : VARYING [$u1] OF CHAR ; Source : String_descriptor) : Status ; EXTERNAL; [ASYNCHRONOUS,EXTERNAL(STR$COPY_DX)] FUNCTION String_copy_desc_desc ( VAR Dest : String_descriptor ; Source : VARYING [max_linelength] OF CHAR) : Status ; EXTERNAL; { String package -- Upcase string } [ASYNCHRONOUS,EXTERNAL(STR$UPCASE)] FUNCTION Upcase_string ( %DESCR Dest : VARYING [$u1] OF CHAR ; Source : String_descriptor) : Status ; EXTERNAL; { String package -- Allocate dynamic string storage } [ASYNCHRONOUS,EXTERNAL(STR$GET1_DX)] FUNCTION Get_dynamic_string ( length : Word_integer ; VAR Descriptor : String_descriptor ) : Status ; EXTERNAL; { String package -- Release dynamic string storage } [ASYNCHRONOUS,EXTERNAL(STR$FREE1_DX)] FUNCTION Release_descriptor ( VAR Descriptor : String_descriptor) : Status ; EXTERNAL; { Screen package -- Erase all or part of display screen } [ASYNCHRONOUS,EXTERNAL] FUNCTION LIB$ERASE_PAGE ( line_number : Word_integer ; Column_number : Word_integer ) : Status ; EXTERNAL ; { Screen package -- Put text to screen } [ASYNCHRONOUS,EXTERNAL] FUNCTION LIB$PUT_SCREEN ( %DESCR out_text : VARYING [$u1] OF CHAR ; line_number : Word_integer ; Column_number : Word_integer ; Flags : Word_integer := %IMMED 0 ) : Status ; EXTERNAL ; { Screen package -- Get response to prompt } [ASYNCHRONOUS,EXTERNAL] FUNCTION LIB$GET_SCREEN ( VAR Dest : String_descriptor ; { %DESCR Input_text : VARYING [$u1] OF CHAR ;} %DESCR Prompt_string : VARYING [$u2] OF CHAR := %IMMED 0 ; VAR Out_len : Word_integer := %IMMED 0 ) : Status ; EXTERNAL ; { Screen package -- position cursor on screen } [ASYNCHRONOUS,EXTERNAL] FUNCTION LIB$SET_CURSOR ( line_number : Word_integer ; Column_number : Word_integer ) : Status ; EXTERNAL ; { VMS condition handler -- Signal Error } [EXTERNAL(LIB$SIGNAL),ASYNCHRONOUS] PROCEDURE $SIGNAL ( %IMMED Condition : INTEGER ); EXTERNAL; { Define the interface to the ENCRYPT facility utility routines } { Encrypt library -- HOL initialize encrypt/decrypt stream } [ASYNCHRONOUS,EXTERNAL(ENCRYPT$INIT)] FUNCTION ENCRYPT$INIT ( VAR Context : INTEGER ; %STDESCR Algorithm_name : PACKED ARRAY [$l2..$u2:INTEGER] OF CHAR ; Key_type : INTEGER ; %STDESCR Key_name : PACKED ARRAY[$l3..$u3:INTEGER] OF CHAR ) : Status ; EXTERNAL; { Encrypt library -- HOL encrypt record } [ASYNCHRONOUS,EXTERNAL(ENCRYPT$ENCRYPT)] FUNCTION $ENCRYPT ( Context : INTEGER ; Input : String_descriptor; Output : String_descriptor) : Status ; EXTERNAL; { Encrypt library -- HOL decrypt record } [ASYNCHRONOUS,EXTERNAL(ENCRYPT$DECRYPT)] FUNCTION $DECRYPT ( Context : INTEGER ; Input : String_descriptor; Output : String_descriptor ) : Status ; EXTERNAL; { Encrypt library -- HOL close encrypt/decrypt stream } [ASYNCHRONOUS,EXTERNAL(ENCRYPT$FINI)] FUNCTION ENCRYPT$FINI ( VAR Context : INTEGER ) : Status ; EXTERNAL; [ASYNCHRONOUS] PROCEDURE Check_Status ( IO_Status : Status ) ; { Functional Description: Verify IO status from operation and signal any non-success status. Input: status value to check. Output: None. Signals any non-success status. } BEGIN IF IO_Status <> SS$_NORMAL THEN $SIGNAL ( IO_Status ) END; PROCEDURE Command_Parse ; { Functional Description: Obtain the command line parameters and qualifiers from DCL and build the appropriate string descriptors and control masks for EDT. Input: DCL context Output: String descriptors are built for the input, output, journal, and command file as appropriate. Qualifiers for read-only, etc. are noted. Algorithm and Keyname values are noted for each file as specified. } VAR output_status, { status values from CLI$... calls } journal_status, recover_status, create_status, command_status, read_only_status : Status ; BEGIN $Getvalue ( 'INPUT' , input_filename ) ; { Get the input filespec } { Is there an output file? } output_status := $present ('OUTPUT') ; { There is always output by default. Turn on the READ_ONLY bit if /NOOUTPUT was explicitly specified } IF output_status = CLI$_PRESENT THEN BEGIN edt_options_mask.nooutput := FALSE ; $getvalue ( 'OUTPUT' , output_filename ) ; { Get output file spec } END ELSE IF output_status = CLI$_NEGATED THEN edt_options_mask.nooutput := TRUE ELSE edt_options_mask.nooutput := FALSE ; { Check for explicit command file specification. } command_status := $present ( 'COMMAND' ) ; IF command_status = CLI$_NEGATED THEN edt_options_mask.nocommand := TRUE ELSE BEGIN edt_options_mask.nocommand := FALSE ; { Get command file spec if specified } IF ODD ($getvalue ( 'COMMAND' , command_filename )) THEN edt_options_mask.command := TRUE ; END; { Check for explicit journal file specificaton. } journal_status := $present ( 'JOURNAL' ) ; IF journal_status = CLI$_NEGATED THEN edt_options_mask.nojournal := TRUE ELSE BEGIN edt_options_mask.nojournal := FALSE ; $getvalue ( 'JOURNAL' , journal_filename ) ; END; { Look for /create qualifier } create_status := $present ( 'CREATE' ) ; IF create_status = CLI$_NEGATED THEN edt_options_mask.nocreate := TRUE ELSE edt_options_mask.nocreate := FALSE ; { Look for /recover qualifier } recover_status := $present ( 'RECOVER' ); IF ODD (recover_status) THEN edt_options_mask.recover := TRUE ; { Look for /readonly qualifier } read_only_status := $present ( 'READ_ONLY' ) ; IF read_only_status <> cli$_absent THEN BEGIN { If journaling was not indicated explicitly, set it based on the value of /readonly } IF ( journal_status = cli$_absent ) OR ( journal_status = cli$_defaulted ) THEN edt_options_mask.nojournal := FALSE ; IF ODD (read_only_status) THEN edt_options_mask.nojournal := TRUE ; { If an output file was not indicated explicitly, set it based on the value of /readonly } IF ( output_status = cli$_absent ) OR ( output_status = cli$_defaulted ) THEN edt_options_mask.nooutput := FALSE ; IF ODD (read_only_status) THEN edt_options_mask.nooutput := TRUE ; END; END; [ASYNCHRONOUS] PROCEDURE Get_key_name( Code:INTEGER ; File_name : VARYING[name_len] OF CHAR ; VAR Key_name : VARYING [$u3] OF CHAR ; VAR Key_name_len : String_len ); { Functional Description: Obtain Key_name from operator for a particular file. The bottom three lines of the screen are cleared, a message indicating the IO stream and the filename displayed, and the user prompted for the name of the encrypt/decrypt key to be used on that stream. Input: Code = EDT code for the I/O operation beging performed File_name = the user or command specified filename Output: key_name = user typed in key name string key_name_length = character count of key name value } VAR Prompt : VARYING[80] OF CHAR ; getscr_descriptor : String_descriptor ; IO_status : Status ; BEGIN getscr_descriptor.string_length :=0 ; getscr_descriptor.string_type := DSC$K_DTYPE_T ; getscr_descriptor.string_class := DSC$K_CLASS_D ; getscr_descriptor.string_address := NIL ; Key_name := '' ; { Reset key name value } Prompt := 'Enter RETURN if not encrypted or KEY_NAME for ' + Stream_action[Code] + ' File'; IO_status := LIB$ERASE_PAGE ( 22,1 ) ; IO_status := LIB$PUT_SCREEN ( Prompt , 22, 1 , 2); IO_status := LIB$PUT_SCREEN ( File_name , 23, 1 , 2); IO_status := LIB$SET_CURSOR ( 24, 1 ); IO_status := LIB$GET_SCREEN ( Getscr_descriptor, '?:' , Key_name_len); IO_status := Upcase_string ( Key_name, Getscr_descriptor) ; Release_descriptor ( Getscr_descriptor ) END ; [ASYNCHRONOUS,GLOBAL] FUNCTION Edt_File_IO ( Code:INTEGER ; Stream:INTEGER ; VAR Srecord : String_descriptor; VAR Rhb : String_descriptor) :STATUS ; { Functional Description: Perform each file open/close/input/output operation for EDT. This routine is called directly from EDT to perform each normal file I/O operation. As appropriate, local processing of the request is performed and if any RMS operations are required, the EDT standard I/O routine is called to perform the operation. Input: Code = EDT code for the operation to be performed Stream = EDT code for the I/O stream to be manipulated Srecord = descriptor for filename or buffer Rhb = descriptor for record header information } VAR IO_status : Status ; { Local status value } buffer_descriptor : String_descriptor ; crypt_buffer : [VOLATILE] Varying_string; crypt_buffer_desc : String_descriptor ; key_name : VARYING [max_keyname] OF CHAR ; key_name_length : string_len ; line_buffer : VARYING [max_linelength] OF CHAR ; BEGIN { Initialize local string descriptors to reference local temporary data buffers. } buffer_descriptor.string_length :=0 ; buffer_descriptor.string_type := DSC$K_DTYPE_T ; buffer_descriptor.string_class := DSC$K_CLASS_D ; buffer_descriptor.string_address := NIL ; { For the Command File, always pass it on to EDT. Command files are never encrypted } IF Stream = EDT$K_COMMAND_FILE THEN BEGIN Edt_File_IO := EDT$FILEIO ( Code , Stream , Srecord , Rhb ) ; END ELSE { For all other files, we may have to encrypt/decrypt records. } CASE Code OF EDT$K_OPEN_INPUT, EDT$K_OPEN_OUTPUT_SEQ , EDT$K_OPEN_OUTPUT_NOSEQ , EDT$K_OPEN_IN_OUT: BEGIN IF Stream <> EDT$K_JOURNAL_FILE THEN BEGIN String_copy_desc_str ( line_buffer , Srecord ) ; Get_key_name ( Code , line_buffer , key_name , key_name_length ) ; IF LENGTH(Key_name) <> 0 THEN BEGIN { Initialize the indicated stream with the specified key The encryption algorithm is fixed at the DES Cypher block chaining algorithm. } IO_Status := ENCRYPT$INIT ( Stream_pointer[Stream], 'DESCBC' , 0 , (Key_name) ) ; Check_Status ( IO_Status ) ; { If we just initalized the INPUT stream, setup the JOURNAL stream with the same encrypt key as well. } IF Stream = EDT$K_INPUT_FILE THEN IO_Status := ENCRYPT$INIT ( Stream_pointer[EDT$K_JOURNAL_FILE], 'DESCBC' , 0 , (Key_name) ) ; Check_Status ( IO_Status ) ; END; END; { Now do the actual RMS operation for this stream } Edt_File_IO := EDT$FILEIO ( Code , Stream , Srecord , Rhb ) ; END; EDT$K_GET: BEGIN IF Stream_pointer[Stream] = 0 { If not decrypting this stream } THEN IO_Status := EDT$FILEIO ( Code , Stream , Srecord , Rhb ) ELSE BEGIN { Obtain the next encrypted record from the file and put it to a dynamic string. See note below under the put function for an explaination of the record function. } IO_Status := EDT$FILEIO ( Code, Stream, buffer_descriptor, Rhb ); IF IO_Status <> RMS$_EOF THEN BEGIN Check_Status ( IO_Status ) ; { Create a static descriptor referencing the string storage area of crypt_buffer. After decryption, the first word of the storage area will be set with the actual length parameter for the true length of the string. } crypt_buffer_desc.string_length := max_linelength ; crypt_buffer_desc.string_address := ADDRESS(crypt_buffer) ; crypt_buffer_desc.string_type := DSC$K_DTYPE_T ; crypt_buffer_desc.string_class := DSC$K_CLASS_S ; { Decrypt the record into the varying string crypt_buffer. } IO_Status := $DECRYPT ( Stream_pointer[Stream] , buffer_descriptor, crypt_buffer_desc ) ; { And copy the exact number of cleartext characters that were encrypted to the target record descriptor leaving the extra counts and roundup pad bytes behind } String_copy_desc_desc ( Srecord, crypt_buffer ) ; Check_Status ( IO_Status ) ; END ; END ; Edt_File_IO := IO_Status ; END; EDT$K_PUT: BEGIN IF Stream_pointer[Stream] = 0 THEN Edt_File_IO := EDT$FILEIO ( Code , Stream , Srecord , Rhb ) ELSE BEGIN { The encrypt algorighm requirements for the DESCBC format include that the input string be a multiple of 8 bytes long. To meet this requirement, we copy the text string including a word containing its original length to a new buffer and pass to the encrypt algorithm this buffer with a rounded up byte count. Some junk from the buffer will be encrypted along with the text and length word but this is of no consequence. The output from this encryption operation is written to the disk file. Upon decryption, we obtain the original text count, the text itself, and the pad junk (if any) back into the decrypt buffer. The original text may then be copied out. Load the crypt_buffer with cleartext string which, since crypt_buffer is a varying string, will be preceeded by the true string count. } string_copy_desc_str ( crypt_buffer, srecord ) ; { Build a descriptor referencing the crypt_buffer to pass to encrypt facility. Length is cleartext length plus the length word preceding the string rounded up to the next highest 8 bytes. Since the crypt_buffer was declared at max_linelength + 8, the padding characters are present (as junk). } crypt_buffer_desc.string_length := srecord.string_length + 2 + 8 ; crypt_buffer_desc.string_length := (crypt_buffer_desc.string_length ::BOOLEAN AND NOT (7)::BOOLEAN)::String_len ; crypt_buffer_desc.string_type := DSC$K_DTYPE_T ; crypt_buffer_desc.string_class := DSC$K_CLASS_S ; crypt_buffer_desc.string_address := ADDRESS(crypt_buffer) ; { And now encrypt the padded record into a separate string Since 'bufer_descriptor' is a dynamic string descriptor, ENCRYPT$ENCRYPT will force its allocated length to be equal to that of the input string. } IO_Status := $ENCRYPT ( Stream_pointer[Stream] , crypt_buffer_desc , buffer_descriptor ) ; Check_Status ( IO_Status ) ; { Write the encrypted string to the appropriate file } Edt_File_IO := EDT$FILEIO ( Code , Stream , buffer_descriptor, Rhb ) ; END ; END; EDT$K_CLOSE_DEL , EDT$K_CLOSE: BEGIN Edt_File_IO := EDT$FILEIO ( Code , Stream , Srecord , Rhb ) ; IF Stream_pointer[Stream] <> 0 THEN ENCRYPT$FINI ( Stream_pointer[Stream] ) ; END; END; { Make sure that the virtual memory allocated dynamically to the buffer_descriptor (if any) is returned. } Release_descriptor ( buffer_descriptor ) ; END; { Mainline. Setup parameters for call to EDT. Call to obtain DCL command elements. Call EDT. Exit. } BEGIN { Initialize state of EDT options mask flags } edt_options_mask.recover := FALSE ; edt_options_mask.command := FALSE ; edt_options_mask.nojournal := FALSE ; edt_options_mask.nooutput := FALSE ; edt_options_mask.nocommand := FALSE ; edt_options_mask.nocreate := FALSE ; { Obtain the command parameters and qualifiers from DCL } Command_Parse ; { Now call the sharable EDT section passing filename values and the addresses of functions to perform general file I/O, work file I/O, and XLATE function. The later two are defined within the EDT image itself and not intercepted here. } EDT$EDIT ( input_filename , output_filename , command_filename , journal_filename , edt_options_mask , Edt_File_IO , EDT$WORKIO , EDT$XLATE ) ; END.