10
0
Fork 0
softsim/src/simos_server.rb

308 lines
8.2 KiB
Ruby

#!/usr/bin/env ruby
# encoding: UTF-8
=begin
This file is part of softSIM.
softSIM 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 3 of the License, or
(at your option) any later version.
softSIM 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 sofSIM. If not, see <http://www.gnu.org/licenses/>.
Copyright (C) 2011 Kevin "tsaitgaist" Redon kevredon@mail.tsaitgaist.info
=end
require_relative 'sap/server'
require_relative 'lib/apdu'
require 'socket'
require 'xml'
# SAP server using a SIM backup file
class SIMServer < Server
def initialize(io,path="sim.xml")
super(io)
@xml_path = path
end
#====================
#== main functions ==
#====================
# read file
def connect
begin
xml = IO.read(@xml_path)
doc = XML::Parser.string(xml)
@card = doc.parse
rescue
log("connect","can't read #{@xml_path}",3)
status = create_message("STATUS_IND",[[0x08,[0x02]]])
send(status)
sleep 1
retry
end
# select MF
select([0x3f,0x00])
# card ready
return true
end
def disconnect
@card.save(@xml_path)
log("disconnect","save SIM in #{@xml_path}",3)
end
# get ATR
def atr
raise "connect to card to get ATR" unless @card
return @card.find_first("/sim")["atr"].hex2arr
end
# send APDU and get response
def apdu(request)
raise "connect to card to send APDU" unless @card
# check the size
return [0x6f,0x00] unless request.length>=5
# I can only handle SIM APDU (class A0)
return [0x6e,0x00] unless request[0]==0xa0
# default
data = []
sw = [0x6f,0x00]
# the instruction
case request[1]
when 0xa4 # SELECT
# remove the last response
@response = nil
# verify the apdu
if request[2,2]!=[0x00,0x00] then
# incorrect parameter P1 or P2
sw = [0x6b,0x00]
elsif request[4]!=0x02 then
# incorrect parameter P3
sw = [0x67,0x02]
else
# is the file ID present ?
if request.length==7 then
# check if the directory can be selected
file_id = request[5,2]
node = select(file_id)
if node then
# file selected, response awaiting
@response = node.find_first("./header").content.hex2arr
sw = [0x9f,@response.length]
log("APDU select","file selected : #{file_id.to_hex_disp}",3)
else
# out of range (invalid address)
sw = [0x94,0x02]
log("APDU select","file not found/accessible : #{file_id.to_hex_disp}",3)
end
else
# out of range (invalid address)
sw = [0x94,0x02]
log("APDU select","file not found/accessible : #{file_id.to_hex_disp}",3)
end
end
when 0xc0 # GET RESPONSE
# verify the apdu
if request[2,2]!=[0x00,0x00] then
# incorrect parameter P1 or P2
sw = [0x6b,0x00]
elsif !@response then
# technical problem with no diagnostic given
sw = [0x6f,0x00]
elsif request[4]!=@response.length then
# incorrect parameter P3
sw = [0x67,@response.length]
else
# return the response
data = @response
sw = [0x90,0x00]
end
when 0xf2 # STATUS
# get current directory
status = @pwd.find_first("./header").content.hex2arr
# verify the apdu
if request[2,2]!=[0x00,0x00] then
# incorrect parameter P1 or P2
sw = [0x6b,0x00]
elsif request[4]!=status.length then
# incorrect parameter P3
sw = [0x67,status.length]
else
# return the status
data = status
sw = [0x90,0x00]
end
when 0xb0 # READ BINARY
# is an ef selected ?
type = file_info
if type[:type]!="EF" then
# no EF selected
sw = [0x94,0x00]
elsif type[:structure]!="transparent" then
# file is inconsitent with the command
sw = [0x94,0x08]
else
body = @selected.find_first("./body").content.hex2arr
offset = (request[2]<<8)+request[3]
length = request[4]
if offset>=body.length or offset+length>body.length then
# out of range (invalid address)
sw = [0x94,0x02]
else
# return the data
data = body[offset,length]
sw = [0x90,0x00]
end
end
when 0xd6 # UPDATE BINARY
# is an transparent ef selected ?
type = file_info
if type[:type]!="EF" then
# no EF selected
sw = [0x94,0x00]
elsif type[:structure]!="transparent" then
# file is inconsitent with the command
sw = [0x94,0x08]
else
body = @selected.find_first("./body").content.hex2arr
offset = (request[2]<<8)+request[3]
length = request[4]
if offset>=body.length or offset+length>body.length then
# out of range (invalid address)
sw = [0x94,0x02]
else
# write the data
body[offset,length] = request[5,length]
sw = [0x90,0x00]
end
end
when 0x88 # RUN GSM ALGORITHM
# verify the apdu
if request[2,2]!=[0x00,0x00] then
# incorrect parameter P1 or P2
sw = [0x6b,0x00]
elsif request[4]!=0x10 then
# incorrect parameter P3
sw = [0x67,0x00]
elsif ![file_info(@pwd)[:id],file_info(@pwd.find_first(".."))[:id]].include? [0x7F,0x20] then
# not under DF_GSM, file is inconsistent with the command
sw = [0x94,0x08]
else
# return the SRES/Kc
# do I have it ?
tuple = @card.find_first("/sim/a38/tuple[@rand='#{request[5,16].to_hex}']")
if tuple then
@response = tuple["sres"].hex2arr+tuple["kc"].hex2arr
sw = [0x9f,@response.length]
else
# memory problem
sw = [0x92,0x40]
end
end
else # unknown instruction byte
sw = [0x6d,0x00]
end
return data+sw
end
#===================
#== SIM functions ==
#===================
# select file using the file ID
# node representing the file is returned
# nil is return if file does not exist or is unaccessible
def select(id)
# find file
if id==[0x3f,0x00] then
# the MF is always selectable
response = @card.find_first("/sim/file[@id='#{id.to_hex}']")
elsif result=@pwd.find_first("./file[@id='#{id.to_hex}']") then
# any file which is an immediate child of the current directory
response = result
elsif file_info(@pwd)[:type]=="DF" and result=@pwd.find_first("../file[@id='#{id.to_hex}']") then
# any DF which is an immediate child of the parent of the current DF
response = result
elsif result=@pwd.find_first("..") and file_info(result)[:id]==id then
# the parent of the current directory
response = result
elsif file_info(@pwd)[:id]==id then
# the current DF
response = @pwd
else
# file not found
response = nil
end
# remember new selected file
@selected = response if response
# get current directory
if ["MF","DF"].include? file_info(@selected)[:type] then
# selected of a DF
@pwd = @selected
else
# selected is an ED, DF is parent
@pwd = @selected.find_first("..")
end
return response
end
# get type of selected file, and structure if EF
def file_info(file=@selected)
to_return = {}
header = @selected.find_first("./header").content.hex2arr
# file id
to_return[:id] = [header[4],header[5]]
# file type
type = case header[6]
when 0
"RFU"
when 1
"MF"
when 2
"DF"
when 4
"EF"
else
"unknown"
end
to_return[:type] = type
# EF struture
if type=="EF" then
# structure
structure = case header[13]
when 0
"transparent"
when 1
"linear fixed"
when 3
"cyclic"
else
"unknown"
end
to_return[:structure] = structure
end
return to_return
end
end