Skip to content

Commit

Permalink
Refactor CMS data structures used in pkinit functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
smashery committed Dec 20, 2024
1 parent c6e3df8 commit 49c8c8a
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 161 deletions.
10 changes: 5 additions & 5 deletions lib/msf/core/exploit/remote/kerberos/client/pkinit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,9 @@ def build_pa_pk_as_req(pfx, dh, dh_nonce, request_body, opts)
# @param auth_pack [Rex::Proto::Kerberos::Model::Pkinit::AuthPack] The AuthPack to sign
# @param key [OpenSSL::PKey] The private key to digitally sign the data
# @param dh [OpenSSL::X509::Certificate] The certificate associated with the private key
# @return [Rex::Proto::Kerberos::Model::Pkinit::ContentInfo] The signed AuthPack
# @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed AuthPack
def sign_auth_pack(auth_pack, key, certificate)
signer_info = Rex::Proto::Kerberos::Model::Pkinit::SignerInfo.new(
signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new(
version: 1,
sid: {
issuer: certificate.issuer,
Expand Down Expand Up @@ -268,7 +268,7 @@ def sign_auth_pack(auth_pack, key, certificate)

signer_info[:signature] = signature

signed_data = Rex::Proto::Kerberos::Model::Pkinit::SignedData.new(
signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new(
version: 3,
digest_algorithms: [
{
Expand All @@ -283,9 +283,9 @@ def sign_auth_pack(auth_pack, key, certificate)
signer_infos: [signer_info]
)

Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.new(
Rex::Proto::CryptoAsn1::Cms::ContentInfo.new(
content_type: Rex::Proto::Kerberos::Model::OID::SignedData,
signed_data: signed_data
data: signed_data
)
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/msf/core/exploit/remote/ms_icpr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algor
# @param [OpenSSL::X509::Certificate] cert The public key to use for signing the request.
# @param [OpenSSL::PKey::RSA] key The private key to use for signing the request.
# @param [String] algorithm The digest algorithm to use.
# @return [Rex::Proto::Kerberos::Model::Pkinit::ContentInfo] The signed request content.
# @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed request content.
def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256')
# algorithm needs to be one that OpenSSL supports, but we also need the OID constants defined
digest = OpenSSL::Digest.new(algorithm)
Expand All @@ -309,7 +309,7 @@ def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256')

digest_oid = Rex::Proto::Kerberos::Model::OID.const_get(digest.name)

signer_info = Rex::Proto::Kerberos::Model::Pkinit::SignerInfo.new(
signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new(
version: 1,
sid: {
issuer: cert.issuer,
Expand Down Expand Up @@ -342,7 +342,7 @@ def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256')

signer_info[:signature] = signature

signed_data = Rex::Proto::Kerberos::Model::Pkinit::SignedData.new(
signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new(
version: 3,
digest_algorithms: [
{
Expand All @@ -357,9 +357,9 @@ def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256')
signer_infos: [signer_info]
)

Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.new(
Rex::Proto::CryptoAsn1::Cms::ContentInfo.new(
content_type: Rex::Proto::Kerberos::Model::OID::SignedData,
signed_data: signed_data
data: signed_data
)
end

Expand Down
45 changes: 44 additions & 1 deletion lib/rex/proto/crypto_asn1/cms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,47 @@ class EnvelopedData < RASN1::Model
]
end

class SignerInfo < RASN1::Model
sequence :signer_info,
content: [integer(:version),
model(:sid, IssuerAndSerialNumber),
model(:digest_algorithm, AlgorithmIdentifier),
set_of(:signed_attrs, Attribute, implicit: 0, optional: true),
model(:signature_algorithm, AlgorithmIdentifier),
octet_string(:signature),
]
end

class EncapsulatedContentInfo < RASN1::Model
sequence :encapsulated_content_info,
content: [objectid(:econtent_type),
octet_string(:econtent, explicit: 0, constructed: true, optional: true)
]

def econtent
if self[:econtent_type].value == Rex::Proto::CryptoAsn1::OIDs::OID_DIFFIE_HELLMAN_KEYDATA.value
Rex::Proto::Kerberos::Model::Pkinit::KdcDhKeyInfo.parse(self[:econtent].value)
elsif self[:econtent_type].value == Rex::Proto::Kerberos::Model::OID::PkinitAuthData
Rex::Proto::Kerberos::Model::Pkinit::AuthPack.parse(self[:econtent].value)
end
end
end

class SignedData < RASN1::Model
sequence :signed_data,
explicit: 0, constructed: true,
content: [integer(:version),
set_of(:digest_algorithms, AlgorithmIdentifier),
model(:encap_content_info, EncapsulatedContentInfo),
set_of(:certificates, Certificate, implicit: 0, optional: true),
# CRLs - not implemented
set_of(:signer_infos, SignerInfo)
]
end

class ContentInfo < RASN1::Model
sequence :content_info,
content: [model(:content_type, ContentType),
# In our case, expected to be EnvelopedData
any(:data)
]

Expand All @@ -248,5 +285,11 @@ def enveloped_data
EnvelopedData.parse(self[:data].value)
end
end

def signed_data
if self[:content_type].value == Rex::Proto::CryptoAsn1::OIDs::OID_CMS_SIGNED_DATA.value
SignedData.parse(self[:data].value)
end
end
end
end
3 changes: 3 additions & 0 deletions lib/rex/proto/crypto_asn1/o_i_ds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ class OIDs
OID_PKIX_KP_TIMESTAMP_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.8', name: 'OID_PKIX_KP_TIMESTAMP_SIGNING', label: 'Time Stamping')
OID_ROOT_LIST_SIGNER = ObjectId.new('1.3.6.1.4.1.311.10.3.9', name: 'OID_ROOT_LIST_SIGNER', label: 'Root List Signer')
OID_WHQL_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.5', name: 'OID_WHQL_CRYPTO', label: 'Windows Hardware Driver Verification')
OID_DIFFIE_HELLMAN_KEYDATA = ObjectId.new('1.3.6.1.5.2.3.2', name: 'OID_DIFFIE_HELLMAN_KEYDATA', label: 'Diffie Hellman Key Data')


OID_CMS_ENVELOPED_DATA = ObjectId.new('1.2.840.113549.1.7.3', name: 'OID_CMS_ENVELOPED_DATA', label: 'PKCS#7 CMS Enveloped Data')
OID_CMS_SIGNED_DATA = ObjectId.new('1.2.840.113549.1.7.2', name: 'OID_CMS_SIGNED_DATA', label: 'CMS Signed Data')

OID_DES_EDE3_CBC = ObjectId.new('1.2.840.113549.3.7', name: 'OID_DES_EDE_CBC', label: 'Triple DES encryption in CBC mode')
OID_AES256_CBC = ObjectId.new('2.16.840.1.101.3.4.1.42', name: 'OID_AES256_CBC', label: 'AES256 in CBC mode')
Expand Down
8 changes: 8 additions & 0 deletions lib/rex/proto/crypto_asn1/x509.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ class PrivateDomainName < RASN1::Model
]
end

class SubjectPublicKeyInfo < RASN1::Model
sequence :subject_public_key_info,
explicit: 1, constructed: true, optional: true,
content: [model(:algorithm, Rex::Proto::CryptoAsn1::Cms::AlgorithmIdentifier),
bit_string(:subject_public_key)
]
end

class BuiltinDomainDefinedAttribute < RASN1::Model
sequence :BuiltinDomainDefinedAttribute, content: [
printable_string(:type),
Expand Down
145 changes: 1 addition & 144 deletions lib/rex/proto/kerberos/model/pkinit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,78 +8,6 @@ module Model
# Contains the models for PKINIT-related ASN1 structures
# These use the RASN1 library to define the types
module Pkinit
class AlgorithmIdentifier < RASN1::Model
sequence :algorithm_identifier,
content: [objectid(:algorithm),
any(:parameters, optional: true)
]
end

class Attribute < RASN1::Model
sequence :attribute,
content: [objectid(:attribute_type),
set_of(:attribute_values, RASN1::Types::Any)
]
end

class AttributeTypeAndValue < RASN1::Model
sequence :attribute_type_and_value,
content: [objectid(:attribute_type),
any(:attribute_value)
]
end

class Certificate
# Rather than specifying the entire structure of a certificate, we pass this off
# to OpenSSL, effectively providing an interface between RASN and OpenSSL.

attr_accessor :options

def initialize(options={})
self.options = options
end

def to_der
self.options[:openssl_certificate]&.to_der || ''
end

# RASN1 Glue method - Say if DER can be built (not default value, not optional without value, has a value)
# @return [Boolean]
# @since 0.12
def can_build?
!to_der.empty?
end

# RASN1 Glue method
def primitive?
false
end

# RASN1 Glue method
def value
options[:openssl_certificate]
end

def parse!(str, ber: false)
self.options[:openssl_certificate] = OpenSSL::X509::Certificate.new(str)
to_der.length
end
end

class ContentInfo < RASN1::Model
sequence :content_info,
content: [objectid(:content_type),
# In our case, expected to be SignedData
any(:signed_data)
]

def signed_data
if self[:content_type].value == '1.2.840.113549.1.7.2'
SignedData.parse(self[:signed_data].value)
end
end
end

class DomainParameters < RASN1::Model
sequence :domain_parameters,
content: [integer(:p),
Expand All @@ -90,46 +18,6 @@ class DomainParameters < RASN1::Model
]
end

class EncapsulatedContentInfo < RASN1::Model
sequence :encapsulated_content_info,
content: [objectid(:econtent_type),
octet_string(:econtent, explicit: 0, constructed: true, optional: true)
]

def econtent
if self[:econtent_type].value == '1.3.6.1.5.2.3.2'
KdcDhKeyInfo.parse(self[:econtent].value)
elsif self[:econtent_type].value == '1.3.6.1.5.2.3.1'
AuthPack.parse(self[:econtent].value)
end
end
end

class Name
# Rather than specifying the entire structure of a name, we pass this off
# to OpenSSL, effectively providing an interface between RASN and OpenSSL.
attr_accessor :value

def initialize(options={})
end

def parse!(str, ber: false)
self.value = OpenSSL::X509::Name.new(str)
to_der.length
end

def to_der
self.value.to_der
end
end

class IssuerAndSerialNumber < RASN1::Model
sequence :signer_identifier,
content: [model(:issuer, Name),
integer(:serial_number)
]
end

class KdcDhKeyInfo < RASN1::Model
sequence :kdc_dh_key_info,
content: [bit_string(:subject_public_key, explicit: 0, constructed: true),
Expand All @@ -148,41 +36,10 @@ class PkAuthenticator < RASN1::Model
]
end

class SignerInfo < RASN1::Model
sequence :signer_info,
content: [integer(:version),
model(:sid, IssuerAndSerialNumber),
model(:digest_algorithm, AlgorithmIdentifier),
set_of(:signed_attrs, Attribute, implicit: 0, optional: true),
model(:signature_algorithm, AlgorithmIdentifier),
octet_string(:signature),
]
end

class SignedData < RASN1::Model
sequence :signed_data,
explicit: 0, constructed: true,
content: [integer(:version),
set_of(:digest_algorithms, AlgorithmIdentifier),
model(:encap_content_info, EncapsulatedContentInfo),
set_of(:certificates, Certificate, implicit: 0, optional: true),
# CRLs - not implemented
set_of(:signer_infos, SignerInfo)
]
end

class SubjectPublicKeyInfo < RASN1::Model
sequence :subject_public_key_info,
explicit: 1, constructed: true, optional: true,
content: [model(:algorithm, AlgorithmIdentifier),
bit_string(:subject_public_key)
]
end

class AuthPack < RASN1::Model
sequence :auth_pack,
content: [model(:pk_authenticator, PkAuthenticator),
model(:client_public_value, SubjectPublicKeyInfo),
model(:client_public_value, Rex::Proto::CryptoAsn1::X509::SubjectPublicKeyInfo),
octet_string(:client_dh_nonce, implicit: 3, constructed: true, optional: true)
]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rex/proto/kerberos/model/pre_auth_pk_as_rep.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class PreAuthPkAsRep < RASN1::Model
]

def dh_rep_info
Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse(self[:dh_rep_info].value)
Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(self[:dh_rep_info].value)
end

def self.decode(data)
Expand Down
2 changes: 1 addition & 1 deletion lib/rex/proto/kerberos/model/pre_auth_pk_as_req.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PreAuthPkAsReq < RASN1::Model

def parse!(der, ber: false)
res = super(der, ber: ber)
self.signed_auth_pack = Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse(self[:signed_auth_pack].value)
self.signed_auth_pack = Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(self[:signed_auth_pack].value)

res
end
Expand Down
4 changes: 2 additions & 2 deletions spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
end

let(:content_info) do
Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse(
Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(
"\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \
"\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \
"\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \
Expand Down Expand Up @@ -328,7 +328,7 @@
end

it 'return a ContentInfo object' do
expect(result).to be_a(Rex::Proto::Kerberos::Model::Pkinit::ContentInfo)
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end

it 'should respond to #to_der' do
Expand Down
4 changes: 2 additions & 2 deletions spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
end

let(:content_info) do
Rex::Proto::Kerberos::Model::Pkinit::ContentInfo.parse(
Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(
"\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \
"\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \
"\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \
Expand Down Expand Up @@ -330,7 +330,7 @@
end

it 'return a ContentInfo object' do
expect(result).to be_a(Rex::Proto::Kerberos::Model::Pkinit::ContentInfo)
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end

it 'should respond to #to_der' do
Expand Down

0 comments on commit 49c8c8a

Please sign in to comment.