require 'spec_helper'
require 'r509/asn1'

describe R509::ASN1 do
  it "does not error with valid extension on get_extension_payload" do
    #SAN extension
    der = "0L\u0006\u0003U\u001D\u0011\u0001\u0001\xFF\u0004B0@\x82\u000Ewww.test.local\x87\u0004\n\u0001\u0002\u0003\x86\u0015http://www.test.local\x81\u0011myemail@email.com"
    ext = OpenSSL::X509::Extension.new(der)
    payload = R509::ASN1.get_extension_payload(ext)
    payload.should_not be_nil
  end

  context "general_name_parser" do
    it "returns nil if passed nil" do
      general_names = R509::ASN1.general_name_parser(nil)
      general_names.should be_nil
    end
    it "correctly parses dns names" do
      general_names = R509::ASN1.general_name_parser(['domain2.com','domain3.com'])
      general_names.dns_names.should == ["domain2.com", "domain3.com"]
    end

    it "adds SAN IPv4 names" do
      general_names = R509::ASN1.general_name_parser(['1.2.3.4','2.3.4.5'])
      general_names.ip_addresses.should == ["1.2.3.4", "2.3.4.5"]
    end

    it "adds SAN IPv6 names" do
      general_names = R509::ASN1.general_name_parser(['FE80:0:0:0:0:0:0:1','fe80::2',])
      general_names.ip_addresses.should == ["fe80::1", "fe80::2"]
    end

    it "adds SAN URI names" do
      general_names = R509::ASN1.general_name_parser(['http://myuri.com','ftp://whyftp'])
      general_names.uris.should == ['http://myuri.com','ftp://whyftp']
    end

    it "adds SAN rfc822 names" do
      general_names = R509::ASN1.general_name_parser(['email@domain.com','some@other.com'])
      general_names.rfc_822_names.should == ['email@domain.com','some@other.com']
    end

    it "adds directoryNames via R509::Subject objects" do
      s = R509::Subject.new([['CN','what-what']])
      s2 = R509::Subject.new([['C','US'],['L','locality']])
      general_names = R509::ASN1.general_name_parser([s,s2])
      general_names.directory_names.size.should == 2
      general_names.directory_names[0].CN.should == 'what-what'
      general_names.directory_names[0].C.should be_nil
      general_names.directory_names[1].C.should == 'US'
      general_names.directory_names[1].L.should == 'locality'
    end

    it "adds directoryNames via arrays" do
      s = [['CN','what-what']]
      s2 = [['C','US'],['L','locality']]
      general_names = R509::ASN1.general_name_parser([s,s2])
      general_names.directory_names.size.should == 2
      general_names.directory_names[0].CN.should == 'what-what'
      general_names.directory_names[0].C.should be_nil
      general_names.directory_names[1].C.should == 'US'
      general_names.directory_names[1].L.should == 'locality'
    end

    it "adds a mix of SAN name types" do
      general_names = R509::ASN1.general_name_parser(['1.2.3.4','http://langui.sh','email@address.local','domain.internal','2.3.4.5'])
      general_names.ip_addresses.should == ['1.2.3.4','2.3.4.5']
      general_names.dns_names.should == ['domain.internal']
      general_names.uris.should == ['http://langui.sh']
      general_names.rfc_822_names.should == ['email@address.local']
    end

    it "handles empty array" do
      general_names = R509::ASN1.general_name_parser([])
      general_names.names.size.should == 0
    end

  end
end

