lib/cryptorecord/tlsa.rb
#--
# Copyright (C) 2018 Wolfgang Hotwagner <code@feedyourhead.at>
#
# This file is part of the cryptorecord gem
#
# This cryptorecord gem 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 2
# of the License, or (at your option) any later version.
#
# This cryptorecord gem 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 this cryptorecord gem; if not, write to the
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301 USA
#++
# This module provides the api for cryptorecords
module Cryptorecord
require 'openssl'
# Cryptorecord::Tlsa-class generates
# tlsa-dns-records.
# @!attribute [r] selector
# @return [Integer] the selector
# @!attribute [r] mtype
# @return [Integer] the match-type
# @!attribute [r] usage
# @return [Integer] the usage
# @!attribute [r] cert
# @return [String] the x509 certificate
# @!attribute [r] rectype
# @return [String] "TLSA"
# @!attribute host
# @return [String] the fqdn for the record
# @!attribute proto
# @return [String] the network protocol
# @!attribute port
# @return [String] the network port
class Tlsa
attr_reader :selector, :mtype, :usage, :cert, :rectype
attr_accessor :host, :proto, :port
# constructor for the tlsa-object
#
# @param [Hash] args
# @option args [Integer] mtype the matching type
# @option args [Integer] selector the selector for the tlsa-record
# @option args [String] host host-part for the tlsa-record
# @option args [String] proto the network-protocol for the tlsa-record
# @option args [Integer] port the network-port for the tlsa-record
# @option args [Integer] usage the usage for this record
# @option args [String] cert the certificate as a string
def initialize(args = {})
self.mtype = args.fetch(:mtype, 1)
self.selector = args.fetch(:selector, 0)
@host = args.fetch(:host, 'localhost')
@proto = args.fetch(:proto, 'tcp')
@port = args.fetch(:port, 443)
self.usage = args.fetch(:usage, 3)
self.cert = args.fetch(:cert, nil)
@rectype = 'TLSA'
end
# This setter initializes the selector
#
# @param [Integer] val Selector for the association.
# 0 = Full Cert, 1 = SubjectPublicKeyInfo
def selector=(val)
if val.to_i < 0 || val.to_i > 1
raise ArgumentError, 'Invalid selector. Has to be 0 or 1'
end
@selector = val
end
# This setter initializes the mtype
#
# @param [Integer] val The Matching Type of the association.
# 0 = Exact Match, 1 = SHA-256, 2 = SHA-512
def mtype=(val)
if val.to_i < 0 || val.to_i > 2
raise ArgumentError, 'Invalid match type.'\
'Has to be 0,1 or 2'
end
@mtype = val
end
# This setter initializes the usage
#
# @param [Integer] val Usage for the association.
# 0 = PKIX-CA, 1 = PKIX-EE, 2 = DANE-TA, 3 = DANE-EE
# @raise Cryptorecord::ArgumentError
def usage=(val)
if val.to_i < 0 || val.to_i > 3
raise ArgumentError, 'Invalid usage. Has to be 0,1,2 or 3'
end
@usage = val
end
# this setter initializes the certificate
#
# @param [OpenSSL::X509::Certificate] val the x509 certificate
# @raise Cryptorecord::ArgumentError
def cert=(val)
unless val.is_a?(OpenSSL::X509::Certificate) || val.nil?
raise ArgumentError, 'cert has to be a OpenSSL::X509::Certificate'
end
@cert = val
end
# This function reads in the certificate from file
#
# @param [String] file path to certificate-file
def read_file(file)
data = File.read(file)
self.cert = OpenSSL::X509::Certificate.new(data)
end
# this function creates a hash-string defined by mtype and selector
# @return depending on mtype and selector a proper hash will be returned
# @raise Cryptorecord::MatchTypeError
def fingerprint
raise Cryptorecord::MatchTypeError, 'No certificate defined' if @cert.nil?
case @mtype.to_i
when 0
return bin_to_hex(msg)
when 1
return OpenSSL::Digest::SHA256.new(msg).to_s
when 2
return OpenSSL::Digest::SHA512.new(msg).to_s
end
end
# This method returns the left-hand name of a dns-record
# @return [String] left-hand name of a dns-record
def left
"_#{@port}._#{@proto}.#{@host}."
end
# This method returns the right-hand content of a dns-record
# @return [String] right-hand content of a dns-record
def right
"#{@usage} #{@selector} #{@mtype} #{fingerprint}"
end
# This method concats the tlsa-record
#
# @return [String] tlsa dns-record as defined in rfc6698
def to_s
"#{left} IN #{@rectype} #{right}"
end
private
# This function selects the msg to hash using the selector
#
# @return if selector = 0 it returns cert.to_der,
# if selector = 1 it returns cert.public_key.to_der
def msg
case @selector.to_i
when 0
@cert.to_der
when 1
@cert.public_key.to_der
end
end
# This helper-function converts binary data into hex
#
# @param [String] str Binary-string
# @return hex-string
def bin_to_hex(str)
str.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
end
end
end