From 58628c443d02c2a3df6ca409146f2c352effdbff Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Sat, 4 Jul 2026 04:56:24 -0300 Subject: [PATCH 1/4] Add native Windows ADCS conenction logic --- go.mod | 22 +- go.sum | 31 +++ packages/gateway-v2/adcs/adcs.go | 405 ++++++++++++++++++++++++++++ packages/gateway-v2/adcs_handler.go | 230 ++++++++++++++++ packages/gateway-v2/gateway.go | 14 + 5 files changed, 695 insertions(+), 7 deletions(-) create mode 100644 packages/gateway-v2/adcs/adcs.go create mode 100644 packages/gateway-v2/adcs_handler.go diff --git a/go.mod b/go.mod index dd08a0ed..d24e131e 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/muesli/mango-cobra v1.2.0 github.com/muesli/reflow v0.3.0 github.com/muesli/roff v0.1.0 + github.com/oiweiwei/go-msrpc v1.5.1 github.com/pion/dtls/v3 v3.0.4 github.com/pion/logging v0.2.3 github.com/pion/turn/v4 v4.0.0 @@ -37,17 +38,18 @@ require ( github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a github.com/quic-go/quic-go v0.57.0 github.com/rs/cors v1.11.0 - github.com/rs/zerolog v1.26.1 + github.com/rs/zerolog v1.35.1 github.com/smallnest/resp3 v0.0.0-20251228151914-4f2fa7427e69 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.11.1 github.com/wasilibs/go-re2 v1.10.0 go.mongodb.org/mongo-driver/v2 v2.5.0 - golang.org/x/crypto v0.52.0 + go.mozilla.org/pkcs7 v0.9.0 + golang.org/x/crypto v0.53.0 golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 - golang.org/x/sys v0.45.0 - golang.org/x/term v0.43.0 + golang.org/x/sys v0.46.0 + golang.org/x/term v0.44.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.4 @@ -93,6 +95,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/geoffgarside/ber v1.1.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/errors v0.20.2 // indirect @@ -123,6 +126,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.6 // indirect + github.com/indece-official/go-ebcdic v1.2.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect @@ -134,7 +138,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect @@ -146,6 +150,10 @@ require ( github.com/muesli/mango-pflag v0.1.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oiweiwei/go-math v1.0.0 // indirect + github.com/oiweiwei/go-oem v1.0.0 // indirect + github.com/oiweiwei/go-smb2.fork v1.0.1 // indirect + github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/onsi/gomega v1.36.2 // indirect @@ -189,8 +197,8 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.267.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect diff --git a/go.sum b/go.sum index 15176da8..70eea6db 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= +github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gitleaks/go-gitdiff v0.9.1 h1:ni6z6/3i9ODT685OLCTf+s/ERlWUNWQF4x1pvoNICw0= github.com/gitleaks/go-gitdiff v0.9.1/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA= @@ -376,6 +378,8 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/indece-official/go-ebcdic v1.2.0 h1:nKCubkNoXrGvBp3MSYuplOQnhANCDEY512Ry5Mwr4a0= +github.com/indece-official/go-ebcdic v1.2.0/go.mod h1:RBddVJt0Ks0eDLRG5dhPwBDRiTNA7n+yv0dVFpSs46Q= github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= github.com/infisical/infisical-kmip v0.3.19 h1:JDndxhM9+GoHTqSOX47H3KIxIuDmrEHsq17wvhw3RL8= @@ -436,6 +440,8 @@ github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GW github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -488,6 +494,16 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oiweiwei/go-math v1.0.0 h1:eBxtydohwi0RTfSX8EPQRO7Xt8j4Ck5v7PVv8Hvp7jg= +github.com/oiweiwei/go-math v1.0.0/go.mod h1:rWG2PIZRYIjovChTlCcssArtO/s2pVj38c1h2QfH1UM= +github.com/oiweiwei/go-msrpc v1.5.1 h1:yVmgPcvCwAof1MQS0lxdaFPwYF7+kFSkaelu+0EajWc= +github.com/oiweiwei/go-msrpc v1.5.1/go.mod h1:kS2SutrTnu/feIejUclXs6V492QIwb19hDm65CZpSfU= +github.com/oiweiwei/go-oem v1.0.0 h1:77MJnres0RkNadz7oPEfsbUmZxw1XfYtl8yaxfm8bMM= +github.com/oiweiwei/go-oem v1.0.0/go.mod h1:+QzsBHKmIIYsYk6Cx+vmjBNSymazlroCVurNRzsgyIg= +github.com/oiweiwei/go-smb2.fork v1.0.1 h1:qtXQtuHImkToy3W8x0aByZne3ruHnA70ayCT7Ue9Ejo= +github.com/oiweiwei/go-smb2.fork v1.0.1/go.mod h1:h0CzLVvGAmq39izdYVHKyI5cLv6aHdbQAMKEe4dz4N8= +github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 h1:ZMXO5OtzPPSqZ7KPgknVuvHE5iAbSXq5JLgzrkiXknM= +github.com/oiweiwei/gokrb5.fork/v9 v9.0.6/go.mod h1:KEnkAYUYqZ5VwzxLFbv3JHlRhCvdFahjrdjjssMJJkI= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= @@ -546,6 +562,8 @@ github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -646,6 +664,8 @@ go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5 go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= +go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -705,6 +725,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -818,6 +840,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -877,6 +901,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -885,6 +911,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -900,6 +928,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -963,6 +993,7 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/packages/gateway-v2/adcs/adcs.go b/packages/gateway-v2/adcs/adcs.go new file mode 100644 index 00000000..a6af9241 --- /dev/null +++ b/packages/gateway-v2/adcs/adcs.go @@ -0,0 +1,405 @@ +// Package adcs implements a Microsoft AD CS client that speaks the native +// Windows Client Certificate Enrollment Protocol (MS-WCCE) over DCOM/RPC. +// +// It is used by the gateway to discover certificate templates, read the CA +// chain, and enroll certificates on behalf of the Infisical control plane, +// which sits outside the customer network and cannot speak DCOM directly. +// +// The connection is authenticated with the supplied Windows credentials and +// sealed with RPC packet privacy (encryption + integrity), so no TLS or server +// certificate is involved. Only the ICertRequestD2 interface is bound: binding +// the older ICertRequestD first makes D2 calls resolve to the wrong interface +// and fail with RPC_S_UNKNOWN_IF. +package adcs + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + "strings" + "unicode/utf16" + + config "github.com/oiweiwei/go-msrpc/config" + "github.com/oiweiwei/go-msrpc/dcerpc" + "github.com/oiweiwei/go-msrpc/midl/uuid" + "github.com/oiweiwei/go-msrpc/ssp/gssapi" + + "github.com/oiweiwei/go-msrpc/msrpc/dcom" + iactivation "github.com/oiweiwei/go-msrpc/msrpc/dcom/iactivation/v0" + iobjectexporter "github.com/oiweiwei/go-msrpc/msrpc/dcom/iobjectexporter/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dtyp" + + "github.com/oiweiwei/go-msrpc/msrpc/dcom/wcce" + icertrequestd2 "github.com/oiweiwei/go-msrpc/msrpc/dcom/wcce/icertrequestd2/v0" + winreg "github.com/oiweiwei/go-msrpc/msrpc/rrp/winreg/v1" + + "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" + + "go.mozilla.org/pkcs7" +) + +// clsidCertRequestD is the CLSID of the CertSrv Request DCOM class +// (CCertRequestD), MS-WCCE section 1.9. +var clsidCertRequestD = uuid.MustParse("d99e6e74-fc88-11d0-b498-00a0c90312f3") + +const ( + crPropCASigCertChain = 0x0000000D + crPropTemplates = 0x0000001D + + propTypeBinary = 0x00000003 + propTypeString = 0x00000004 + + // dwFlags for a binary DER PKCS#10 request. + crInBinary = 0x00000002 + crInPKCS10 = 0x00000100 + + // CR_DISP_ISSUED: the request was issued. + dispositionIssued = 3 +) + +// Credentials identifies and authenticates to the CA host. +type Credentials struct { + Host string // CA server FQDN (or IP) + Username string // DOMAIN\user or user@domain + Password string +} + +// Template is a certificate template published on the CA. +type Template struct { + Name string `json:"name"` +} + +// EnrollResult is the outcome of a certificate request. +type EnrollResult struct { + Disposition uint32 `json:"disposition"` + RequestID uint32 `json:"requestId"` + CertificatePem string `json:"certificatePem"` + ChainPem string `json:"chainPem"` + DispositionMessage string `json:"dispositionMessage,omitempty"` + HResult int32 `json:"hresult,omitempty"` +} + +// Client is an authenticated ICertRequestD2 session against one CA host. +type Client struct { + d2 icertrequestd2.CertRequestD2Client + this *dcom.ORPCThis + top dcerpc.Conn + dyn dcerpc.Conn +} + +// Dial establishes an authenticated MS-WCCE session over DCOM. +func Dial(ctx context.Context, creds Credentials) (*Client, error) { + cfg := config.New() + cfg.Server = "ncacn_ip_tcp:" + creds.Host // ServerAddr() returns this when Protocol is empty + cfg.Auth.Level = "privacy" // RPC packet privacy: seals and integrity-protects the channel + cfg.Username = creds.Username // credential pkg parses DOMAIN\user or user@domain + cfg.Credential.Password = creds.Password + + sctx := gssapi.NewSecurityContext(ctx) + + top, err := dcerpc.Dial(sctx, cfg.ServerAddr(), cfg.DialOptions(sctx)...) + if err != nil { + return nil, fmt.Errorf("dial CA endpoint mapper (port 135): %w", err) + } + + oxc, err := iobjectexporter.NewObjectExporterClient(sctx, top, cfg.ClientOptions(sctx)...) + if err != nil { + _ = top.Close(sctx) + return nil, fmt.Errorf("create object exporter client: %w", err) + } + srv, err := oxc.ServerAlive2(sctx, &iobjectexporter.ServerAlive2Request{}) + if err != nil { + _ = top.Close(sctx) + return nil, fmt.Errorf("authenticate to CA (check host and credentials): %w", err) + } + + iact, err := iactivation.NewActivationClient(sctx, top, cfg.ClientOptions(sctx)...) + if err != nil { + _ = top.Close(sctx) + return nil, fmt.Errorf("create activation client: %w", err) + } + act, err := iact.RemoteActivation(sctx, &iactivation.RemoteActivationRequest{ + ORPCThis: &dcom.ORPCThis{Version: srv.COMVersion}, + ClassID: dtyp.GUIDFromUUID(clsidCertRequestD), + IIDs: []*dcom.IID{icertrequestd2.CertRequestD2IID}, + RequestedProtocolSequences: []uint16{7}, // ncacn_ip_tcp + }) + if err != nil { + _ = top.Close(sctx) + return nil, fmt.Errorf("activate certificate service: %w", err) + } + if act.HResult != 0 { + _ = top.Close(sctx) + return nil, fmt.Errorf("activate certificate service: %s", hresult.FromCode(uint32(act.HResult))) + } + + dyn, err := dcerpc.Dial(sctx, cfg.ServerAddr(), + append(cfg.DialOptions(sctx), act.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...)...) + if err != nil { + _ = top.Close(sctx) + return nil, fmt.Errorf("dial certificate service endpoint: %w", err) + } + + // Fresh GSSAPI security context for the second physical connection (the dynamic + // endpoint): the activation connection's context can't be reused across the new dial. + bctx := gssapi.NewSecurityContext(sctx) + // Bind ONLY the ICertRequestD2 syntax so the default presentation context is D2. + d2conn, err := dyn.Bind(bctx, append(cfg.ClientOptions(bctx), + dcerpc.WithAbstractSyntax(icertrequestd2.CertRequestD2SyntaxV0_0))...) + if err != nil { + _ = dyn.Close(sctx) + _ = top.Close(sctx) + return nil, fmt.Errorf("bind ICertRequestD2: %w", err) + } + d2, err := icertrequestd2.NewCertRequestD2Client(bctx, d2conn, dcerpc.WithNoBind(d2conn)) + if err != nil { + _ = dyn.Close(sctx) + _ = top.Close(sctx) + return nil, fmt.Errorf("create ICertRequestD2 client: %w", err) + } + if len(act.InterfaceData) == 0 { + _ = dyn.Close(sctx) + _ = top.Close(sctx) + return nil, fmt.Errorf("activation returned no interface data") + } + d2 = d2.IPID(bctx, act.InterfaceData[0].IPID()) + + return &Client{d2: d2, this: &dcom.ORPCThis{Version: srv.COMVersion}, top: top, dyn: dyn}, nil +} + +// Close releases the underlying connections. +func (c *Client) Close(ctx context.Context) { + if c.dyn != nil { + _ = c.dyn.Close(ctx) + } + if c.top != nil { + _ = c.top.Close(ctx) + } +} + +// Ping verifies the certificate service is answering. It needs no CA name, so +// it validates that the host is reachable and the credentials are accepted. +func (c *Client) Ping(ctx context.Context) error { + if _, err := c.d2.Ping2(ctx, &icertrequestd2.Ping2Request{This: c.this}); err != nil { + return fmt.Errorf("ping certificate service: %w", err) + } + return nil +} + +func (c *Client) getStringProperty(ctx context.Context, caName string, propID int32) (string, error) { + resp, err := c.d2.GetCAProperty(ctx, &icertrequestd2.GetCAPropertyRequest{ + This: c.this, Authority: caName, PropertyID: propID, PropertyType: propTypeString, + }) + if err != nil { + return "", err + } + // The CA reports a rejected request (e.g. wrong authority name) via a non-zero Return + // HRESULT rather than a transport error, leaving PropertyValue undefined. + if resp.Return != 0 { + return "", fmt.Errorf("read CA property: %s", hresult.FromCode(uint32(resp.Return))) + } + if resp.PropertyValue == nil { + return "", fmt.Errorf("read CA property: empty response") + } + return utf16le(resp.PropertyValue.Buffer), nil +} + +func (c *Client) getChainPem(ctx context.Context, caName string) (string, error) { + resp, err := c.d2.GetCAProperty(ctx, &icertrequestd2.GetCAPropertyRequest{ + This: c.this, Authority: caName, PropertyID: crPropCASigCertChain, PropertyType: propTypeBinary, + }) + if err != nil { + return "", fmt.Errorf("read CA chain: %w", err) + } + if resp.Return != 0 { + return "", fmt.Errorf("read CA chain: %s", hresult.FromCode(uint32(resp.Return))) + } + if resp.PropertyValue == nil { + return "", fmt.Errorf("read CA chain: empty response") + } + return pkcs7ToPem(resp.PropertyValue.Buffer) +} + +// Templates lists the certificate templates published on the CA. +func (c *Client) Templates(ctx context.Context, caName string) ([]Template, error) { + raw, err := c.getStringProperty(ctx, caName, crPropTemplates) + if err != nil { + return nil, fmt.Errorf("list templates: %w", err) + } + // CR_PROP_TEMPLATES returns "name\noid\nname\noid\n..." pairs; only the name is needed. + parts := strings.Split(strings.Trim(raw, "\n"), "\n") + var out []Template + for i := 0; i+1 < len(parts); i += 2 { + out = append(out, Template{Name: parts[i]}) + } + return out, nil +} + +// Enroll submits a DER-encoded PKCS#10 CSR against the named template and +// returns the issued certificate and CA chain. +func (c *Client) Enroll(ctx context.Context, caName, template string, csrDER []byte) (*EnrollResult, error) { + resp, err := c.d2.Request2(ctx, &icertrequestd2.Request2Request{ + This: c.this, + Authority: caName, + Flags: crInBinary | crInPKCS10, + Attributes: "CertificateTemplate:" + template, + Request: &wcce.CertTransportBlob{Length: uint32(len(csrDER)), Buffer: csrDER}, + }) + if err != nil { + return nil, fmt.Errorf("submit certificate request: %w", err) + } + + result := &EnrollResult{Disposition: resp.Disposition, RequestID: resp.RequestID, HResult: resp.Return} + if resp.DispositionMessage != nil { + result.DispositionMessage = utf16le(resp.DispositionMessage.Buffer) + } + if resp.Disposition != dispositionIssued || resp.EncodedCert == nil || len(resp.EncodedCert.Buffer) == 0 { + return result, nil + } + + certPem, err := issuedCertToPem(resp.EncodedCert.Buffer) + if err != nil { + return nil, err + } + result.CertificatePem = certPem + + chain, err := c.getChainPem(ctx, caName) + if err != nil { + return nil, err + } + result.ChainPem = chain + return result, nil +} + +// issuedCertToPem accepts either a bare DER certificate or a PKCS#7 bundle and +// returns the leaf certificate as PEM. +func issuedCertToPem(b []byte) (string, error) { + if cert, err := x509.ParseCertificate(b); err == nil { + return string(certToPem(cert)), nil + } + p7, err := pkcs7.Parse(b) + if err != nil { + return "", fmt.Errorf("parse issued certificate: %w", err) + } + if len(p7.Certificates) == 0 { + return "", fmt.Errorf("issued certificate response contained no certificates") + } + for _, cert := range p7.Certificates { + if !cert.IsCA { + return string(certToPem(cert)), nil + } + } + return string(certToPem(p7.Certificates[0])), nil +} + +// pkcs7ToPem converts a PKCS#7 (CERTTRANSBLOB) chain into concatenated PEM certs. +func pkcs7ToPem(der []byte) (string, error) { + if len(der) == 0 { + return "", nil + } + p7, err := pkcs7.Parse(der) + if err != nil { + return "", fmt.Errorf("parse PKCS#7 chain: %w", err) + } + var sb strings.Builder + for _, cert := range p7.Certificates { + sb.Write(certToPem(cert)) + } + return sb.String(), nil +} + +func certToPem(cert *x509.Certificate) []byte { + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) +} + +// certSvcActiveValuePath is the registry key holding the active CA's sanitized +// name in its "Active" value. MS-WCCE offers no way to enumerate the CA name +// (every call requires it as input), so we read it from the registry over +// MS-RRP. Requires the Remote Registry service to be running on the CA host. +const certSvcActiveValuePath = `SYSTEM\CurrentControlSet\Services\CertSvc\Configuration` + +// DiscoverCAName reads the active CA name from the host's registry over MS-RRP, +// using the same host and credentials as the WCCE path. +func DiscoverCAName(ctx context.Context, creds Credentials) (string, error) { + // The Remote Registry service (winreg) is exposed over named pipes (SMB), not + // the TCP endpoint mapper, so use ncacn_np and skip EPM. SMB supports only the + // insecure or seal (privacy) RPC security levels. + cfg := config.New().DisableEPM() + cfg.Protocol = "ncacn_np" + cfg.ServerAddress = creds.Host + cfg.Auth.Level = "privacy" + cfg.Username = creds.Username + cfg.Credential.Password = creds.Password + + sctx := gssapi.NewSecurityContext(ctx) + cc, err := dcerpc.Dial(sctx, cfg.ServerAddr(), cfg.DialOptions(sctx)...) + if err != nil { + return "", fmt.Errorf("dial remote registry: %w", err) + } + defer func() { _ = cc.Close(sctx) }() + + cli, err := winreg.NewWinregClient(sctx, cc, cfg.ClientOptions(sctx)...) + if err != nil { + return "", fmt.Errorf("create winreg client (is the Remote Registry service running?): %w", err) + } + + hklm, err := cli.OpenLocalMachine(sctx, &winreg.OpenLocalMachineRequest{DesiredAccess: winreg.KeyQueryValue}) + if err != nil { + return "", fmt.Errorf("open HKLM: %w", err) + } + // winreg reports failures via a non-zero Return (Win32 error code), not a transport error. + if hklm.Return != 0 { + return "", fmt.Errorf("open HKLM: winreg error %d", hklm.Return) + } + + sub, err := cli.BaseRegOpenKey(sctx, &winreg.BaseRegOpenKeyRequest{ + Key: hklm.Key, + SubKey: &winreg.UnicodeString{Buffer: certSvcActiveValuePath + "\x00"}, + DesiredAccess: winreg.KeyQueryValue, + }) + if err != nil { + return "", fmt.Errorf("open CertSvc configuration key (is AD CS installed on this host?): %w", err) + } + if sub.Return != 0 { + return "", fmt.Errorf("open CertSvc configuration key (is AD CS installed on this host?): winreg error %d", sub.Return) + } + + const bufLen = 1024 + resp, err := cli.BaseRegQueryValue(sctx, &winreg.BaseRegQueryValueRequest{ + Key: sub.ResultKey, + ValueName: &winreg.UnicodeString{Buffer: "Active\x00"}, + Data: make([]byte, bufLen), + DataLength: bufLen, + Length: bufLen, + }) + if err != nil { + return "", fmt.Errorf("read Active CA value: %w", err) + } + if resp.Return != 0 { + return "", fmt.Errorf("read Active CA value: winreg error %d", resp.Return) + } + + decoded, err := winreg.DecodeValue(resp.Type, resp.Data) + if err != nil { + return "", fmt.Errorf("decode Active CA value: %w", err) + } + name, ok := decoded.(string) + if !ok || name == "" { + return "", fmt.Errorf("no active CA name found on this host") + } + return name, nil +} + +func utf16le(b []byte) string { + u := make([]uint16, 0, len(b)/2) + for i := 0; i+1 < len(b); i += 2 { + u = append(u, uint16(b[i])|uint16(b[i+1])<<8) + } + for len(u) > 0 && u[len(u)-1] == 0 { + u = u[:len(u)-1] + } + return string(utf16.Decode(u)) +} diff --git a/packages/gateway-v2/adcs_handler.go b/packages/gateway-v2/adcs_handler.go new file mode 100644 index 00000000..064533c1 --- /dev/null +++ b/packages/gateway-v2/adcs_handler.go @@ -0,0 +1,230 @@ +package gatewayv2 + +import ( + "bufio" + "context" + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/Infisical/infisical-merge/packages/gateway-v2/adcs" + "github.com/rs/zerolog/log" +) + +// adcsRequestEnvelope is the common body for every ADCS/MS-WCCE operation. +// Credentials are supplied per request over the mTLS relay tunnel and are never +// persisted by the gateway. +type adcsRequestEnvelope struct { + Host string `json:"host"` + Username string `json:"username"` + // Password is a string (not []byte we could zero after use, unlike the PKCS#11 PIN) + // because go-msrpc's cfg.Credential.Password is itself a string, so a zeroable copy + // would not remove the secret from process memory anyway. It lives only for the + // request and is never persisted or logged. + Password string `json:"password"` + CAName string `json:"caName"` + Params json.RawMessage `json:"params"` +} + +type adcsEnrollParams struct { + Template string `json:"template"` + CSR string `json:"csr"` // base64-encoded DER PKCS#10 +} + +type adcsResponse struct { + Result json.RawMessage `json:"result"` +} + +type adcsErrorResponse struct { + Error adcsErrorBody `json:"error"` +} + +type adcsErrorBody struct { + Message string `json:"message"` +} + +const ( + adcsRequestDeadline = 90 * time.Second + maxAdcsRequestBodyBytes = 256 * 1024 +) + +// serveAdcsOverTLS reads a single HTTP request off the TLS connection and +// dispatches it to the ADCS operation mux, mirroring the PKCS#11 handler. +func serveAdcsOverTLS(ctx context.Context, conn *tls.Conn, reader *bufio.Reader) error { + _ = conn.SetDeadline(time.Now().Add(adcsRequestDeadline)) + + reqCh := make(chan *http.Request, 1) + errCh := make(chan error, 1) + go func() { + req, err := http.ReadRequest(reader) + if err != nil { + errCh <- err + return + } + reqCh <- req + }() + + var req *http.Request + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + return fmt.Errorf("failed to read HTTP request: %w", err) + case req = <-reqCh: + } + + log.Debug().Str("path", req.URL.Path).Msg("adcs: request received") + + // Tie the operation to the gateway lifecycle and the request budget so an in-flight + // WCCE call is cancelled on shutdown instead of only when the TLS deadline fires. + opCtx, cancel := context.WithTimeout(ctx, adcsRequestDeadline) + defer cancel() + req = req.WithContext(opCtx) + + rw := newBufferedResponseWriter() + serveAdcsMux().ServeHTTP(rw, req) + if err := rw.writeTo(conn); err != nil { + return fmt.Errorf("failed to write response: %w", err) + } + log.Debug().Int("status", rw.status).Msg("adcs: response written") + return nil +} + +func serveAdcsMux() *http.ServeMux { + mux := http.NewServeMux() + mux.HandleFunc("/v1/test", wrapAdcs(handleAdcsTest, true)) + // discover-ca reads the CA name over winreg (SMB) and never touches WCCE, so it must + // not require a WCCE session. It runs before the CA name is even known. + mux.HandleFunc("/v1/discover-ca", wrapAdcs(handleAdcsDiscoverCA, false)) + mux.HandleFunc("/v1/templates", wrapAdcs(handleAdcsTemplates, true)) + mux.HandleFunc("/v1/enroll", wrapAdcs(handleAdcsEnroll, true)) + return mux +} + +type adcsHandler func(ctx context.Context, client *adcs.Client, env *adcsRequestEnvelope) (any, error) + +// wrapAdcs decodes the envelope, optionally opens an authenticated MS-WCCE session, +// runs the operation, and encodes the JSON result or error. It recovers from panics +// so a malformed CA/registry response can never take down the gateway process. +func wrapAdcs(fn adcsHandler, needsWcce bool) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if r.Body != nil { + _ = r.Body.Close() + } + if p := recover(); p != nil { + log.Error().Interface("panic", p).Str("path", r.URL.Path).Msg("adcs: recovered from panic") + writeAdcsError(w, http.StatusInternalServerError, "Internal error handling ADCS request") + } + }() + + if r.Method != http.MethodPost { + writeAdcsError(w, http.StatusMethodNotAllowed, "Only POST is supported") + return + } + if r.ContentLength > maxAdcsRequestBodyBytes { + writeAdcsError(w, http.StatusRequestEntityTooLarge, "Request body too large") + return + } + r.Body = http.MaxBytesReader(w, r.Body, maxAdcsRequestBodyBytes) + + var env adcsRequestEnvelope + if err := json.NewDecoder(r.Body).Decode(&env); err != nil { + writeAdcsError(w, http.StatusBadRequest, "Malformed request body") + return + } + if env.Host == "" || env.Username == "" { + writeAdcsError(w, http.StatusBadRequest, "host and username are required") + return + } + + ctx := r.Context() + + var client *adcs.Client + if needsWcce { + dialed, err := adcs.Dial(ctx, adcs.Credentials{Host: env.Host, Username: env.Username, Password: env.Password}) + if err != nil { + log.Warn().Err(err).Msg("adcs: dial failed") + writeAdcsError(w, http.StatusBadGateway, err.Error()) + return + } + defer dialed.Close(ctx) + client = dialed + } + + result, err := fn(ctx, client, &env) + if err != nil { + log.Warn().Err(err).Str("path", r.URL.Path).Msg("adcs: operation failed") + writeAdcsError(w, http.StatusBadGateway, err.Error()) + return + } + + raw, err := json.Marshal(result) + if err != nil { + writeAdcsError(w, http.StatusInternalServerError, "Failed to marshal result") + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(adcsResponse{Result: raw}); err != nil { + log.Warn().Err(err).Msg("adcs: failed to encode response") + } + } +} + +func handleAdcsTest(ctx context.Context, client *adcs.Client, _ *adcsRequestEnvelope) (any, error) { + if err := client.Ping(ctx); err != nil { + return nil, err + } + return map[string]any{"ok": true}, nil +} + +func handleAdcsDiscoverCA(ctx context.Context, _ *adcs.Client, env *adcsRequestEnvelope) (any, error) { + name, err := adcs.DiscoverCAName(ctx, adcs.Credentials{Host: env.Host, Username: env.Username, Password: env.Password}) + if err != nil { + return nil, err + } + return map[string]any{"caName": name}, nil +} + +func handleAdcsTemplates(ctx context.Context, client *adcs.Client, env *adcsRequestEnvelope) (any, error) { + if env.CAName == "" { + return nil, fmt.Errorf("caName is required") + } + templates, err := client.Templates(ctx, env.CAName) + if err != nil { + return nil, err + } + return map[string]any{"templates": templates}, nil +} + +func handleAdcsEnroll(ctx context.Context, client *adcs.Client, env *adcsRequestEnvelope) (any, error) { + if env.CAName == "" { + return nil, fmt.Errorf("caName is required") + } + var p adcsEnrollParams + if err := json.Unmarshal(env.Params, &p); err != nil { + return nil, fmt.Errorf("malformed enroll params") + } + if p.Template == "" { + return nil, fmt.Errorf("template is required") + } + csrDER, err := base64.StdEncoding.DecodeString(p.CSR) + if err != nil { + return nil, fmt.Errorf("csr is not valid base64 DER") + } + if len(csrDER) == 0 { + return nil, fmt.Errorf("csr is empty") + } + return client.Enroll(ctx, env.CAName, p.Template, csrDER) +} + +func writeAdcsError(w http.ResponseWriter, status int, message string) { + body, _ := json.Marshal(adcsErrorResponse{Error: adcsErrorBody{Message: message}}) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _, _ = w.Write(body) +} diff --git a/packages/gateway-v2/gateway.go b/packages/gateway-v2/gateway.go index d66fae98..6624fd88 100644 --- a/packages/gateway-v2/gateway.go +++ b/packages/gateway-v2/gateway.go @@ -41,6 +41,7 @@ const ( ForwardModePing ForwardMode = "PING" ForwardModeHealth ForwardMode = "HEALTH" ForwardModePkcs11 ForwardMode = "PKCS11" + ForwardModeADCS ForwardMode = "ADCS" ) type ActorType string @@ -994,6 +995,14 @@ func (g *Gateway) handleIncomingChannel(newChannel ssh.NewChannel) { log.Info().Msg("PKCS#11 handler completed") } return + } else if forwardConfig.Mode == ForwardModeADCS { + log.Info().Msg("Starting ADCS/MS-WCCE handler") + if err := serveAdcsOverTLS(g.ctx, tlsConn, reader); err != nil { + log.Error().Err(err).Msg("ADCS handler ended with error") + } else { + log.Info().Msg("ADCS handler completed") + } + return } } @@ -1052,6 +1061,10 @@ func (g *Gateway) parseForwardConfigFromALPN(tlsConn *tls.Conn, reader *bufio.Re config.Mode = ForwardModePkcs11 return config, nil + case "infisical-adcs": + config.Mode = ForwardModeADCS + return config, nil + default: return nil, fmt.Errorf("unsupported ALPN protocol: %s", negotiatedProtocol) } @@ -1225,6 +1238,7 @@ func nextProtosForGateway(pkcs11Loaded bool) []string { "infisical-pam-rdp-browser", "infisical-pam-session-cancellation", "infisical-pam-capabilities", + "infisical-adcs", } if pkcs11Loaded { base = append(base, "infisical-pkcs11") From 0f6c12423895adcdc911a78d2cf30ef6a672ce44 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Sat, 4 Jul 2026 05:21:23 -0300 Subject: [PATCH 2/4] Address bot PR comments --- packages/cmd/root.go | 8 ++++++++ packages/gateway-v2/adcs/adcs.go | 4 ++++ packages/gateway-v2/adcs_handler.go | 7 ++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/cmd/root.go b/packages/cmd/root.go index e690ed6c..da16be0d 100644 --- a/packages/cmd/root.go +++ b/packages/cmd/root.go @@ -215,6 +215,14 @@ func GetLoggerConfig(w io.Writer) zerolog.ConsoleWriter { return zerolog.ConsoleWriter{ Out: w, TimeFormat: time.RFC3339, + // zerolog >= 1.35 bolds info/warn/error messages by default. Keep the message + // rendered as-is so CLI output stays consistent with prior releases. + FormatMessage: func(i interface{}) string { + if i == nil { + return "" + } + return fmt.Sprintf("%s", i) + }, FormatLevel: func(i interface{}) string { level := fmt.Sprintf("%s", i) color := levelColors[level] diff --git a/packages/gateway-v2/adcs/adcs.go b/packages/gateway-v2/adcs/adcs.go index a6af9241..212b246b 100644 --- a/packages/gateway-v2/adcs/adcs.go +++ b/packages/gateway-v2/adcs/adcs.go @@ -241,6 +241,10 @@ func (c *Client) Templates(ctx context.Context, caName string) ([]Template, erro // Enroll submits a DER-encoded PKCS#10 CSR against the named template and // returns the issued certificate and CA chain. func (c *Client) Enroll(ctx context.Context, caName, template string, csrDER []byte) (*EnrollResult, error) { + if strings.ContainsAny(template, "\r\n") { + return nil, fmt.Errorf("template name contains invalid characters") + } + resp, err := c.d2.Request2(ctx, &icertrequestd2.Request2Request{ This: c.this, Authority: caName, diff --git a/packages/gateway-v2/adcs_handler.go b/packages/gateway-v2/adcs_handler.go index 064533c1..e91659e9 100644 --- a/packages/gateway-v2/adcs_handler.go +++ b/packages/gateway-v2/adcs_handler.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "net/http" + "sync" "time" "github.com/Infisical/infisical-merge/packages/gateway-v2/adcs" @@ -93,7 +94,7 @@ func serveAdcsOverTLS(ctx context.Context, conn *tls.Conn, reader *bufio.Reader) return nil } -func serveAdcsMux() *http.ServeMux { +var serveAdcsMux = sync.OnceValue(func() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("/v1/test", wrapAdcs(handleAdcsTest, true)) // discover-ca reads the CA name over winreg (SMB) and never touches WCCE, so it must @@ -102,7 +103,7 @@ func serveAdcsMux() *http.ServeMux { mux.HandleFunc("/v1/templates", wrapAdcs(handleAdcsTemplates, true)) mux.HandleFunc("/v1/enroll", wrapAdcs(handleAdcsEnroll, true)) return mux -} +}) type adcsHandler func(ctx context.Context, client *adcs.Client, env *adcsRequestEnvelope) (any, error) @@ -148,7 +149,7 @@ func wrapAdcs(fn adcsHandler, needsWcce bool) http.HandlerFunc { dialed, err := adcs.Dial(ctx, adcs.Credentials{Host: env.Host, Username: env.Username, Password: env.Password}) if err != nil { log.Warn().Err(err).Msg("adcs: dial failed") - writeAdcsError(w, http.StatusBadGateway, err.Error()) + writeAdcsError(w, http.StatusBadGateway, "Failed to connect to the ADCS host") return } defer dialed.Close(ctx) From b3be8b21eda1bc4eddcf637b894d3a08c4af60df Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Sat, 4 Jul 2026 05:32:45 -0300 Subject: [PATCH 3/4] Fix e2e go.mod --- e2e/go.mod | 26 +++++++++++++-------- e2e/go.sum | 66 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/e2e/go.mod b/e2e/go.mod index c850e836..1e8fe9bd 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -15,14 +15,14 @@ require ( github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 github.com/oapi-codegen/runtime v1.1.2 github.com/redis/go-redis/v9 v9.17.2 - github.com/rs/zerolog v1.26.1 + github.com/rs/zerolog v1.35.1 github.com/spf13/cobra v1.10.1 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go v0.40.0 github.com/testcontainers/testcontainers-go/modules/compose v0.40.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 github.com/testcontainers/testcontainers-go/modules/redis v0.40.0 - golang.org/x/crypto v0.52.0 + golang.org/x/crypto v0.53.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -109,6 +109,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/geoffgarside/ber v1.1.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/gitleaks/go-gitdiff v0.9.1 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -156,6 +157,7 @@ require ( github.com/huandu/xstrings v1.5.0 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/indece-official/go-ebcdic v1.2.0 // indirect github.com/infisical/go-sdk v0.7.0 // indirect github.com/infisical/infisical-kmip v0.3.19 // indirect github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect @@ -177,7 +179,7 @@ require ( github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect @@ -218,6 +220,11 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/oiweiwei/go-math v1.0.0 // indirect + github.com/oiweiwei/go-msrpc v1.5.1 // indirect + github.com/oiweiwei/go-oem v1.0.0 // indirect + github.com/oiweiwei/go-smb2.fork v1.0.1 // indirect + github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect @@ -291,6 +298,7 @@ require ( github.com/zclconf/go-cty v1.17.0 // indirect go.mongodb.org/mongo-driver v1.17.9 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.mozilla.org/pkcs7 v0.9.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect @@ -312,15 +320,15 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/mod v0.35.0 // indirect + golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.45.0 // indirect google.golang.org/api v0.267.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index 1560c629..0aacb47d 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -328,6 +328,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= +github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -492,10 +494,12 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= @@ -553,6 +557,8 @@ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/indece-official/go-ebcdic v1.2.0 h1:nKCubkNoXrGvBp3MSYuplOQnhANCDEY512Ry5Mwr4a0= +github.com/indece-official/go-ebcdic v1.2.0/go.mod h1:RBddVJt0Ks0eDLRG5dhPwBDRiTNA7n+yv0dVFpSs46Q= github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= github.com/infisical/infisical-kmip v0.3.19 h1:JDndxhM9+GoHTqSOX47H3KIxIuDmrEHsq17wvhw3RL8= @@ -640,10 +646,9 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -753,6 +758,16 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oiweiwei/go-math v1.0.0 h1:eBxtydohwi0RTfSX8EPQRO7Xt8j4Ck5v7PVv8Hvp7jg= +github.com/oiweiwei/go-math v1.0.0/go.mod h1:rWG2PIZRYIjovChTlCcssArtO/s2pVj38c1h2QfH1UM= +github.com/oiweiwei/go-msrpc v1.5.1 h1:yVmgPcvCwAof1MQS0lxdaFPwYF7+kFSkaelu+0EajWc= +github.com/oiweiwei/go-msrpc v1.5.1/go.mod h1:kS2SutrTnu/feIejUclXs6V492QIwb19hDm65CZpSfU= +github.com/oiweiwei/go-oem v1.0.0 h1:77MJnres0RkNadz7oPEfsbUmZxw1XfYtl8yaxfm8bMM= +github.com/oiweiwei/go-oem v1.0.0/go.mod h1:+QzsBHKmIIYsYk6Cx+vmjBNSymazlroCVurNRzsgyIg= +github.com/oiweiwei/go-smb2.fork v1.0.1 h1:qtXQtuHImkToy3W8x0aByZne3ruHnA70ayCT7Ue9Ejo= +github.com/oiweiwei/go-smb2.fork v1.0.1/go.mod h1:h0CzLVvGAmq39izdYVHKyI5cLv6aHdbQAMKEe4dz4N8= +github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 h1:ZMXO5OtzPPSqZ7KPgknVuvHE5iAbSXq5JLgzrkiXknM= +github.com/oiweiwei/gokrb5.fork/v9 v9.0.6/go.mod h1:KEnkAYUYqZ5VwzxLFbv3JHlRhCvdFahjrdjjssMJJkI= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -863,9 +878,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1024,7 +1038,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -1038,6 +1051,8 @@ go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5 go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= +go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1112,13 +1127,12 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1158,8 +1172,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1201,7 +1215,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -1238,8 +1251,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1297,14 +1310,12 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1313,8 +1324,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1322,8 +1333,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1337,8 +1348,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1398,11 +1409,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From c45431e654cad207804cdfb42d9fee4894ab4593 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Sun, 5 Jul 2026 22:35:53 -0300 Subject: [PATCH 4/4] ADCS certificate revocation --- packages/gateway-v2/adcs/adcs.go | 126 +++++++++++++++++++++++----- packages/gateway-v2/adcs_handler.go | 57 +++++++++++-- packages/gateway-v2/gateway.go | 2 +- 3 files changed, 157 insertions(+), 28 deletions(-) diff --git a/packages/gateway-v2/adcs/adcs.go b/packages/gateway-v2/adcs/adcs.go index 212b246b..0ae00c92 100644 --- a/packages/gateway-v2/adcs/adcs.go +++ b/packages/gateway-v2/adcs/adcs.go @@ -16,6 +16,7 @@ import ( "context" "crypto/x509" "encoding/pem" + "errors" "fmt" "strings" "unicode/utf16" @@ -29,7 +30,9 @@ import ( iactivation "github.com/oiweiwei/go-msrpc/msrpc/dcom/iactivation/v0" iobjectexporter "github.com/oiweiwei/go-msrpc/msrpc/dcom/iobjectexporter/v0" "github.com/oiweiwei/go-msrpc/msrpc/dtyp" + "github.com/oiweiwei/go-msrpc/msrpc/dtyp/filetime" + icertadmind "github.com/oiweiwei/go-msrpc/msrpc/dcom/csra/icertadmind/v0" "github.com/oiweiwei/go-msrpc/msrpc/dcom/wcce" icertrequestd2 "github.com/oiweiwei/go-msrpc/msrpc/dcom/wcce/icertrequestd2/v0" winreg "github.com/oiweiwei/go-msrpc/msrpc/rrp/winreg/v1" @@ -41,10 +44,21 @@ import ( "go.mozilla.org/pkcs7" ) -// clsidCertRequestD is the CLSID of the CertSrv Request DCOM class -// (CCertRequestD), MS-WCCE section 1.9. +// clsidCertRequestD and clsidCertAdminD are the DCOM coclass CLSIDs that +// IActivation::RemoteActivation needs. go-msrpc ships the interface IIDs but not these, so +// they are the fixed GUIDs from the specs. + +// CCertRequestD (MS-WCCE section 1.9): enrollment via ICertRequestD/D2. var clsidCertRequestD = uuid.MustParse("d99e6e74-fc88-11d0-b498-00a0c90312f3") +// CCertAdminD (MS-CSRA section 2.1): administration via ICertAdminD/D2, used here for revocation. +var clsidCertAdminD = uuid.MustParse("d99e6e73-fc88-11d0-b498-00a0c90312f3") + +// ErrConnect marks a failure to reach or authenticate to the CA host (as opposed to a CA-level +// rejection of the operation). Handlers collapse it to a generic message so the internal CA +// host/port is never echoed back to the control plane. +var ErrConnect = errors.New("failed to connect to the ADCS host") + const ( crPropCASigCertChain = 0x0000000D crPropTemplates = 0x0000001D @@ -90,8 +104,31 @@ type Client struct { dyn dcerpc.Conn } -// Dial establishes an authenticated MS-WCCE session over DCOM. -func Dial(ctx context.Context, creds Credentials) (*Client, error) { +// dcomSession holds the connections and identity from activating a CertSrv coclass and +// binding one of its interfaces. The caller builds its typed client from bound/bindCtx +// and applies ipid. +type dcomSession struct { + top dcerpc.Conn + dyn dcerpc.Conn + bound dcerpc.Conn + bindCtx context.Context + ipid *dcom.IPID + comVer *dcom.COMVersion +} + +func (s *dcomSession) close(ctx context.Context) { + if s.dyn != nil { + _ = s.dyn.Close(ctx) + } + if s.top != nil { + _ = s.top.Close(ctx) + } +} + +// dialAndActivate opens an authenticated DCOM channel to the CA host, activates the given +// coclass, and binds the requested interface syntax, returning a session the caller turns +// into a typed interface client. +func dialAndActivate(ctx context.Context, creds Credentials, clsid *uuid.UUID, iid *dcom.IID, syntax *dcerpc.SyntaxID) (*dcomSession, error) { cfg := config.New() cfg.Server = "ncacn_ip_tcp:" + creds.Host // ServerAddr() returns this when Protocol is empty cfg.Auth.Level = "privacy" // RPC packet privacy: seals and integrity-protects the channel @@ -123,8 +160,8 @@ func Dial(ctx context.Context, creds Credentials) (*Client, error) { } act, err := iact.RemoteActivation(sctx, &iactivation.RemoteActivationRequest{ ORPCThis: &dcom.ORPCThis{Version: srv.COMVersion}, - ClassID: dtyp.GUIDFromUUID(clsidCertRequestD), - IIDs: []*dcom.IID{icertrequestd2.CertRequestD2IID}, + ClassID: dtyp.GUIDFromUUID(clsid), + IIDs: []*dcom.IID{iid}, RequestedProtocolSequences: []uint16{7}, // ncacn_ip_tcp }) if err != nil { @@ -135,6 +172,10 @@ func Dial(ctx context.Context, creds Credentials) (*Client, error) { _ = top.Close(sctx) return nil, fmt.Errorf("activate certificate service: %s", hresult.FromCode(uint32(act.HResult))) } + if len(act.InterfaceData) == 0 { + _ = top.Close(sctx) + return nil, fmt.Errorf("activation returned no interface data") + } dyn, err := dcerpc.Dial(sctx, cfg.ServerAddr(), append(cfg.DialOptions(sctx), act.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...)...) @@ -146,28 +187,75 @@ func Dial(ctx context.Context, creds Credentials) (*Client, error) { // Fresh GSSAPI security context for the second physical connection (the dynamic // endpoint): the activation connection's context can't be reused across the new dial. bctx := gssapi.NewSecurityContext(sctx) - // Bind ONLY the ICertRequestD2 syntax so the default presentation context is D2. - d2conn, err := dyn.Bind(bctx, append(cfg.ClientOptions(bctx), - dcerpc.WithAbstractSyntax(icertrequestd2.CertRequestD2SyntaxV0_0))...) + // Bind ONLY the requested syntax so it is the default presentation context. + bound, err := dyn.Bind(bctx, append(cfg.ClientOptions(bctx), dcerpc.WithAbstractSyntax(syntax))...) if err != nil { _ = dyn.Close(sctx) _ = top.Close(sctx) - return nil, fmt.Errorf("bind ICertRequestD2: %w", err) + return nil, fmt.Errorf("bind interface: %w", err) } - d2, err := icertrequestd2.NewCertRequestD2Client(bctx, d2conn, dcerpc.WithNoBind(d2conn)) + + return &dcomSession{ + top: top, + dyn: dyn, + bound: bound, + bindCtx: bctx, + ipid: act.InterfaceData[0].IPID(), + comVer: srv.COMVersion, + }, nil +} + +// Dial establishes an authenticated MS-WCCE (ICertRequestD2) session over DCOM. +func Dial(ctx context.Context, creds Credentials) (*Client, error) { + sess, err := dialAndActivate(ctx, creds, clsidCertRequestD, icertrequestd2.CertRequestD2IID, icertrequestd2.CertRequestD2SyntaxV0_0) if err != nil { - _ = dyn.Close(sctx) - _ = top.Close(sctx) + return nil, err + } + + d2, err := icertrequestd2.NewCertRequestD2Client(sess.bindCtx, sess.bound, dcerpc.WithNoBind(sess.bound)) + if err != nil { + sess.close(ctx) return nil, fmt.Errorf("create ICertRequestD2 client: %w", err) } - if len(act.InterfaceData) == 0 { - _ = dyn.Close(sctx) - _ = top.Close(sctx) - return nil, fmt.Errorf("activation returned no interface data") + d2 = d2.IPID(sess.bindCtx, sess.ipid) + + return &Client{d2: d2, this: &dcom.ORPCThis{Version: sess.comVer}, top: sess.top, dyn: sess.dyn}, nil +} + +// Revoke revokes the certificate with the given serial number on the named CA via MS-CSRA +// (ICertAdminD::RevokeCertificate). serialHex is plain hex digits with no leading "0x" and +// reason is an RFC 5280 CRLReason code. Requires Manage CA / Certificate Manager rights. +func Revoke(ctx context.Context, creds Credentials, caName, serialHex string, reason uint32) error { + sess, err := dialAndActivate(ctx, creds, clsidCertAdminD, icertadmind.CertAdminDIID, icertadmind.CertAdminDSyntaxV0_0) + if err != nil { + return fmt.Errorf("%w: %w", ErrConnect, err) } - d2 = d2.IPID(bctx, act.InterfaceData[0].IPID()) + defer sess.close(ctx) - return &Client{d2: d2, this: &dcom.ORPCThis{Version: srv.COMVersion}, top: top, dyn: dyn}, nil + admin, err := icertadmind.NewCertAdminDClient(sess.bindCtx, sess.bound, dcerpc.WithNoBind(sess.bound)) + if err != nil { + return fmt.Errorf("%w: %w", ErrConnect, err) + } + admin = admin.IPID(sess.bindCtx, sess.ipid) + + // FileTime is the revocation date recorded in the CRL entry. If left nil the marshaler + // sends a zero FILETIME (a 1601 date), so stamp it with the current time. + resp, err := admin.RevokeCertificate(ctx, &icertadmind.RevokeCertificateRequest{ + This: &dcom.ORPCThis{Version: sess.comVer}, + Authority: caName, + SerialNumber: serialHex, + Reason: reason, + FileTime: filetime.Now(), + }) + if err != nil { + return fmt.Errorf("revoke certificate: %w", err) + } + // The CA reports a rejected revocation (e.g. unknown serial, insufficient rights) via a + // non-zero Return HRESULT rather than a transport error. + if resp.Return != 0 { + return fmt.Errorf("revoke certificate: %s", hresult.FromCode(uint32(resp.Return))) + } + return nil } // Close releases the underlying connections. diff --git a/packages/gateway-v2/adcs_handler.go b/packages/gateway-v2/adcs_handler.go index e91659e9..de72b0db 100644 --- a/packages/gateway-v2/adcs_handler.go +++ b/packages/gateway-v2/adcs_handler.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + "errors" "fmt" "net/http" "sync" @@ -19,7 +20,6 @@ import ( // Credentials are supplied per request over the mTLS relay tunnel and are never // persisted by the gateway. type adcsRequestEnvelope struct { - Host string `json:"host"` Username string `json:"username"` // Password is a string (not []byte we could zero after use, unlike the PKCS#11 PIN) // because go-msrpc's cfg.Credential.Password is itself a string, so a zeroable copy @@ -30,11 +30,26 @@ type adcsRequestEnvelope struct { Params json.RawMessage `json:"params"` } +// adcsCAHostContextKey carries the CA host into the operation handlers. The host comes from the +// signed client-certificate routing extension (GATEWAY_ROUTING_INFO_OID), never the request body, +// so a caller cannot point the gateway at an arbitrary host (SSRF). +type adcsCAHostContextKey struct{} + +func adcsCAHostFromContext(ctx context.Context) string { + host, _ := ctx.Value(adcsCAHostContextKey{}).(string) + return host +} + type adcsEnrollParams struct { Template string `json:"template"` CSR string `json:"csr"` // base64-encoded DER PKCS#10 } +type adcsRevokeParams struct { + SerialNumber string `json:"serialNumber"` // plain hex digits, no leading 0x + Reason uint32 `json:"reason"` // RFC 5280 CRLReason code +} + type adcsResponse struct { Result json.RawMessage `json:"result"` } @@ -54,7 +69,7 @@ const ( // serveAdcsOverTLS reads a single HTTP request off the TLS connection and // dispatches it to the ADCS operation mux, mirroring the PKCS#11 handler. -func serveAdcsOverTLS(ctx context.Context, conn *tls.Conn, reader *bufio.Reader) error { +func serveAdcsOverTLS(ctx context.Context, conn *tls.Conn, reader *bufio.Reader, caHost string) error { _ = conn.SetDeadline(time.Now().Add(adcsRequestDeadline)) reqCh := make(chan *http.Request, 1) @@ -83,6 +98,7 @@ func serveAdcsOverTLS(ctx context.Context, conn *tls.Conn, reader *bufio.Reader) // WCCE call is cancelled on shutdown instead of only when the TLS deadline fires. opCtx, cancel := context.WithTimeout(ctx, adcsRequestDeadline) defer cancel() + opCtx = context.WithValue(opCtx, adcsCAHostContextKey{}, caHost) req = req.WithContext(opCtx) rw := newBufferedResponseWriter() @@ -102,6 +118,9 @@ var serveAdcsMux = sync.OnceValue(func() *http.ServeMux { mux.HandleFunc("/v1/discover-ca", wrapAdcs(handleAdcsDiscoverCA, false)) mux.HandleFunc("/v1/templates", wrapAdcs(handleAdcsTemplates, true)) mux.HandleFunc("/v1/enroll", wrapAdcs(handleAdcsEnroll, true)) + // revoke uses MS-CSRA (ICertAdminD) rather than MS-WCCE, so it manages its own DCOM + // session inside the handler and must not require the shared WCCE session. + mux.HandleFunc("/v1/revoke", wrapAdcs(handleAdcsRevoke, false)) return mux }) @@ -137,16 +156,16 @@ func wrapAdcs(fn adcsHandler, needsWcce bool) http.HandlerFunc { writeAdcsError(w, http.StatusBadRequest, "Malformed request body") return } - if env.Host == "" || env.Username == "" { - writeAdcsError(w, http.StatusBadRequest, "host and username are required") + ctx := r.Context() + caHost := adcsCAHostFromContext(ctx) + if caHost == "" || env.Username == "" { + writeAdcsError(w, http.StatusBadRequest, "CA host and username are required") return } - ctx := r.Context() - var client *adcs.Client if needsWcce { - dialed, err := adcs.Dial(ctx, adcs.Credentials{Host: env.Host, Username: env.Username, Password: env.Password}) + dialed, err := adcs.Dial(ctx, adcs.Credentials{Host: caHost, Username: env.Username, Password: env.Password}) if err != nil { log.Warn().Err(err).Msg("adcs: dial failed") writeAdcsError(w, http.StatusBadGateway, "Failed to connect to the ADCS host") @@ -184,7 +203,7 @@ func handleAdcsTest(ctx context.Context, client *adcs.Client, _ *adcsRequestEnve } func handleAdcsDiscoverCA(ctx context.Context, _ *adcs.Client, env *adcsRequestEnvelope) (any, error) { - name, err := adcs.DiscoverCAName(ctx, adcs.Credentials{Host: env.Host, Username: env.Username, Password: env.Password}) + name, err := adcs.DiscoverCAName(ctx, adcs.Credentials{Host: adcsCAHostFromContext(ctx), Username: env.Username, Password: env.Password}) if err != nil { return nil, err } @@ -223,6 +242,28 @@ func handleAdcsEnroll(ctx context.Context, client *adcs.Client, env *adcsRequest return client.Enroll(ctx, env.CAName, p.Template, csrDER) } +func handleAdcsRevoke(ctx context.Context, _ *adcs.Client, env *adcsRequestEnvelope) (any, error) { + if env.CAName == "" { + return nil, fmt.Errorf("caName is required") + } + var p adcsRevokeParams + if err := json.Unmarshal(env.Params, &p); err != nil { + return nil, fmt.Errorf("malformed revoke params") + } + if p.SerialNumber == "" { + return nil, fmt.Errorf("serialNumber is required") + } + creds := adcs.Credentials{Host: adcsCAHostFromContext(ctx), Username: env.Username, Password: env.Password} + if err := adcs.Revoke(ctx, creds, env.CAName, p.SerialNumber, p.Reason); err != nil { + if errors.Is(err, adcs.ErrConnect) { + log.Warn().Err(err).Msg("adcs: revoke connection failed") + return nil, adcs.ErrConnect + } + return nil, err + } + return map[string]any{"ok": true}, nil +} + func writeAdcsError(w http.ResponseWriter, status int, message string) { body, _ := json.Marshal(adcsErrorResponse{Error: adcsErrorBody{Message: message}}) w.Header().Set("Content-Type", "application/json") diff --git a/packages/gateway-v2/gateway.go b/packages/gateway-v2/gateway.go index 6624fd88..d3a1cc5b 100644 --- a/packages/gateway-v2/gateway.go +++ b/packages/gateway-v2/gateway.go @@ -997,7 +997,7 @@ func (g *Gateway) handleIncomingChannel(newChannel ssh.NewChannel) { return } else if forwardConfig.Mode == ForwardModeADCS { log.Info().Msg("Starting ADCS/MS-WCCE handler") - if err := serveAdcsOverTLS(g.ctx, tlsConn, reader); err != nil { + if err := serveAdcsOverTLS(g.ctx, tlsConn, reader, forwardConfig.TargetHost); err != nil { log.Error().Err(err).Msg("ADCS handler ended with error") } else { log.Info().Msg("ADCS handler completed")