describe R509::ASN1::GeneralName do
  context "parses types to tags within ::map_type_to_tag" do
    it "handles otherName" do
      R509::ASN1::GeneralName.map_type_to_tag(:otherName).should == 0
      R509::ASN1::GeneralName.map_type_to_tag("otherName").should == 0
    end
    it "handles rfc822Name" do
      R509::ASN1::GeneralName.map_type_to_tag(:rfc822Name).should == 1
      R509::ASN1::GeneralName.map_type_to_tag("rfc822Name").should == 1
      R509::ASN1::GeneralName.map_type_to_tag("email").should == 1
    end
    it "handles dNSName" do
      R509::ASN1::GeneralName.map_type_to_tag(:dNSName).should == 2
      R509::ASN1::GeneralName.map_type_to_tag("dNSName").should == 2
      R509::ASN1::GeneralName.map_type_to_tag("DNS").should == 2
    end
    it "handles x400Address" do
      R509::ASN1::GeneralName.map_type_to_tag(:x400Address).should == 3
      R509::ASN1::GeneralName.map_type_to_tag("x400Address").should == 3
    end
    it "handles directoryName" do
      R509::ASN1::GeneralName.map_type_to_tag(:directoryName).should == 4
      R509::ASN1::GeneralName.map_type_to_tag("directoryName").should == 4
      R509::ASN1::GeneralName.map_type_to_tag("dirName").should == 4
    end
    it "handles ediPartyName" do
      R509::ASN1::GeneralName.map_type_to_tag(:ediPartyName).should == 5
      R509::ASN1::GeneralName.map_type_to_tag("ediPartyName").should == 5
    end
    it "handles uniformResourceIdentifier" do
      R509::ASN1::GeneralName.map_type_to_tag(:uniformResourceIdentifier).should == 6
      R509::ASN1::GeneralName.map_type_to_tag("uniformResourceIdentifier").should == 6
      R509::ASN1::GeneralName.map_type_to_tag("URI").should == 6
    end
    it "handles iPAddress" do
      R509::ASN1::GeneralName.map_type_to_tag(:iPAddress).should == 7
      R509::ASN1::GeneralName.map_type_to_tag("iPAddress").should == 7
      R509::ASN1::GeneralName.map_type_to_tag("IP").should == 7
    end
    it "handles registeredID" do
      R509::ASN1::GeneralName.map_type_to_tag(:registeredID).should == 8
      R509::ASN1::GeneralName.map_type_to_tag("registeredID").should == 8
    end
  end
  context "::map_tag_to_type" do
    it "handles otherName" do
      R509::ASN1::GeneralName.map_tag_to_type(0).should == :otherName
    end
    it "handles rfc822Name" do
      R509::ASN1::GeneralName.map_tag_to_type(1).should == :rfc822Name
    end
    it "handles dNSName" do
      R509::ASN1::GeneralName.map_tag_to_type(2).should == :dNSName
    end
    it "handles x400Address" do
      R509::ASN1::GeneralName.map_tag_to_type(3).should == :x400Address
    end
    it "handles directoryName" do
      R509::ASN1::GeneralName.map_tag_to_type(4).should == :directoryName
    end
    it "handles ediPartyName" do
      R509::ASN1::GeneralName.map_tag_to_type(5).should == :ediPartyName
    end
    it "handles uniformResourceIdentifier" do
      R509::ASN1::GeneralName.map_tag_to_type(6).should == :uniformResourceIdentifier
    end
    it "handles iPAddress" do
      R509::ASN1::GeneralName.map_tag_to_type(7).should == :iPAddress
    end
    it "handles registeredID" do
      R509::ASN1::GeneralName.map_tag_to_type(8).should == :registeredID
    end
    it "raises error with invalid tag" do
      expect { R509::ASN1::GeneralName.map_tag_to_type(28) }.to raise_error(R509::R509Error,"Invalid tag 28")
    end

  end
  context "::map_tag_to_serial_prefix" do
    it "handles otherName" do
      expect { R509::ASN1::GeneralName.map_tag_to_serial_prefix(0) }.to raise_error(R509::R509Error)
    end
    it "handles rfc822Name" do
      R509::ASN1::GeneralName.map_tag_to_serial_prefix(1).should == "email"
    end
    it "handles dNSName" do
      R509::ASN1::GeneralName.map_tag_to_serial_prefix(2).should == "DNS"
    end
    it "handles x400Address" do
      expect { R509::ASN1::GeneralName.map_tag_to_serial_prefix(3) }.to raise_error(R509::R509Error)
    end
    it "handles directoryName" do
      R509::ASN1::GeneralName.map_tag_to_serial_prefix(4).should == "dirName"
    end
    it "handles ediPartyName" do
      expect { R509::ASN1::GeneralName.map_tag_to_serial_prefix(5) }.to raise_error(R509::R509Error)
    end
    it "handles uniformResourceIdentifier" do
      R509::ASN1::GeneralName.map_tag_to_serial_prefix(6).should == "URI"
    end
    it "handles iPAddress" do
      R509::ASN1::GeneralName.map_tag_to_serial_prefix(7).should == "IP"
    end
    it "handles registeredID" do
      expect { R509::ASN1::GeneralName.map_tag_to_serial_prefix(8) }.to raise_error(R509::R509Error)
    end
  end
  it "handles rfc822Name" do
    der = "\x81\u0011myemail@email.com"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :rfc822Name
    gn.value.should == 'myemail@email.com'
  end
  it "handles dNSName" do
    der = "\x82\u000Ewww.test.local"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :dNSName
    gn.value.should == 'www.test.local'
  end
  it "handles uniformResourceIdentifier" do
    der = "\x86\u001Fhttp://www.test.local/subca.crl"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :uniformResourceIdentifier
    gn.value.should == "http://www.test.local/subca.crl"
  end
  it "handles iPAddress v4" do
    der = "\x87\u0004\n\u0001\u0002\u0003"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :iPAddress
    gn.value.should == '10.1.2.3'
  end
  it "handles iPAddress v6" do
    der = "\x87\x10\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :iPAddress
    gn.value.should == 'ff::'
  end
  it "handles iPAddress v4 with netmask" do
    der = "\x87\b\n\x01\x02\x03\xFF\xFF\xFF\xFF"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :iPAddress
    gn.value.should == '10.1.2.3/255.255.255.255'
  end
  it "handles iPAddress v6 with netmask" do
    der = "\x87 \x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00\xFF\x00\xFF\x00\xFF\x00\xFF\x00\xFF\x00\xFF\x00\xFF"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :iPAddress
    gn.value.should == 'ff::/ff:ff:ff:ff:ff:ff:ff:ff'
  end
  it "handles directoryName" do
    der = "\xA4`0^1\v0\t\u0006\u0003U\u0004\u0006\u0013\u0002US1\u00110\u000F\u0006\u0003U\u0004\b\f\bIllinois1\u00100\u000E\u0006\u0003U\u0004\a\f\aChicago1\u00180\u0016\u0006\u0003U\u0004\n\f\u000FRuby CA Project1\u00100\u000E\u0006\u0003U\u0004\u0003\f\aTest CA"
    asn = OpenSSL::ASN1.decode der
    gn = R509::ASN1::GeneralName.new(asn)
    gn.type.should == :directoryName
    gn.value.to_s.should == '/C=US/ST=Illinois/L=Chicago/O=Ruby CA Project/CN=Test CA'
  end
  it "errors on unimplemented type" do
    # otherName type
    der = "\xA0\u0014\u0006\u0003*\u0003\u0004\xA0\r\u0016\vHello World"
    asn = OpenSSL::ASN1.decode der
    expect { R509::ASN1::GeneralName.new(asn) }.to raise_error(R509::R509Error, "Unimplemented GeneralName tag: 0. At this time R509 does not support GeneralName types other than rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, and directoryName")
  end
