#!/usr/bin/env perl # # This is a proof-of-concept script to show that the client and server wrappers # can be created by a script. It is not hooked into the build, so is run # manually and the output files are what are to be reviewed. In due course # this will be replaced by a Python script based on the # code_wrapper.psa_wrapper module. # # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later # use strict; use Data::Dumper; use File::Basename; use JSON qw(encode_json); my $debug = 0; # Globals (sorry!) my $output_dir = dirname($0); my %functions = get_functions(); my @functions = sort keys %functions; # We don't want these functions (e.g. because they are not implemented, etc) my @skip_functions = ( 'mbedtls_psa_crypto_free', # redefined rather than wrapped 'mbedtls_psa_external_get_random', # not in the default config, uses unsupported type 'mbedtls_psa_get_stats', # uses unsupported type 'mbedtls_psa_platform_get_builtin_key', # not in the default config, uses unsupported type 'psa_get_key_slot_number', # not in the default config, uses unsupported type 'psa_key_derivation_verify_bytes', # not implemented yet 'psa_key_derivation_verify_key', # not implemented yet ); my $skip_functions_re = '\A(' . join('|', @skip_functions). ')\Z'; @functions = grep(!/$skip_functions_re |_pake_ # Skip everything PAKE |_init\Z # constructors /x, @functions); # Restore psa_crypto_init() and put it first. unshift @functions, 'psa_crypto_init'; # get_functions(), called above, returns a data structure for each function # that we need to create client and server stubs for. The functions are # listed from PSA header files. # # In this script, the data for psa_crypto_init() looks like: # # "psa_crypto_init": { # "return": { # Info on return type # "type": "psa_status_t", # Return type # "name": "status", # Name to be used for this in C code # "default": "PSA_ERROR_CORRUPTION_DETECTED" # Default value # }, # "args": [], # void function, so args empty # } # # The data for psa_hash_compute() looks like: # # "psa_hash_compute": { # "return": { # Information on return type # "type": "psa_status_t", # "name": "status", # "default": "PSA_ERROR_CORRUPTION_DETECTED" # }, # "args": [{ # "type": "psa_algorithm_t", # Type of first argument # "ctypename": "psa_algorithm_t ", # C type with trailing spaces # # (so that e.g. `char *` looks ok) # "name": "alg", # "is_output": 0 # }, { # "type": "const buffer", # Specially created # "ctypename": "", # (so no C type) # "name": "input, input_length", # A pair of arguments # "is_output": 0 # const, so not an output argument # }, { # "type": "buffer", # Specially created # "ctypename": "", # "name": "hash, hash_size", # "is_output": 1 # Not const, so output argument # }, { # "type": "size_t", # size_t *hash_length # "ctypename": "size_t ", # "name": "*hash_length", # * comes into the name # "is_output": 1 # } # ], # }, # # It's possible that a production version might not need both type and ctypename; # that was done for convenience and future-proofing during development. write_function_codes("$output_dir/psa_functions_codes.h"); write_client_calls("$output_dir/psa_sim_crypto_client.c"); write_server_implementations("$output_dir/psa_sim_crypto_server.c"); sub write_function_codes { my ($file) = @_; open(my $fh, ">", $file) || die("$0: $file: $!\n"); # NOTE: psa_crypto_init() is written manually print $fh <", $file) || die("$0: $file: $!\n"); print $fh client_calls_header(); for my $function (@functions) { # psa_crypto_init() is hand written to establish connection to server if ($function ne "psa_crypto_init") { my $f = $functions{$function}; output_client($fh, $f, $function); } } close($fh); } sub write_server_implementations { my ($file) = @_; open(my $fh, ">", $file) || die("$0: $file: $!\n"); print $fh server_implementations_header(); print $fh debug_functions() if $debug; for my $function (@functions) { my $f = $functions{$function}; output_server_wrapper($fh, $f, $function); } # Now output a switch statement that calls each of the wrappers print $fh < msg.out_size[1]) { fprintf(stderr, "unable to write %zu bytes into buffer of %zu bytes\\n", out_params_len, msg.out_size[1]); exit(1); } /* Write the exact amount of data we're returning */ psa_write(msg.handle, 0, &out_params_len, sizeof(out_params_len)); /* And write the data itself */ if (out_params_len) { psa_write(msg.handle, 1, out_params, out_params_len); } free(out_params); return ok ? PSA_SUCCESS : PSA_ERROR_GENERIC_ERROR; } EOF # Finally, add psa_crypto_close() print $fh < #include #include #include "psa_functions_codes.h" #include "psa_sim_serialise.h" #include "service.h" #if !defined(MBEDTLS_PSA_CRYPTO_C) #error "Error: MBEDTLS_PSA_CRYPTO_C must be enabled on server build" #endif #if defined(MBEDTLS_TEST_HOOKS) void (*mbedtls_test_hook_error_add)(int, int, const char *, int); #endif EOF } sub client_calls_header { my $code = <<'EOF'; /* THIS FILE WAS AUTO-GENERATED BY psa_sim_generate.pl. DO NOT EDIT!! */ /* client calls */ /* * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ #include #include /* Includes from psasim */ #include #include #include "psa_manifest/sid.h" #include "psa_functions_codes.h" #include "psa_sim_serialise.h" /* Includes from mbedtls */ #include "mbedtls/version.h" #include "psa/crypto.h" #define CLIENT_PRINT(fmt, ...) \ INFO("Client: " fmt, ##__VA_ARGS__) static psa_handle_t handle = -1; #if defined(MBEDTLS_PSA_CRYPTO_C) #error "Error: MBEDTLS_PSA_CRYPTO_C must be disabled on client build" #endif EOF $code .= debug_functions() if $debug; $code .= <<'EOF'; int psa_crypto_call(int function, uint8_t *in_params, size_t in_params_len, uint8_t **out_params, size_t *out_params_len) { // psa_outvec outvecs[1]; if (handle < 0) { fprintf(stderr, "NOT CONNECTED\n"); exit(1); } psa_invec invec; invec.base = in_params; invec.len = in_params_len; size_t max_receive = 24576; uint8_t *receive = malloc(max_receive); if (receive == NULL) { fprintf(stderr, "FAILED to allocate %u bytes\n", (unsigned) max_receive); exit(1); } size_t actual_received = 0; psa_outvec outvecs[2]; outvecs[0].base = &actual_received; outvecs[0].len = sizeof(actual_received); outvecs[1].base = receive; outvecs[1].len = max_receive; psa_status_t status = psa_call(handle, function, &invec, 1, outvecs, 2); if (status != PSA_SUCCESS) { free(receive); return 0; } *out_params = receive; *out_params_len = actual_received; return 1; // success } psa_status_t psa_crypto_init(void) { char mbedtls_version[18]; uint8_t *result = NULL; size_t result_length; psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; mbedtls_version_get_string_full(mbedtls_version); CLIENT_PRINT("%s", mbedtls_version); CLIENT_PRINT("My PID: %d", getpid()); CLIENT_PRINT("PSA version: %u", psa_version(PSA_SID_CRYPTO_SID)); handle = psa_connect(PSA_SID_CRYPTO_SID, 1); if (handle < 0) { CLIENT_PRINT("Couldn't connect %d", handle); return PSA_ERROR_COMMUNICATION_FAILURE; } int ok = psa_crypto_call(PSA_CRYPTO_INIT, NULL, 0, &result, &result_length); CLIENT_PRINT("PSA_CRYPTO_INIT returned: %d", ok); if (!ok) { goto fail; } uint8_t *rpos = result; size_t rremain = result_length; ok = psasim_deserialise_begin(&rpos, &rremain); if (!ok) { goto fail; } ok = psasim_deserialise_psa_status_t(&rpos, &rremain, &status); if (!ok) { goto fail; } fail: free(result); return status; } void mbedtls_psa_crypto_free(void) { /* Do not try to close a connection that was never started.*/ if (handle == -1) { return; } CLIENT_PRINT("Closing handle"); psa_close(handle); handle = -1; } EOF } sub debug_functions { return <> 4); p[1] = hex_digit(b & 0x0F); return 2; } int hex_uint16(char *p, uint16_t b) { hex_byte(p, b >> 8); hex_byte(p + 2, b & 0xFF); return 4; } char human_char(uint8_t c) { return (c >= ' ' && c <= '~') ? (char)c : '.'; } void dump_buffer(const uint8_t *buffer, size_t len) { char line[80]; const uint8_t *p = buffer; size_t max = (len > 0xFFFF) ? 0xFFFF : len; for (size_t i = 0; i < max; i += 16) { char *q = line; q += hex_uint16(q, (uint16_t)i); *q++ = ' '; *q++ = ' '; size_t ll = (i + 16 > max) ? (max % 16) : 16; size_t j; for (j = 0; j < ll; j++) { q += hex_byte(q, p[i + j]); *q++ = ' '; } while (j++ < 16) { *q++ = ' '; *q++ = ' '; *q++ = ' '; } *q++ = ' '; for (j = 0; j < ll; j++) { *q++ = human_char(p[i + j]); } *q = '\\0'; printf("%s\\n", line); } } void hex_dump(uint8_t *p, size_t n) { for (size_t i = 0; i < n; i++) { printf("0x%02X ", p[i]); } printf("\\n"); } EOF } sub output_server_wrapper { my ($fh, $f, $name) = @_; my $ret_type = $f->{return}->{type}; my $ret_name = $f->{return}->{name}; my $ret_default = $f->{return}->{default}; my @buffers = (); # We need to free() these on exit print $fh <{args}; for my $i (0 .. $#$args) { my $arg = $args->[$i]; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; $argtype =~ s/^const //; if ($argtype =~ /^(const )?buffer$/) { my ($n1, $n2) = split(/,\s*/, $argname); print $fh <= 0) { # If we have any args (>= 0) print $fh <= 0) { # If we have any args (>= 0) print $fh <[$i]; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; my $sep = ($i == $#$args) ? ";" : " +"; $argtype =~ s/^const //; if ($argtype =~ /^(const )?buffer$/) { my ($n1, $n2) = split(/,\s*/, $argname); print $fh <{is_output}, @$args); my $sep1 = (($ret_type eq "void") and ($#outputs < 0)) ? ";" : " +"; print $fh <{is_output}; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; my $sep = ($i == $#outputs) ? ";" : " +"; $argtype =~ s/^const //; $argname =~ s/^\*//; # Remove any leading * my $server_specific = ($argtype =~ /^psa_\w+_operation_t/) ? "server_" : ""; print $fh <{is_output}, @$args); for my $i (0 .. $#outputs) { my $arg = $outputs[$i]; die("$i: this should have been filtered out by grep") unless $arg->{is_output}; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; my $sep = ($i == $#outputs) ? ";" : " +"; $argtype =~ s/^const //; if ($argtype eq "buffer") { print $fh <{return}->{type}; my $ret_name = $f->{return}->{name}; my $ret_default = $f->{return}->{default}; print $fh <{args}; for my $i (0 .. $#$args) { my $arg = $args->[$i]; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; my $sep = ($i == $#$args) ? ";" : " +"; $argtype =~ s/^const //; print $fh <[$i]; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; my $sep = ($i == $#$args) ? ";" : " +"; $argtype =~ s/^const //; print $fh <{is_output}, @$args); for my $i (0 .. $#outputs) { my $arg = $outputs[$i]; die("$i: this should have been filtered out by grep") unless $arg->{is_output}; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; my $sep = ($i == $#outputs) ? ";" : " +"; $argtype =~ s/^const //; if ($argtype eq "buffer") { print $fh <{return}->{type}; my $ret_name = $f->{return}->{name}; my $args = $f->{args}; if ($ret_type eq "void") { print $fh "\n $name(\n"; } else { print $fh "\n $ret_name = $name(\n"; } print $fh " );\n" if $#$args < 0; # If no arguments, empty arg list for my $i (0 .. $#$args) { my $arg = $args->[$i]; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $argname = $arg->{name}; if ($argtype =~ /^(const )?buffer$/) { my ($n1, $n2) = split(/,\s*/, $argname); print $fh " $n1, $n2"; } else { $argname =~ s/^\*/\&/; # Replace leading * with & if ($is_server && $argtype =~ /^psa_\w+_operation_t/) { $argname =~ s/^\&//; # Actually, for psa_XXX_operation_t, don't do this on the server side } print $fh " $argname"; } my $sep = ($i == $#$args) ? "\n );" : ","; print $fh "$sep\n"; } } sub output_signature { my ($fh, $f, $name, $what) = @_; my $ret_type = $f->{return}->{type}; my $args = $f->{args}; my $final_sep = ($what eq "declaration") ? "\n);" : "\n )"; print $fh "\n$ret_type $name(\n"; print $fh " void\n )\n" if $#$args < 0; # No arguments for my $i (0 .. $#$args) { my $arg = $args->[$i]; my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" my $ctypename = $arg->{ctypename}; # e.g. "int ", "char *"; empty for buffer my $argname = $arg->{name}; if ($argtype =~ /^(const )?buffer$/) { my $const = length($1) ? "const " : ""; my ($n1, $n2) = split(/,/, $argname); print $fh " ${const}uint8_t *$n1, size_t $n2"; } else { print $fh " $ctypename$argname"; } my $sep = ($i == $#$args) ? $final_sep : ","; print $fh "$sep\n"; } } sub get_functions { my $header_dir = 'tf-psa-crypto/include'; my $src = ""; for my $header_file ('psa/crypto.h', 'psa/crypto_extra.h') { local *HEADER; open HEADER, '<', "$header_dir/$header_file" or die "$header_dir/$header_file: $!"; while (
) { chomp; s/\/\/.*//; s/\s+^//; s/\s+/ /g; $_ .= "\n"; $src .= $_; } close HEADER; } $src =~ s/\/\*.*?\*\///gs; my @src = split(/\n+/, $src); my @rebuild = (); my %funcs = (); for (my $i = 0; $i <= $#src; $i++) { my $line = $src[$i]; if ($line =~ /^(static(?:\s+inline)?\s+)? ((?:(?:enum|struct|union)\s+)?\w+\s*\**\s*)\s+ ((?:mbedtls|psa)_\w*)\(/x) { # begin function declaration #print "have one $line\n"; while ($line !~ /;/) { $line .= $src[$i + 1]; $i++; } if ($line =~ /^static/) { # IGNORE static inline functions: they're local. next; } $line =~ s/\s+/ /g; if ($line =~ /(\w+)\s+\b(\w+)\s*\(\s*(.*\S)\s*\)\s*[;{]/s) { my ($ret_type, $func, $args) = ($1, $2, $3); my $copy = $line; $copy =~ s/{$//; my $f = { "orig" => $copy, }; my @args = split(/\s*,\s*/, $args); my $ret_name = ""; $ret_name = "status" if $ret_type eq "psa_status_t"; $ret_name = "value" if $ret_type eq "uint32_t"; $ret_name = "value" if $ret_type eq "int"; $ret_name = "(void)" if $ret_type eq "void"; die("ret_name for $ret_type?") unless length($ret_name); my $ret_default = ""; $ret_default = "PSA_ERROR_CORRUPTION_DETECTED" if $ret_type eq "psa_status_t"; $ret_default = "0" if $ret_type eq "uint32_t"; $ret_default = "0" if $ret_type eq "int"; $ret_default = "(void)" if $ret_type eq "void"; die("ret_default for $ret_type?") unless length($ret_default); #print "FUNC $func RET_NAME $ret_name RET_TYPE $ret_type ARGS (", join("; ", @args), ")\n"; $f->{return} = { "type" => $ret_type, "default" => $ret_default, "name" => $ret_name, }; $f->{args} = []; # psa_algorithm_t alg; const uint8_t *input; size_t input_length; uint8_t *hash; size_t hash_size; size_t *hash_length for (my $i = 0; $i <= $#args; $i++) { my $arg = $args[$i]; # "type" => "psa_algorithm_t", # "ctypename" => "psa_algorithm_t ", # "name" => "alg", # "is_output" => 0, my ($type, $ctype, $name, $is_output); if ($arg =~ /^(\w+)\s+(\w+)$/) { # e.g. psa_algorithm_t alg ($type, $name) = ($1, $2); $ctype = $type . " "; $is_output = 0; } elsif ($arg =~ /^((const)\s+)?uint8_t\s*\*\s*(\w+)$/) { $type = "buffer"; $is_output = (length($1) == 0) ? 1 : 0; $type = "const buffer" if !$is_output; $ctype = ""; $name = $3; #print("$arg: $name: might be a buffer?\n"); die("$arg: not a buffer 1!\n") if $i == $#args; my $next = $args[$i + 1]; if ($func eq "psa_key_derivation_verify_bytes" && $arg eq "const uint8_t *expected_output" && $next eq "size_t output_length") { $next = "size_t expected_output_length"; # doesn't follow naming convention, so override } die("$arg: not a buffer 2!\n") if $next !~ /^size_t\s+(${name}_\w+)$/; $i++; # We're using the next param here my $nname = $1; $name .= ", " . $nname; } elsif ($arg =~ /^((const)\s+)?(\w+)\s*\*(\w+)$/) { ($type, $name) = ($3, "*" . $4); $ctype = $1 . $type . " "; $is_output = (length($1) == 0) ? 1 : 0; } elsif ($arg eq "void") { # we'll just ignore this one } else { die("ARG HELP $arg\n"); } #print "$arg => <$type><$ctype><$name><$is_output>\n"; if ($arg ne "void") { push(@{$f->{args}}, { "type" => $type, "ctypename" => $ctype, "name" => $name, "is_output" => $is_output, }); } } $funcs{$func} = $f; } else { die("FAILED"); } push(@rebuild, $line); } elsif ($line =~ /^#/i) { # IGNORE directive while ($line =~ /\\$/) { $i++; $line = $src[$i]; } } elsif ($line =~ /^(?:typedef +)?(enum|struct|union)[^;]*$/) { # IGNORE compound type definition while ($line !~ /^\}/) { $i++; $line = $src[$i]; } } elsif ($line =~ /^typedef /i) { # IGNORE type definition } elsif ($line =~ / = .*;$/) { # IGNORE assignment in inline function definition } else { if ($line =~ /psa_/) { print "NOT PARSED: $line\n"; } push(@rebuild, $line); } } #print ::Dumper(\%funcs); #exit; return %funcs; }