require 'pkernel' require_relative "io_utils" require_relative "identity" require_relative "bc_helpers" #require_relative '../pkernel_jce' module PkernelJce # RFC3161 is timestamping backed by PKI module Rfc3161 module Response # generate rfc3161 timestamp token def generate(opts = { }) bin = opts[:bin] file = opts[:file] if not (file.nil? or file.empty?) breq = IoUtils.file_to_memory_byte_array(file) elsif not bin.nil? breq = IoUtils.ensure_java_bytes(bin) else raise PkernelJce::Error, "No request file or memory is given to generate timestamp response" end id = opts[:identity] if id.nil? raise PkernelJce::Error, "Identity is not given to generate timestamping token" end req = org.bouncycastle.tsp.TimeStampRequest.new(breq) #p req.messageImprintAlgOID begin dgstEng = BcHelpers.find_digest_calculator(req.messageImprintAlgOID) PkernelJce::GConf.instance.glog.debug "Timestamp request using hash '#{dgstEng}'" rescue PkernelJce::Error => ex opts[:status] = Pkernel::Rfc3161::RESP_REJECTION opts[:reason] = Pkernel::Rfc3161::REASON_BAD_ALG opts[:reasonMsg] = "Digest not supported" end chain = id.chain list = java.util.ArrayList.new chain.each do |c| list.add(c) end store = org.bouncycastle.cert.jcajce.JcaCertStore.new(list) # this one seems useless since the actual algo shall be taken from request anyway... signHashAlgo = opts[:signHash] || "SHA256" policyId = opts[:policy_id] || "1.2.3.4.1" signInfo = org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder.new.setProvider(id.provider).build(PkernelJce::KeyPair.derive_signing_algo(id.privKey,signHashAlgo), id.privKey, id.certificate) # policy 1.2.3.4.1 = tsa_policy1 gen = org.bouncycastle.tsp.TimeStampTokenGenerator.new(signInfo, BcHelpers.find_digest_calculator(signHashAlgo), org.bouncycastle.asn1.ASN1ObjectIdentifier.new(policyId)) # policy 1.2 = ISO Member body #gen = org.bouncycastle.tsp.TimeStampTokenGenerator.new(signInfo, SHA256DigestCalculator.new, org.bouncycastle.asn1.ASN1ObjectIdentifier.new("1.2")) gen.addCertificates(store) if req.cert_req? PkernelJce::GConf.instance.glog.debug "Client requested to include certificate for timestamping" # this option requires the request to reqCert set first if not error shall be thrown at the verification end gen.setTSA(org.bouncycastle.asn1.x509.GeneralName.new(PkernelJce::Certificate.ensure_bc_cert(id.certificate).subject_to_x500)) end status = opts[:status] || Pkernel::Rfc3161::RESP_GRANTED reason = opts[:reason] || Pkernel::Rfc3161::REASON_SYS_FAILURE reasonMsg = opts[:reasonMsg] || "" respGen = org.bouncycastle.tsp.TimeStampResponseGenerator.new(gen, org.bouncycastle.tsp.TSPAlgorithms::ALLOWED) if status == Pkernel::Rfc3161::RESP_GRANTED or status == Pkernel::Rfc3161::RESP_GRANTED_WITH_MODS # etsi want min 1 sec...:) accuracy = opts[:accuracy] || { seconds: 1 } if not accuracy[:seconds].nil? PkernelJce::GConf.instance.glog.debug "Timestamping setting accuracy #{accuracy[:seconds]} seconds" gen.setAccuracySeconds(accuracy[:seconds].to_i) end if not accuracy[:milis].nil? PkernelJce::GConf.instance.glog.debug "Timestamping setting accuracy #{accuracy[:milis]} mili seconds" gen.setAccuracyMillis(accuracy[:milis].to_i) end if not accuracy[:micros].nil? PkernelJce::GConf.instance.glog.debug "Timestamping setting accuracy #{accuracy[:micros]} micro seconds" gen.setAccuracyMicros(accuracy[:micros].to_i) end tsResp = respGen.generateGrantedResponse(req, java.math.BigInteger.new(SecureRandom.uuid.gsub("-",""),16), java.util.Date.new) else tsResp = respGen.generateFailResponse(status, reason, reasonMsg) end #resp = org.bouncycastle.tsp.TimeStampResponse.new(tsResp.getEncoded()) #resp tsResp end # # end generate() # def parse(opts = {}) file = opts[:file] bin = opts[:bin] if not (file.nil? or file.empty?) bresp = IoUtils.file_to_memory_byte_array(file) elsif not bin.nil? bresp = IoUtils.ensure_java_bytes(bin) else raise PkernelJce::Error, "No file or memory is given to parse timestamp response" end result = { } verifyResp = opts[:verifyResp] || true resp = org.bouncycastle.tsp.TimeStampResponse.new(bresp) result[:status] = resp.status if resp.status == Pkernel::Rfc3161::RESP_GRANTED || resp.status == Pkernel::Rfc3161::RESP_GRANTED_WITH_MODS token = resp.getTimeStampToken if not token.nil? info = token.getTimeStampInfo else PkernelJce::GConf.instance.glog.warn "Timestamp does not contain token!" end if not token.nil? and verifyResp tsaCert = opts[:tsaCert] signingCert = nil token.getCertificates.to_a.each do |c| if c.subject_to_x500.equals(info.getTsa.name) signingCert = c break end end if not tsaCert.nil? # check if tsaCert and signingCert is same if not tsaCert.equals(signingCert) raise PkernelJce::Error, "Given TSA cert and signing cert obtained from the timestamp token is not the same" end end prov = PkernelJce::Provider.add_default begin token.validate(org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder.new.setProvider(prov).build(signingCert)) result[:signature_status] = :verified PkernelJce::GConf.instance.glog.debug "Timestamp verified against signing cert [#{signingCert.subject}]" result[:signer] = { } result[:signer][:cert] = signingCert result[:signer][:chain] = token.getCertificates.to_a rescue Exception => ex result[:signature_status] = :failed result[:signature_status_details] = ex.message end end # end verifyResp req = opts[:request] if not token.nil? and not req.nil? source = opts[:data] if not source.nil? sfile = source[:file] sbin = source[:bin] end if not (sfile.nil? or sfile.empty?) or not sbin.nil? dgst = PkernelJce::BcHelpers.find_digest_calculator(req.getMessageImprintAlgOID) if not dgst.nil? if not (sfile.nil? or sfile.empty?) PkernelJce::GConf.instance.glog.debug "Calculating hash for source '#{sfile}'" b = Java::byte[10240].new fis = java.io.FileInputStream.new(sfile) while((read = fis.read(b,0,b.length)) != -1) dgst.getOutputStream.write(b,0,read) end dgst.getOutputStream.close elsif not sbin.nil? PkernelJce::GConf.instance.glog.debug "Calculating hash for source from memory" dgst.getOutputStream.write(sbin.to_java_bytes) dgst.getOutputStream.close end if not java.util.Arrays.equals(dgst.digest, req.getMessageImprintDigest) raise PkernelJce::Error, "Source digest is different from request! Source file is not the original data sent for timestamp" else PkernelJce::GConf.instance.glog.debug "Source file hash matched request digest." end else PkernelJce::GConf.instance.glog.warn "Failed to initialize the hashing algo for source verification. Source verification shall be skipped." end end # end not source.nil? begin resp.validate(req) result[:request_validation] = :verified result[:digest] = info.getMessageImprintDigest result[:digest_algo] = info.getMessageImprintAlgOID PkernelJce::GConf.instance.glog.debug "Timestamp response verified against timestamp request. Checked [Nonce, Status, Digest, Hash Engine, Policy, Signing Certificates]" rescue Exception => ex result[:request_validation] = :failed result[:request_validation_details] = ex.message end end if not info.nil? acc = info.getAccuracy result[:accuracy] = {} result[:accuracy][:seconds] = acc.getSeconds.nil? ? 0 : acc.getSeconds.value result[:accuracy][:milis] = acc.getMillis.nil? ? 0 : acc.getMillis.value result[:accuracy][:micros] = acc.getMicros.nil? ? 0 : acc.getMicros.value time = info.getGenTime result[:time] = time nonce = info.getNonce result[:nonce] = nonce.to_s(16) serial = info.getSerialNumber result[:serial] = serial.to_s(16) policy = info.getPolicy result[:policy] = policy tsa = info.getTsa result[:tsa_name] = tsa.name.to_s ordered = info.isOrdered result[:ordered] = ordered end else # status is NOT granted or granted with mods! result[:reason] = resp.getFailInfo result[:reasonMsg] = resp.getStatusString end # if status is granted or granted with mods result end # end parse() # end # end module response module Request def generate(opts = { }) targetFile = opts[:tbts_file] targetBin = opts[:tbts_bin] reqGen = org.bouncycastle.tsp.TimeStampRequestGenerator.new reqCert = opts[:inc_certs] || true if reqCert reqGen.setCertReq(true) end reqPolicy = opts[:req_policy] if not (reqPolicy.nil? or reqPolicy.empty?) reqGen.setReqPolicy(org.bouncycastle.asn1.x509.AlgorithmIdentifier.new(reqPolicy)) end nonce = Java::byte[16].new java.util.Random.new.nextBytes(nonce) hashAlgo = opts[:hashAlgo] || "SHA256" if not hashAlgo.nil? hashAlgo = hashAlgo.upcase case hashAlgo when "SHA224","SHA256","SHA384","SHA512","RIPEMD128","RIPEMD160","RIPEMD256" dgstCal = PkernelJce::BcHelpers.find_digest_calculator(hashAlgo) if not (targetFile.nil? or targetFile.empty?) b = Java::byte[10240].new fis = java.io.FileInputStream.new(targetFile) while((cont = fis.read(b,0,b.length)) != -1) dgstCal.getOutputStream.write(b,0,cont) end dgstCal.getOutputStream.close elsif not targetBin.nil? bin = PkernelJce::IoUtils.ensure_java_bytes(targetBin) dgstCal.getOutputStream.write(bin) dgstCal.getOutputStream.close else raise PkernelJce::Error, "No to-be-timestamped file or memory input given to timestamp" end else raise PkernelJce::Error, "Unsupported hash algo '#{hashAlgo}'" end end digest = dgstCal.digest tspHash = PkernelJce::Rfc3161::Request.find_tsp_algo(dgstCal) reqGen.generate(tspHash, digest.to_s.to_java_bytes, java.math.BigInteger.new(nonce).abs) end # end generate() # def parse(opts = { }) bin = opts[:bin] file = opts[:file] if not (file.nil? or file.empty?) breq = IoUtils.file_to_memory_byte_array(file) elsif not bin.nil? breq = IoUtils.ensure_java_bytes(bin) else raise PkernelJce::Error, "No request file or memory is given to parse" end result = { } req = org.bouncycastle.tsp.TimeStampRequest.new(breq) result[:cert_req] = req.cert_req? result[:digest_algo] = req.getMessageImprintAlgOID result[:digest] = req.getMessageImprintDigest result[:nonce] = req.getNonce.to_s(16) result[:policy] = req.req_policy result end def Request.find_tsp_algo(dgstCal) if dgstCal.nil? raise PkernelJce::Error, "Digest calculator not given to derive tsp algo" end tspHash = nil org.bouncycastle.tsp.TSPAlgorithms.constants.each do |al| tspAlgo = org.bouncycastle.tsp.TSPAlgorithms.send(al) next if not tspAlgo.java_kind_of?(Java::OrgBouncycastleAsn1::ASN1ObjectIdentifier) if tspAlgo.toASN1Object.equals(dgstCal.algorithm_identifier.algorithm) tspHash = tspAlgo break end end tspHash end # end find_tsp_algo end # end module Request end # # end Rfc3161 # class Rfc3161RequestEngine extend PkernelJce::Rfc3161::Request end class Rfc3161ResponseEngine extend PkernelJce::Rfc3161::Response end # end class Rfc3161Engine end