end

describe R509::ASN1::GeneralNames do
  it "adds items of allowed type to the object" do
    asn = OpenSSL::ASN1.decode "\x82\u000Ewww.test.local"
    asn2 = OpenSSL::ASN1.decode "\x81\u0011myemail@email.com"
    asn3 = OpenSSL::ASN1.decode "\x82\u000Ewww.text.local"
    gns = R509::ASN1::GeneralNames.new
    gns.add_item(asn)
    gns.add_item(asn2)
    gns.add_item(asn3)
    gns.dns_names.should == ["www.test.local","www.text.local"]
    gns.rfc_822_names.should == ["myemail@email.com"]
  end
  it "errors on unimplemented type" do
    # otherName type
    gns = R509::ASN1::GeneralNames.new
    der = "\xA0\u0014\u0006\u0003*\u0003\u0004\xA0\r\u0016\vHello World"
    asn = OpenSSL::ASN1.decode der
    expect { gns.add_item(asn) }.to raise_error(R509::R509Error, "Unimplemented GeneralName tag: 0. At this time R509 does not support GeneralName types other than rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, and directoryName")
  end
  it "preserves order" do
    asn = OpenSSL::ASN1.decode "\x82\u000Ewww.test.local"
    asn2 = OpenSSL::ASN1.decode "\x81\u0011myemail@email.com"
    asn3 = OpenSSL::ASN1.decode "\x82\u000Ewww.text.local"
    gns = R509::ASN1::GeneralNames.new
    gns.add_item(asn)
    gns.add_item(asn2)
    gns.add_item(asn3)
    gns.names.count.should == 3
    gns.names[0].type.should == :dNSName
    gns.names[0].value.should == "www.test.local"
    gns.names[1].type.should == :rfc822Name
    gns.names[1].value.should == "myemail@email.com"
    gns.names[2].type.should == :dNSName
    gns.names[2].value.should == "www.text.local"
  end

  it "allows #uniq-ing of #names" do
    gns = R509::ASN1::GeneralNames.new
    gns.create_item(:tag => 1, :value => "test")
    gns.create_item(:tag => 1, :value => "test")
    gns.names.count.should == 2
    gns.names.uniq.count.should == 1
  end

  it "errors with invalid params to #create_item" do
    gns = R509::ASN1::GeneralNames.new
    expect { gns.create_item({}) }.to raise_error(ArgumentError,'Must be a hash with (:tag or :type) and :value nodes')
  end

  it "allows addition of directoryNames with #create_item passing existing subject object" do
    gns = R509::ASN1::GeneralNames.new
    s = R509::Subject.new([['C','US'],['L','locality']])
    gns.directory_names.size.should == 0
    gns.create_item( :tag => 4, :value => s )
    gns.directory_names.size.should == 1
  end
  it "allows addition of directoryNames with #create_item passing array" do
    gns = R509::ASN1::GeneralNames.new
    gns.directory_names.size.should == 0
    gns.create_item( :tag => 4, :value => [['C','US'],['L','locality']] )
    gns.directory_names.size.should == 1
  end
end

describe R509::ASN1::PolicyInformation do
  it "loads data with a policy oid but no qualifiers" do
    data = OpenSSL::ASN1.decode "0\r\u0006\v`\x86H\u0001\xE09\u0001\u0002\u0003\u0004\u0001"
    pi = R509::ASN1::PolicyInformation.new(data)
    pi.policy_identifier.should == '2.16.840.1.12345.1.2.3.4.1'
    pi.policy_qualifiers.should be_nil
  end
  it "loads data with a policy oid and a single qualifier" do
    data = OpenSSL::ASN1.decode "0U\u0006\v`\x86H\u0001\xE09\u0001\u0002\u0003\u0004\u00010F0\"\u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u0001\u0016\u0016http://example.com/cps0 \u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u0001\u0016\u0014http://other.com/cps"
    pi = R509::ASN1::PolicyInformation.new(data)
    pi.policy_identifier.should == '2.16.840.1.12345.1.2.3.4.1'
    pi.policy_qualifiers.cps_uris.empty?.should == false
    pi.policy_qualifiers.user_notices.empty?.should == true
  end
  it "loads data with a policy oid and multiple qualifiers" do
    data = OpenSSL::ASN1.decode "0\x81\x94\u0006\n`\x86H\u0001\x86\x8D\u001F\u0015\x81k0\x81\x850#\u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u0001\u0016\u0017http://example.com/cps20;\u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u00020/0\u0018\u0016\vanother org0\t\u0002\u0001\u0003\u0002\u0001\u0002\u0002\u0001\u0001\u001A\u0013this is a bad thing0!\u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u00020\u0015\u001A\u0013another user notice"
    pi = R509::ASN1::PolicyInformation.new(data)
    pi.policy_identifier.should == '2.16.840.1.99999.21.235'
    pi.policy_qualifiers.cps_uris.empty?.should == false
    pi.policy_qualifiers.user_notices.empty?.should == false
  end
end

describe R509::ASN1::PolicyQualifiers do
  before :each do
    @pq = R509::ASN1::PolicyQualifiers.new
  end

  it "initializes empty cps_uris and user_notices" do
    @pq.should_not be_nil
    @pq.cps_uris.empty?.should == true
    @pq.user_notices.empty?.should == true
  end
  it "parses a cps qualifier and adds it to cps_uris" do
    data = OpenSSL::ASN1.decode "0#\u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u0001\u0016\u0017http://example.com/cps2"
    @pq.parse(data)
    @pq.cps_uris.should == ['http://example.com/cps2']
    @pq.user_notices.should == []
  end
  it "parses a user notice and adds it to user_notices" do
    data = OpenSSL::ASN1.decode "0!\u0006\b+\u0006\u0001\u0005\u0005\a\u0002\u00020\u0015\u001A\u0013another user notice"
    @pq.parse(data)
    @pq.cps_uris.should == []
    @pq.user_notices.count.should == 1
  end
end

describe R509::ASN1::UserNotice do
  it "loads data with both a notice reference and explicit text" do
    data = OpenSSL::ASN1.decode "0\u001F0\u0016\u0016\u0006my org0\f\u0002\u0001\u0001\u0002\u0001\u0002\u0002\u0001\u0003\u0002\u0001\u0004\u001A\u0005thing"
    un = R509::ASN1::UserNotice.new(data)
    un.notice_reference.should_not be_nil
    un.explicit_text.should == 'thing'
  end
  it "loads data with a notice reference" do
    data = OpenSSL::ASN1.decode "0\u00180\u0016\u0016\u0006my org0\f\u0002\u0001\u0001\u0002\u0001\u0002\u0002\u0001\u0003\u0002\u0001\u0004"
    un = R509::ASN1::UserNotice.new(data)
    un.notice_reference.should_not be_nil
    un.explicit_text.should be_nil
  end
  it "loads data with an explicit text" do
    data = OpenSSL::ASN1.decode "0\a\u001A\u0005thing"
    un = R509::ASN1::UserNotice.new(data)
    un.notice_reference.should be_nil
    un.explicit_text.should == 'thing'
  end
end

describe R509::ASN1::NoticeReference do
  it "loads data with an org and no notice numbers" do
    data = OpenSSL::ASN1.decode "0\n\u0016\u0006my org0\u0000"
    nr = R509::ASN1::NoticeReference.new(data)
    nr.organization.should == 'my org'
    nr.notice_numbers.should == []
  end
  it "loads data with an org and 1 notice number" do
    data = OpenSSL::ASN1.decode "0\r\u0016\u0006my org0\u0003\u0002\u0001\u0001"
    nr = R509::ASN1::NoticeReference.new(data)
    nr.organization.should == 'my org'
    nr.notice_numbers.should == [1]
  end
  it "loads data with an org and more than 1 notice number" do
    data = OpenSSL::ASN1.decode "0\u0016\u0016\u0006my org0\f\u0002\u0001\u0001\u0002\u0001\u0002\u0002\u0001\u0003\u0002\u0001\u0004"
    nr = R509::ASN1::NoticeReference.new(data)
    nr.organization.should == 'my org'
    nr.notice_numbers.should == [1,2,3,4]
  end
end