pax_global_header00006660000000000000000000000064142703134030014507gustar00rootroot0000000000000052 comment=89c463bcf0787b5ef10fcd9e467e759584ee0859 gokrb5-8.4.3/000077500000000000000000000000001427031340300127145ustar00rootroot00000000000000gokrb5-8.4.3/.github/000077500000000000000000000000001427031340300142545ustar00rootroot00000000000000gokrb5-8.4.3/.github/workflows/000077500000000000000000000000001427031340300163115ustar00rootroot00000000000000gokrb5-8.4.3/.github/workflows/testing.yml000066400000000000000000000074241427031340300205200ustar00rootroot00000000000000name: v7 on: push: paths-ignore: - 'v[0-9]+/**' pull_request: paths-ignore: - 'v[0-9]+/**' jobs: build: name: Tests runs-on: ubuntu-latest strategy: matrix: go: [ '1.16.x', '1.17.x', '1.18.x' ] env: TEST_KDC_ADDR: 127.0.0.1 TEST_HTTP_URL: http://cname.test.gokrb5 TEST_HTTP_ADDR: 127.0.0.1 DNS_IP: 127.0.88.53 DNSUTILS_OVERRIDE_NS: 127.0.88.53:53 GO111MODULE: auto steps: - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v1 with: go-version: ${{ matrix.go }} - name: Checkout uses: actions/checkout@v2 with: ref: ${{ github.ref }} - name: Test well formatted with gofmt run: | # Remove major version sub directories find . -maxdepth 1 -type d -regex '\./v[0-9]+' | xargs -i rm -rf {} GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g') id: gofmt - name: Copy to gopkg.in in GOPATH run: | mkdir -p /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7 cp -r $GITHUB_WORKSPACE/* /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7 id: gopkg - name: Get dependencies run: | go get -v -t -d ./... id: goGet - name: Unit tests run: | cd /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7 go test -race $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/) id: unitTests - name: Start integration test dependencies run: | sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq krb5-user sudo chmod 666 /etc/krb5.conf sudo docker run -d -h ns.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -e "TEST_KDC_ADDR=${TEST_KDC_ADDR}" -e "TEST_HTTP_ADDR=${TEST_HTTP_ADDR}" -p ${DNSUTILS_OVERRIDE_NS}:53 -p ${DNSUTILS_OVERRIDE_NS}:53/udp --name dns jcmturner/gokrb5:dns sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 88:88 -p 88:88/udp -p 464:464 -p 464:464/udp --name krb5kdc jcmturner/gokrb5:kdc-centos-default sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 78:88 -p 78:88/udp --name krb5kdc-old jcmturner/gokrb5:kdc-older sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 98:88 -p 98:88/udp --name krb5kdc-latest jcmturner/gokrb5:kdc-latest sudo docker run -d -h kdc.resdom.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 188:88 -p 188:88/udp --name krb5kdc-resdom jcmturner/gokrb5:kdc-resdom sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 58:88 -p 58:88/udp --name krb5kdc-shorttickets jcmturner/gokrb5:kdc-shorttickets sudo docker run -d --add-host host.test.gokrb5:127.0.0.88 -v /etc/localtime:/etc/localtime:ro -p 80:80 -p 443:443 --name gokrb5-http jcmturner/gokrb5:http sudo sed -i 's/nameserver .*/nameserver '${DNS_IP}'/g' /etc/resolv.conf dig _kerberos._udp.TEST.GOKRB5 id: intgTestDeps - name: Run Examples run: go run -tags="examples" examples/example.go id: examples - name: Tests including integration tests run: | cd /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7 go test -race $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/) env: INTEGRATION: 1 TESTPRIVILEGED: 1 id: intgTests - name: Tests (32bit) run: | cd /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7 go test $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/) env: GOARCH: 386 INTEGRATION: 1 TESTPRIVILEGED: 1 id: test32 gokrb5-8.4.3/.github/workflows/testingv8.yml000066400000000000000000000062621427031340300207750ustar00rootroot00000000000000# Name of the workflow needs to match the name of the major version directory name: v8 on: push: paths: - 'v8/**' pull_request: paths: - 'v8/**' jobs: build: name: Tests runs-on: ubuntu-latest strategy: matrix: go: [ '1.16.x', '1.17.x', '1.18.x' ] env: TEST_KDC_ADDR: 127.0.0.1 TEST_HTTP_URL: http://cname.test.gokrb5 TEST_HTTP_ADDR: 127.0.0.1 DNS_IP: 127.0.88.53 DNSUTILS_OVERRIDE_NS: 127.0.88.53:53 steps: - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v1 with: go-version: ${{ matrix.go }} - name: Checkout uses: actions/checkout@v2 with: ref: ${{ github.ref }} - name: Test well formatted with gofmt run: | GO_FILES=$(find ${GITHUB_WORKFLOW} -iname '*.go' -type f | grep -v /vendor/) test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g') id: gofmt - name: Unit tests run: | cd ${GITHUB_WORKFLOW} go test -race ./... id: unitTests - name: Start integration test dependencies run: | sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq krb5-user sudo chmod 666 /etc/krb5.conf sudo docker run -d -h ns.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -e "TEST_KDC_ADDR=${TEST_KDC_ADDR}" -e "TEST_HTTP_ADDR=${TEST_HTTP_ADDR}" -p ${DNSUTILS_OVERRIDE_NS}:53 -p ${DNSUTILS_OVERRIDE_NS}:53/udp --name dns jcmturner/gokrb5:dns sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 88:88 -p 88:88/udp -p 464:464 -p 464:464/udp --name krb5kdc jcmturner/gokrb5:kdc-centos-default sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 78:88 -p 78:88/udp --name krb5kdc-old jcmturner/gokrb5:kdc-older sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 98:88 -p 98:88/udp --name krb5kdc-latest jcmturner/gokrb5:kdc-latest sudo docker run -d -h kdc.resdom.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 188:88 -p 188:88/udp --name krb5kdc-resdom jcmturner/gokrb5:kdc-resdom sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 58:88 -p 58:88/udp --name krb5kdc-shorttickets jcmturner/gokrb5:kdc-shorttickets sudo docker run -d --add-host host.test.gokrb5:127.0.0.88 -v /etc/localtime:/etc/localtime:ro -p 80:80 -p 443:443 --name gokrb5-http jcmturner/gokrb5:http sudo sed -i 's/nameserver .*/nameserver '${DNS_IP}'/g' /etc/resolv.conf dig _kerberos._udp.TEST.GOKRB5 id: intgTestDeps - name: Run Examples run: | cd ${GITHUB_WORKFLOW} go run -tags="examples" examples/example.go id: examples - name: Tests including integration tests run: | cd ${GITHUB_WORKFLOW} go test -race ./... env: INTEGRATION: 1 TESTPRIVILEGED: 1 id: intgTests - name: Tests (32bit) run: | cd ${GITHUB_WORKFLOW} go test ./... env: GOARCH: 386 INTEGRATION: 1 TESTPRIVILEGED: 1 id: test32 gokrb5-8.4.3/.gitignore000066400000000000000000000004401427031340300147020ustar00rootroot00000000000000*.keytab *.cap *.raw # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof gokrb5-8.4.3/CONTRIBUTING.md000066400000000000000000000117621427031340300151540ustar00rootroot00000000000000# Contribution Guide to gokrb5 If you're reading this then I guess you are interested in contributing to gokrb5 which is brilliant! Thank you for your interest and taking the time to read this guide. The information below should help you successfully contribute and by following these guidelines you are expressing the respect you have for the project and other contributors. In return I will endeavour to respond promptly to issues and pull requests but please have patience as I do this in my spare time. Therefore it can take a few weeks for me to get round to some items. A variety of contribution types are welcome: * Raising bug reports. * Suggesting enhancements. * Updating and adding to documentation. * Fixing bugs. * Coding enhancements. * Extending test coverage. This project is not intended to be a support forum for general Kerberos issues and troubleshooting specific environment configurations. ## Ground Rules Above all, be respectful and considerate of others. This means: * Assume miss-communication is mistake not malice. * Be patient. Many of those contributing are doing this out of good will and in their own time. If something is really important to you, describe why and what the impact is so that others can understand and relate to it. * Provide feedback in a constructive manner. ### Code Contribution Ground Rules When contributing code please adhere to these responsibilities: * Create issues (if one does not already exist) for any changes and enhancements that you wish to make. Discuss how you intend to fix the bug or implement the enhancement to give the community a chance to comment and get us to the best solution. * Do not create any new packages unless absolutely necessary. * Do not alter the existing exported functions and constants unless absolutely necessary. This would require a major (vX.\_.\_) version update. * Only add new exported functions and constants if absolutely necessary. This would require a minor (v\_.X.\_) version update. * Keep your code platform agnostic. * Ensure that any functions added or updated are covered by tests. * Ensure tests pass. * Ensure godoc comments are created or updated as required for any new or updated code. * Ensure your contributions are formatted correctly with gofmt. The travis build will test this. * Do not use external package dependencies. As gokrb5 is designed to be a core library used in other applications it is best to avoid dependencies the project has no control over, other than the Go standard library, as issues with any dependency could have large knock on effects. * Provide useful commit messages. * Pull requests must address one issue only and keep to the scope of the issue. This makes it easier to review and merge, so your contribution will get incorporated faster this way. * Pull requests must have a message obeying this format: ``` More detailed explanatory text. This should reference the related issue. ``` This to adhere to the [git best practice](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project) and mirror the [contribution guidelines for the Go standard library](https://golang.org/doc/contribute.html). An Example: ``` update to the godoc comments for the function Blah The godoc comments to function subpkg.Blah have been updated to make it clearer as to what the function is for. ``` ### Issue Raising Ground Rules A good rule of thumb: The easier you make it for the reader of an issue to help the more help you'll get. #### Bugs When raising bugs please include the following items in your issue: * The version of gokrb5 being used (vX.Y.Z or master or branch name). * The version of Go being used (output of the ```go version``` command is handy). * Details of the environment in which you are seeing the issue. For example, what is being used as the KDC, what the krb5.conf contains, etc. * Details on how to re-create the issue. * Details on what you are experiencing that indicates the issue. * What you expected to see. * In which gokrb5 package(s) you think the issue arises from. * If the bug relates to compliance with an RFC please specify the RFC number and section you are referring to. #### Enhancements When raising enhancement requests or suggestions please include the following: * What the enhancement is or would do. * Why you need the enhancement or why you think it would be a good idea. * Any suggestions you may have on how to implement. ## Tips ### Running Tests Running the tests without any particular switches runs only the unit tests. It is recommended to run tests with the ```-race``` argument. There are integration tests that run against various other network services such as KDCs, HTTP web servers, DNS servers, etc. To run these pass ```-tags=integration``` as an argument to the go test command. There are vagrant and docker resources available to spin up these network services. See the [readme](https://github.com/jcmturner/gokrb5/blob/master/testenv/README.md) in the testenv directory for instructions. gokrb5-8.4.3/LICENSE000066400000000000000000000261351427031340300137300ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gokrb5-8.4.3/NOTICE000066400000000000000000000000671427031340300136230ustar00rootroot00000000000000gokrb5 Copyright 2016, Jonathan Turner gokrb5-8.4.3/README.md000066400000000000000000000161231427031340300141760ustar00rootroot00000000000000# gokrb5 It is recommended to use the latest version: [![Version](https://img.shields.io/github/release/jcmturner/gokrb5.svg)](https://github.com/jcmturner/gokrb5/releases) Development will be focused on the latest major version. New features will only be targeted at this version. | Versions | Dependency Management | Import Path | Usage | Godoc | Go Report Card | |----------|-----------------------|-------------|-------|-------|----------------| | [![v8](https://github.com/jcmturner/gokrb5/workflows/v8/badge.svg)](https://github.com/jcmturner/gokrb5/actions?query=workflow%3Av8) | Go modules | import "github.com/jcmturner/gokrb5/v8/{sub-package}" | [![Usage](https://img.shields.io/badge/v8-usage-blue)](https://github.com/jcmturner/gokrb5/blob/master/v8/USAGE.md) | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue)](https://pkg.go.dev/github.com/jcmturner/gokrb5/v8) | [![Go Report Card](https://goreportcard.com/badge/github.com/jcmturner/gokrb5/v8)](https://goreportcard.com/report/github.com/jcmturner/gokrb5/v8) | | [![v7](https://github.com/jcmturner/gokrb5/workflows/v7/badge.svg)](https://github.com/jcmturner/gokrb5/actions?query=workflow%3Av7) | gopkg.in | import "gopkg.in/jcmturner/gokrb5.v7/{sub-package}" | [![Usage](https://img.shields.io/badge/v7-usage-blue)](https://github.com/jcmturner/gokrb5/blob/master/USAGE.md) | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue)](https://pkg.go.dev/github.com/jcmturner/gokrb5@v7.5.0+incompatible) | [![Go Report Card](https://goreportcard.com/badge/gopkg.in/jcmturner/gokrb5.v7)](https://goreportcard.com/report/gopkg.in/jcmturner/gokrb5.v7) | #### Go Version Support ![Go version](https://img.shields.io/badge/Go-1.18-brightgreen.svg) ![Go version](https://img.shields.io/badge/Go-1.17-brightgreen.svg) ![Go version](https://img.shields.io/badge/Go-1.16-brightgreen.svg) gokrb5 may work with other versions of Go but they are not formally tested. It has been reported that gokrb5 also works with the [gollvm](https://go.googlesource.com/gollvm/) compiler but this is not formally tested. ## Features * **Pure Go** - no dependency on external libraries * No platform specific code * Server Side * HTTP handler wrapper implements SPNEGO Kerberos authentication * HTTP handler wrapper decodes Microsoft AD PAC authorization data * Client Side * Client that can authenticate to an SPNEGO Kerberos authenticated web service * Ability to change client's password * General * Kerberos libraries for custom integration * Parsing Keytab files * Parsing krb5.conf files * Parsing client credentials cache files such as `/tmp/krb5cc_$(id -u $(whoami))` #### Implemented Encryption & Checksum Types | Implementation | Encryption ID | Checksum ID | RFC | |-------|-------------|------------|------| | des3-cbc-sha1-kd | 16 | 12 | 3961 | | aes128-cts-hmac-sha1-96 | 17 | 15 | 3962 | | aes256-cts-hmac-sha1-96 | 18 | 16 | 3962 | | aes128-cts-hmac-sha256-128 | 19 | 19 | 8009 | | aes256-cts-hmac-sha384-192 | 20 | 20 | 8009 | | rc4-hmac | 23 | -138 | 4757 | The following is working/tested: * Tested against MIT KDC (1.6.3 is the oldest version tested against) and Microsoft Active Directory (Windows 2008 R2) * Tested against a KDC that supports PA-FX-FAST. * Tested against users that have pre-authentication required using PA-ENC-TIMESTAMP. * Microsoft PAC Authorization Data is processed and exposed in the HTTP request context. Available if Microsoft Active Directory is used as the KDC. ## Contributing If you are interested in contributing to gokrb5, great! Please read the [contribution guidelines](https://github.com/jcmturner/gokrb5/blob/master/CONTRIBUTING.md). --- ## References * [RFC 3244 Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols](https://tools.ietf.org/html/rfc3244) * [RFC 4120 The Kerberos Network Authentication Service (V5)](https://tools.ietf.org/html/rfc4120) * [RFC 3961 Encryption and Checksum Specifications for Kerberos 5](https://tools.ietf.org/html/rfc3961) * [RFC 3962 Advanced Encryption Standard (AES) Encryption for Kerberos 5](https://tools.ietf.org/html/rfc3962) * [RFC 4121 The Kerberos Version 5 GSS-API Mechanism](https://tools.ietf.org/html/rfc4121) * [RFC 4178 The Simple and Protected Generic Security Service Application Program Interface (GSS-API) Negotiation Mechanism](https://tools.ietf.org/html/rfc4178.html) * [RFC 4559 SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows](https://tools.ietf.org/html/rfc4559.html) * [RFC 4757 The RC4-HMAC Kerberos Encryption Types Used by Microsoft Windows](https://tools.ietf.org/html/rfc4757) * [RFC 6806 Kerberos Principal Name Canonicalization and Cross-Realm Referrals](https://tools.ietf.org/html/rfc6806.html) * [RFC 6113 A Generalized Framework for Kerberos Pre-Authentication](https://tools.ietf.org/html/rfc6113.html) * [RFC 8009 AES Encryption with HMAC-SHA2 for Kerberos 5](https://tools.ietf.org/html/rfc8009) * [IANA Assigned Kerberos Numbers](http://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml) * [HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol - Part 1](https://msdn.microsoft.com/en-us/library/ms995329.aspx) * [HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol - Part 2](https://msdn.microsoft.com/en-us/library/ms995330.aspx) * [Microsoft PAC Validation](https://blogs.msdn.microsoft.com/openspecification/2009/04/24/understanding-microsoft-kerberos-pac-validation/) * [Microsoft Kerberos Protocol Extensions](https://msdn.microsoft.com/en-us/library/cc233855.aspx) * [Windows Data Types](https://msdn.microsoft.com/en-us/library/cc230273.aspx) ### Useful Links * https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing ## Thanks * Greg Hudson from the MIT Consortium for Kerberos and Internet Trust for providing useful advice. ## Contributing Thank you for your interest in contributing to gokrb5 please read the [contribution guide](https://github.com/jcmturner/gokrb5/blob/master/CONTRIBUTING.md) as it should help you get started. ## Known Issues | Issue | Worked around? | References | |-------|-------------|------------| | The Go standard library's encoding/asn1 package cannot unmarshal into slice of asn1.RawValue | Yes | https://github.com/golang/go/issues/17321 | | The Go standard library's encoding/asn1 package cannot marshal into a GeneralString | Yes - using https://github.com/jcmturner/gofork/tree/master/encoding/asn1 | https://github.com/golang/go/issues/18832 | | The Go standard library's encoding/asn1 package cannot marshal into slice of strings and pass stringtype parameter tags to members | Yes - using https://github.com/jcmturner/gofork/tree/master/encoding/asn1 | https://github.com/golang/go/issues/18834 | | The Go standard library's encoding/asn1 package cannot marshal with application tags | Yes | | | The Go standard library's x/crypto/pbkdf2.Key function uses the int type for iteraction count limiting meaning the 4294967296 count specified in https://tools.ietf.org/html/rfc3962 section 4 cannot be met on 32bit systems | Yes - using https://github.com/jcmturner/gofork/tree/master/x/crypto/pbkdf2 | https://go-review.googlesource.com/c/crypto/+/85535 | gokrb5-8.4.3/USAGE.md000066400000000000000000000204021427031340300141000ustar00rootroot00000000000000## Version 7 Usage This usage documentation relates to version 7 only. For other versions please refer to the USAGE.md in the relevant major version sub-directory. ### Configuration The gokrb5 libraries use the same krb5.conf configuration file format as MIT Kerberos, described [here](https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html). Config instances can be created by loading from a file path or by passing a string, io.Reader or bufio.Scanner to the relevant method: ```go import "gopkg.in/jcmturner/gokrb5.v7/config" cfg, err := config.Load("/path/to/config/file") cfg, err := config.NewConfigFromString(krb5Str) //String must have appropriate newline separations cfg, err := config.NewConfigFromReader(reader) cfg, err := config.NewConfigFromScanner(scanner) ``` ### Keytab files Standard keytab files can be read from a file or from a slice of bytes: ```go import "gopkg.in/jcmturner/gokrb5.v7/keytab" ktFromFile, err := keytab.Load("/path/to/file.keytab") ktFromBytes, err := keytab.Parse(b) ``` --- ### Kerberos Client **Create** a client instance with either a password or a keytab. A configuration must also be passed. Additionally optional additional settings can be provided. ```go import "gopkg.in/jcmturner/gokrb5.v7/client" cl := client.NewClientWithPassword("username", "REALM.COM", "password", cfg) cl := client.NewClientWithKeytab("username", "REALM.COM", kt, cfg) ``` Optional settings are provided using the functions defined in the ``client/settings.go`` source file. **Login**: ```go err := cl.Login() ``` Kerberos Ticket Granting Tickets (TGT) will be automatically renewed unless the client was created from a CCache. A client can be **destroyed** with the following method: ```go cl.Destroy() ``` #### Active Directory KDC and FAST negotiation Active Directory does not commonly support FAST negotiation so you will need to disable this on the client. If this is the case you will see this error: ```KDC did not respond appropriately to FAST negotiation``` To resolve this disable PA-FX-Fast on the client before performing Login(). This is done with one of the optional client settings as shown below: ```go cl := client.NewClientWithPassword("username", "REALM.COM", "password", cfg, client.DisablePAFXFAST(true)) ``` #### Authenticate to a Service ##### HTTP SPNEGO Create the HTTP request object and then create an SPNEGO client and use this to process the request with methods that are the same as on a HTTP client. If nil is passed as the HTTP client when creating the SPNEGO client the http.DefaultClient is used. When creating the SPNEGO client pass the Service Principal Name (SPN) or auto generate the SPN from the request object by passing a null string "". ```go r, _ := http.NewRequest("GET", "http://host.test.gokrb5/index.html", nil) spnegoCl := spnego.NewClient(cl, nil, "") resp, err := spnegoCl.Do(r) ``` ##### Generic Kerberos Client To authenticate to a service a client will need to request a service ticket for a Service Principal Name (SPN) and form into an AP_REQ message along with an authenticator encrypted with the session key that was delivered from the KDC along with the service ticket. The steps below outline how to do this. * Get the service ticket and session key for the service the client is authenticating to. The following method will use the client's cache either returning a valid cached ticket, renewing a cached ticket with the KDC or requesting a new ticket from the KDC. Therefore the GetServiceTicket method can be continually used for the most efficient interaction with the KDC. ```go tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") ``` The steps after this will be specific to the application protocol but it will likely involve a client/server Authentication Protocol exchange (AP exchange). This will involve these steps: * Generate a new Authenticator and generate a sequence number and subkey: ```go auth, _ := types.NewAuthenticator(cl.Credentials.Realm, cl.Credentials.CName) etype, _ := crypto.GetEtype(key.KeyType) auth.GenerateSeqNumberAndSubKey(key.KeyType, etype.GetKeyByteSize()) ``` * Set the checksum on the authenticator The checksum is an application specific value. Set as follows: ```go auth.Cksum = types.Checksum{ CksumType: checksumIDint, Checksum: checksumBytesSlice, } ``` * Create the AP_REQ: ```go APReq, err := messages.NewAPReq(tkt, key, auth) ``` Now send the AP_REQ to the service. How this is done will be specific to the application use case. #### Changing a Client Password This feature uses the Microsoft Kerberos Password Change protocol (RFC 3244). This is implemented in Microsoft Active Directory and in MIT krb5kdc as of version 1.7. Typically the kpasswd server listens on port 464. Below is example code for how to use this feature: ```go cfg, err := config.Load("/path/to/config/file") if err != nil { panic(err.Error()) } kt, err := keytab.Load("/path/to/file.keytab") if err != nil { panic(err.Error()) } cl := client.NewClientWithKeytab("username", "REALM.COM", kt) cl.WithConfig(cfg) ok, err := cl.ChangePasswd("newpassword") if err != nil { panic(err.Error()) } if !ok { panic("failed to change password") } ``` The client kerberos config (krb5.conf) will need to have either the kpassd_server or admin_server defined in the relevant [realms] section. For example: ``` REALM.COM = { kdc = 127.0.0.1:88 kpasswd_server = 127.0.0.1:464 default_domain = realm.com } ``` See https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms for more information. --- ### Kerberised Service #### SPNEGO/Kerberos HTTP Service A HTTP handler wrapper can be used to implement Kerberos SPNEGO authentication for web services. To configure the wrapper the keytab for the SPN and a Logger are required: ```go kt, err := keytab.Load("/path/to/file.keytab") l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile) ``` Create a handler function of the application's handling method (apphandler in the example below): ```go h := http.HandlerFunc(apphandler) ``` Configure the HTTP handler: ```go http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l))) ``` The handler to be wrapped and the keytab are required arguments. Additional optional settings can be provided, such as the logger shown above. Another example of optional settings may be that when using Active Directory where the SPN is mapped to a user account the keytab may contain an entry for this user account. In this case this should be specified as below with the ``KeytabPrincipal``: ```go http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.KeytabPrincipal(pn))) ``` If authentication succeeds then the request's context will have the following values added so they can be accessed within the application's handler: * spnego.CTXKeyAuthenticated - Boolean indicating if the user is authenticated. Use of this value should also handle that this value may not be set and should assume "false" in that case. * spnego.CTXKeyCredentials - The authenticated user's credentials. If Microsoft Active Directory is used as the KDC then additional ADCredentials are available in the credentials.Attributes map under the key credentials.AttributeKeyADCredentials. For example the SIDs of the users group membership are available and can be used by your application for authorization. Access the credentials within your application: ```go ctx := r.Context() if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser { if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok { if ADCreds, ok := creds.Attributes()[credentials.AttributeKeyADCredentials].(credentials.ADCredentials); ok { // Now access the fields of the ADCredentials struct. For example: groupSids := ADCreds.GroupMembershipSIDs } } } ``` #### Generic Kerberised Service - Validating Client Details To validate the AP_REQ sent by the client on the service side call this method: ```go import "gopkg.in/jcmturner/gokrb5.v7/service" s := service.NewSettings(&kt) // kt is a keytab and optional settings can also be provided. if ok, creds, err := service.VerifyAPREQ(APReq, s); ok { // Perform application specific actions // creds object has details about the client identity } ``` gokrb5-8.4.3/asn1tools/000077500000000000000000000000001427031340300146375ustar00rootroot00000000000000gokrb5-8.4.3/asn1tools/tools.go000066400000000000000000000057011427031340300163310ustar00rootroot00000000000000// Package asn1tools provides tools for managing ASN1 marshaled data. package asn1tools import ( "github.com/jcmturner/gofork/encoding/asn1" ) // MarshalLengthBytes returns the ASN1 encoded bytes for the length 'l' // // There are two forms: short (for lengths between 0 and 127), and long definite (for lengths between 0 and 2^1008 -1). // // Short form: One octet. Bit 8 has value "0" and bits 7-1 give the length. // // Long form: Two to 127 octets. Bit 8 of first octet has value "1" and bits 7-1 give the number of additional length octets. Second and following octets give the length, base 256, most significant digit first. func MarshalLengthBytes(l int) []byte { if l <= 127 { return []byte{byte(l)} } var b []byte p := 1 for i := 1; i < 127; { b = append([]byte{byte((l % (p * 256)) / p)}, b...) p = p * 256 l = l - l%p if l <= 0 { break } } return append([]byte{byte(128 + len(b))}, b...) } // GetLengthFromASN returns the length of a slice of ASN1 encoded bytes from the ASN1 length header it contains. func GetLengthFromASN(b []byte) int { if int(b[1]) <= 127 { return int(b[1]) } // The bytes that indicate the length lb := b[2 : 2+int(b[1])-128] base := 1 l := 0 for i := len(lb) - 1; i >= 0; i-- { l += int(lb[i]) * base base = base * 256 } return l } // GetNumberBytesInLengthHeader returns the number of bytes in the ASn1 header that indicate the length. func GetNumberBytesInLengthHeader(b []byte) int { if int(b[1]) <= 127 { return 1 } // The bytes that indicate the length return 1 + int(b[1]) - 128 } // AddASNAppTag adds an ASN1 encoding application tag value to the raw bytes provided. func AddASNAppTag(b []byte, tag int) []byte { r := asn1.RawValue{ Class: asn1.ClassApplication, IsCompound: true, Tag: tag, Bytes: b, } ab, _ := asn1.Marshal(r) return ab } /* // The Marshal method of golang's asn1 package does not enable you to define wrapping the output in an application tag. // This method adds that wrapping tag. func AddASNAppTag(b []byte, tag int) []byte { // The ASN1 wrapping consists of 2 bytes: // 1st byte -> Identifier Octet - Application Tag // 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here. // Application Tag: //| Bit: | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | //| Value: | 0 | 1 | 1 | From the RFC spec 4120 | //| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value | // Therefore the value of the byte is an integer = ( Application tag value + 96 ) //b = append(MarshalLengthBytes(int(b[1])+2), b...) b = append(MarshalLengthBytes(len(b)), b...) b = append([]byte{byte(96 + tag)}, b...) return b } */ gokrb5-8.4.3/client/000077500000000000000000000000001427031340300141725ustar00rootroot00000000000000gokrb5-8.4.3/client/ASExchange.go000066400000000000000000000156711427031340300165010ustar00rootroot00000000000000package client import ( "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) // ASExchange performs an AS exchange for the client to retrieve a TGT. func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) { if ok, err := cl.IsConfigured(); !ok { return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed") } // Set PAData if required err := setPAData(cl, nil, &ASReq) if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ") } b, err := ASReq.Marshal() if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ") } var ASRep messages.ASRep rb, err := cl.sendToKDC(b, realm) if err != nil { if e, ok := err.(messages.KRBError); ok { switch e.ErrorCode { case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED: // From now on assume this client will need to do this pre-auth and set the PAData cl.settings.assumePreAuthentication = true err = setPAData(cl, &e, &ASReq) if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required") } b, err := ASReq.Marshal() if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData") } rb, err = cl.sendToKDC(b, realm) if err != nil { if _, ok := err.(messages.KRBError); ok { return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC") } return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC") } case errorcode.KDC_ERR_WRONG_REALM: // Client referral https://tools.ietf.org/html/rfc6806.html#section-7 if referral > 5 { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded") } referral++ return cl.ASExchange(e.CRealm, ASReq, referral) default: return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC") } } else { return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC") } } err = ASRep.Unmarshal(rb) if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP") } if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect") } return ASRep, nil } // setPAData adds pre-authentication data to the AS_REQ. func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error { if !cl.settings.DisablePAFXFAST() { pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP} ASReq.PAData = append(ASReq.PAData, pa) } if cl.settings.AssumePreAuthentication() { // Identify the etype to use to encrypt the PA Data var et etype.EType var err error var key types.EncryptionKey if krberr == nil { // This is not in response to an error from the KDC. It is preemptive or renewal // There is no KRB Error that tells us the etype to use etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated if etn == 0 { etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config } et, err = crypto.GetEtype(etn) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption") } key, err = cl.Key(et, nil) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials") } } else { // Get the etype to use from the PA data in the KRBError e-data et, err = preAuthEType(krberr) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption") } cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use key, err = cl.Key(et, krberr) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials") } } // Generate the PA data paTSb, err := types.GetPAEncTSEncAsnMarshalled() if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication") } //TODO (theme: KVNO from keytab) the kvno should not be hard coded to 1 as this hampers troubleshooting. paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, 1) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp") } pb, err := paEncTS.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data") } pa := types.PAData{ PADataType: patype.PA_ENC_TIMESTAMP, PADataValue: pb, } // Look for and delete any exiting patype.PA_ENC_TIMESTAMP for i, pa := range ASReq.PAData { if pa.PADataType == patype.PA_ENC_TIMESTAMP { ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1] ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1] } } ASReq.PAData = append(ASReq.PAData, pa) } return nil } // preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC. func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) { //RFC 4120 5.2.7.5 covers the preference order of ETYPE-INFO2 and ETYPE-INFO. var etypeID int32 var pas types.PADataSequence e := pas.Unmarshal(krberr.EData) if e != nil { err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data") return } for _, pa := range pas { switch pa.PADataType { case patype.PA_ETYPE_INFO2: info, e := pa.GetETypeInfo2() if e != nil { err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data") return } etypeID = info[0].EType break case patype.PA_ETYPE_INFO: info, e := pa.GetETypeInfo() if e != nil { err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data") return } etypeID = info[0].EType } } etype, e = crypto.GetEtype(etypeID) if e != nil { err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype") return } return etype, nil } gokrb5-8.4.3/client/TGSExchange.go000066400000000000000000000114301427031340300166200ustar00rootroot00000000000000package client import ( "gopkg.in/jcmturner/gokrb5.v7/iana/flags" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) // TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN. func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) { tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal) if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ") } return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0) } // TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP. // Referrals are automatically handled. // The client's cache is updated with the ticket received. func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) { var tgsRep messages.TGSRep b, err := tgsReq.Marshal() if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ") } r, err := cl.sendToKDC(b, kdcRealm) if err != nil { if _, ok := err.(messages.KRBError); ok { return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString()) } return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC") } err = tgsRep.Unmarshal(r) if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP") } err = tgsRep.DecryptEncPart(sessionKey) if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP") } if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid") } if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) { if referral > 5 { return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded") } // Server referral https://tools.ietf.org/html/rfc6806.html#section-8 // The TGS Rep contains a TGT for another domain as the service resides in that domain. cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart) realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1] referral++ if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 { tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0]) if err != nil { return tgsReq, tgsRep, err } } tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal) if err != nil { return tgsReq, tgsRep, err } return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral) } cl.cache.addEntry( tgsRep.Ticket, tgsRep.DecryptedEncPart.AuthTime, tgsRep.DecryptedEncPart.StartTime, tgsRep.DecryptedEncPart.EndTime, tgsRep.DecryptedEncPart.RenewTill, tgsRep.DecryptedEncPart.Key, ) cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime) return tgsReq, tgsRep, err } // GetServiceTicket makes a request to get a service ticket for the SPN specified // SPN format: / Eg. HTTP/www.example.com // The ticket will be added to the client's ticket cache func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) { var tkt messages.Ticket var skey types.EncryptionKey if tkt, skey, ok := cl.GetCachedTicket(spn); ok { // Already a valid ticket in the cache return tkt, skey, nil } princ := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) realm := cl.Config.ResolveRealm(princ.NameString[len(princ.NameString)-1]) tgt, skey, err := cl.sessionTGT(realm) if err != nil { return tkt, skey, err } _, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false) if err != nil { return tkt, skey, err } return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil } gokrb5-8.4.3/client/cache.go000066400000000000000000000055451427031340300155750ustar00rootroot00000000000000package client import ( "errors" "sync" "time" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) // Cache for service tickets held by the client. type Cache struct { Entries map[string]CacheEntry mux sync.RWMutex } // CacheEntry holds details for a cache entry. type CacheEntry struct { Ticket messages.Ticket AuthTime time.Time StartTime time.Time EndTime time.Time RenewTill time.Time SessionKey types.EncryptionKey } // NewCache creates a new client ticket cache instance. func NewCache() *Cache { return &Cache{ Entries: map[string]CacheEntry{}, } } // getEntry returns a cache entry that matches the SPN. func (c *Cache) getEntry(spn string) (CacheEntry, bool) { c.mux.RLock() defer c.mux.RUnlock() e, ok := (*c).Entries[spn] return e, ok } // addEntry adds a ticket to the cache. func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry { spn := tkt.SName.PrincipalNameString() c.mux.Lock() defer c.mux.Unlock() (*c).Entries[spn] = CacheEntry{ Ticket: tkt, AuthTime: authTime, StartTime: startTime, EndTime: endTime, RenewTill: renewTill, SessionKey: sessionKey, } return c.Entries[spn] } // clear deletes all the cache entries func (c *Cache) clear() { c.mux.Lock() defer c.mux.Unlock() for k := range c.Entries { delete(c.Entries, k) } } // RemoveEntry removes the cache entry for the defined SPN. func (c *Cache) RemoveEntry(spn string) { c.mux.Lock() defer c.mux.Unlock() delete(c.Entries, spn) } // GetCachedTicket returns a ticket from the cache for the SPN. // Only a ticket that is currently valid will be returned. func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) { if e, ok := cl.cache.getEntry(spn); ok { //If within time window of ticket return it if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) { cl.Log("ticket received from cache for %s", spn) return e.Ticket, e.SessionKey, true } else if time.Now().UTC().Before(e.RenewTill) { e, err := cl.renewTicket(e) if err != nil { return e.Ticket, e.SessionKey, false } return e.Ticket, e.SessionKey, true } } var tkt messages.Ticket var key types.EncryptionKey return tkt, key, false } // renewTicket renews a cache entry ticket. // To renew from outside the client package use GetCachedTicket func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) { spn := e.Ticket.SName _, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true) if err != nil { return e, err } e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString()) if !ok { return e, errors.New("ticket was not added to cache") } cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime) return e, nil } gokrb5-8.4.3/client/client.go000066400000000000000000000202551427031340300160030ustar00rootroot00000000000000// Package client provides a client library and methods for Kerberos 5 authentication. package client import ( "errors" "fmt" "time" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) // Client side configuration and state. type Client struct { Credentials *credentials.Credentials Config *config.Config settings *Settings sessions *sessions cache *Cache } // NewClientWithPassword creates a new client from a password credential. // Set the realm to empty string to use the default realm from config. func NewClientWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client { creds := credentials.New(username, realm) return &Client{ Credentials: creds.WithPassword(password), Config: krb5conf, settings: NewSettings(settings...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } } // NewClientWithKeytab creates a new client from a keytab credential. func NewClientWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client { creds := credentials.New(username, realm) return &Client{ Credentials: creds.WithKeytab(kt), Config: krb5conf, settings: NewSettings(settings...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } } // NewClientFromCCache create a client from a populated client cache. // // WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires. func NewClientFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) { cl := &Client{ Credentials: c.GetClientCredentials(), Config: krb5conf, settings: NewSettings(settings...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } spn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", c.DefaultPrincipal.Realm}, } cred, ok := c.GetEntry(spn) if !ok { return cl, errors.New("TGT not found in CCache") } var tgt messages.Ticket err := tgt.Unmarshal(cred.Ticket) if err != nil { return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err) } cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{ realm: c.DefaultPrincipal.Realm, authTime: cred.AuthTime, endTime: cred.EndTime, renewTill: cred.RenewTill, tgt: tgt, sessionKey: cred.Key, } for _, cred := range c.GetEntries() { var tkt messages.Ticket err = tkt.Unmarshal(cred.Ticket) if err != nil { return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err) } cl.cache.addEntry( tkt, cred.AuthTime, cred.StartTime, cred.EndTime, cred.RenewTill, cred.Key, ) } return cl, nil } // Key returns the client's encryption key for the specified encryption type. // The key can be retrieved either from the keytab or generated from the client's password. // If the client has both a keytab and a password defined the keytab is favoured as the source for the key // A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive // the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument. func (cl *Client) Key(etype etype.EType, krberr *messages.KRBError) (types.EncryptionKey, error) { if cl.Credentials.HasKeytab() && etype != nil { return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), 0, etype.GetETypeID()) } else if cl.Credentials.HasPassword() { if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED { var pas types.PADataSequence err := pas.Unmarshal(krberr.EData) if err != nil { return types.EncryptionKey{}, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err) } key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas) return key, err } key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{}) return key, err } return types.EncryptionKey{}, errors.New("credential has neither keytab or password to generate key") } // IsConfigured indicates if the client has the values required set. func (cl *Client) IsConfigured() (bool, error) { if cl.Credentials.UserName() == "" { return false, errors.New("client does not have a username") } if cl.Credentials.Domain() == "" { return false, errors.New("client does not have a define realm") } // Client needs to have either a password, keytab or a session already (later when loading from CCache) if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() { authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil || authTime.IsZero() { return false, errors.New("client has neither a keytab nor a password set and no session") } } if !cl.Config.LibDefaults.DNSLookupKDC { for _, r := range cl.Config.Realms { if r.Realm == cl.Credentials.Domain() { if len(r.KDC) > 0 { return true, nil } return false, errors.New("client krb5 config does not have any defined KDCs for the default realm") } } } return true, nil } // Login the client with the KDC via an AS exchange. func (cl *Client) Login() error { if ok, err := cl.IsConfigured(); !ok { return err } if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() { _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session") } if time.Now().UTC().After(endTime) { return krberror.NewKrberror(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session") } // no credentials but there is a session with tgt already return nil } ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName()) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ") } ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0) if err != nil { return err } cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart) return nil } // AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT. func (cl *Client) AffirmLogin() error { _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil || time.Now().UTC().After(endTime) { err := cl.Login() if err != nil { return fmt.Errorf("could not get valid TGT for client's realm: %v", err) } } return nil } // realmLogin obtains or renews a TGT and establishes a session for the realm specified. func (cl *Client) realmLogin(realm string) error { if realm == cl.Credentials.Domain() { return cl.Login() } _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil || time.Now().UTC().After(endTime) { err := cl.Login() if err != nil { return fmt.Errorf("could not get valid TGT for client's realm: %v", err) } } tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain()) if err != nil { return err } spn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } _, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false) if err != nil { return err } cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart) return nil } // Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client. func (cl *Client) Destroy() { creds := credentials.New("", "") cl.sessions.destroy() cl.cache.clear() cl.Credentials = creds cl.Log("client destroyed") } gokrb5-8.4.3/client/client_ad_integration_test.go000066400000000000000000000124531427031340300221120ustar00rootroot00000000000000package client import ( "bytes" "encoding/hex" "log" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" "testing" ) func TestClient_SuccessfulLogin_AD(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.KRB5_CONF_AD) cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } } func TestClient_GetServiceTicket_AD(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.KRB5_CONF_AD) cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } spn := "HTTP/user2.user.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("Error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_USER_GOKRB5) skt := keytab.New() skt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser2"}} err = tkt.DecryptEncPart(skt, &sname) if err != nil { t.Errorf("could not decrypt service ticket: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(skt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "should have PAC") assert.Equal(t, "USER", pac.KerbValidationInfo.LogonDomainName.String(), "domain name in PAC not correct") } func TestClient_GetServiceTicket_AD_TRUST_USER_DOMAIN(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.KRB5_CONF_AD) c.LibDefaults.DefaultRealm = "USER.GOKRB5" c.LibDefaults.Canonicalize = true c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true)) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } spn := "HTTP/host.res.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("Error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_SYSHTTP_RES_GOKRB5) skt := keytab.New() skt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}} err = tkt.DecryptEncPart(skt, &sname) if err != nil { t.Errorf("error decrypting ticket with service keytab: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(skt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "Did not find PAC in service ticket") assert.Equal(t, "testuser1", pac.KerbValidationInfo.EffectiveName.Value, "PAC value not parsed") } func TestClient_GetServiceTicket_AD_USER_DOMAIN(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.KRB5_CONF_AD) c.LibDefaults.Canonicalize = true c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true)) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } spn := "HTTP/user2.user.gokrb5" tkt, _, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("Error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) //assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_USER_GOKRB5) skt := keytab.New() skt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser2"}} err = tkt.DecryptEncPart(skt, &sname) if err != nil { t.Errorf("error decrypting ticket with service keytab: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(skt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "Did not find PAC in service ticket") assert.Equal(t, "testuser1", pac.KerbValidationInfo.EffectiveName.Value, "PAC value not parsed") } gokrb5-8.4.3/client/client_dns_test.go000066400000000000000000000015571427031340300177120ustar00rootroot00000000000000package client import ( "encoding/hex" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "testing" ) func TestClient_Login_DNSKDCs(t *testing.T) { test.Privileged(t) //ns := os.Getenv("DNSUTILS_OVERRIDE_NS") //if ns == "" { // os.Setenv("DNSUTILS_OVERRIDE_NS", testdata.TEST_NS) //} c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) // Set to lookup KDCs in DNS c.LibDefaults.DNSLookupKDC = true //Blank out the KDCs to ensure they are not being used c.Realms = []config.Realm{} b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on logging in using DNS lookup of KDCs: %v\n", err) } } gokrb5-8.4.3/client/client_integration_test.go000066400000000000000000000470261427031340300214520ustar00rootroot00000000000000package client_test import ( "bytes" "encoding/hex" "errors" "io" "net/http" "os" "os/exec" "os/user" "runtime" "testing" "time" "fmt" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/spnego" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "strings" "sync" ) func TestClient_SuccessfulLogin_Keytab(t *testing.T) { test.Integration(t) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) var tests = []string{ testdata.TEST_KDC, testdata.TEST_KDC_OLD, testdata.TEST_KDC_LASTEST, } for _, tst := range tests { c.Realms[0].KDC = []string{addr + ":" + tst} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on logging in with KDC %s: %v\n", tst, err) } } } func TestClient_SuccessfulLogin_Password(t *testing.T) { test.Integration(t) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) var tests = []string{ testdata.TEST_KDC, testdata.TEST_KDC_OLD, testdata.TEST_KDC_LASTEST, } for _, tst := range tests { c.Realms[0].KDC = []string{addr + ":" + tst} cl := client.NewClientWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue", c) err := cl.Login() if err != nil { t.Errorf("error on logging in with KDC %s: %v\n", tst, err) } } } func TestClient_SuccessfulLogin_TCPOnly(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} c.LibDefaults.UDPPreferenceLimit = 1 cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } } func TestClient_ASExchange_TGSExchange_EncTypes_Keytab(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_LASTEST} var tests = []string{ "des3-cbc-sha1-kd", "aes128-cts-hmac-sha1-96", "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha256-128", "aes256-cts-hmac-sha384-192", "rc4-hmac", } for _, tst := range tests { c.LibDefaults.DefaultTktEnctypes = []string{tst} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[tst]} c.LibDefaults.DefaultTGSEnctypes = []string{tst} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[tst]} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on login using enctype %s: %v\n", tst, err) } tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") if err != nil { t.Errorf("error in TGS exchange using enctype %s: %v", tst, err) } assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", tst) assert.Equal(t, etypeID.ETypesByName[tst], key.KeyType, "Key is not for enctype %s", tst) } } func TestClient_ASExchange_TGSExchange_EncTypes_Password(t *testing.T) { test.Integration(t) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_LASTEST} var tests = []string{ "des3-cbc-sha1-kd", "aes128-cts-hmac-sha1-96", "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha256-128", "aes256-cts-hmac-sha384-192", "rc4-hmac", } for _, tst := range tests { c.LibDefaults.DefaultTktEnctypes = []string{tst} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[tst]} c.LibDefaults.DefaultTGSEnctypes = []string{tst} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[tst]} cl := client.NewClientWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue", c) err := cl.Login() if err != nil { t.Errorf("error on login using enctype %s: %v\n", tst, err) } tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") if err != nil { t.Errorf("error in TGS exchange using enctype %s: %v", tst, err) } assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", tst) assert.Equal(t, etypeID.ETypesByName[tst], key.KeyType, "Key is not for enctype %s", tst) } } func TestClient_FailedLogin(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_WRONGPASSWD) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err == nil { t.Fatal("login with incorrect password did not error") } } func TestClient_SuccessfulLogin_UserRequiringPreAuth(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl := client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } } func TestClient_SuccessfulLogin_UserRequiringPreAuth_TCPOnly(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} c.LibDefaults.UDPPreferenceLimit = 1 cl := client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } } func TestClient_NetworkTimeout(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) c.Realms[0].KDC = []string{testdata.TEST_KDC_BADADDR + ":88"} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err == nil { t.Fatal("login with incorrect KDC address did not error") } } func TestClient_GetServiceTicket(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) //Check cache use - should get the same values back again tkt2, key2, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, tkt.EncPart.Cipher, tkt2.EncPart.Cipher) assert.Equal(t, key.KeyValue, key2.KeyValue) } func TestClient_GetServiceTicket_InvalidSPN(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "host.test.gokrb5" _, _, err = cl.GetServiceTicket(spn) assert.NotNil(t, err, "Expected unknown principal error") assert.True(t, strings.Contains(err.Error(), "KDC_ERR_S_PRINCIPAL_UNKNOWN"), "Error text not as expected") } func TestClient_GetServiceTicket_OlderKDC(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_OLD} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) } func TestMultiThreadedClientUse(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go func() { defer wg.Done() err := cl.Login() if err != nil { panic(err) } }() } wg.Wait() var wg2 sync.WaitGroup wg2.Add(5) for i := 0; i < 5; i++ { go func() { defer wg2.Done() err := spnegoGet(cl) if err != nil { panic(err) } }() } wg2.Wait() } func spnegoGet(cl *client.Client) error { url := os.Getenv("TEST_HTTP_URL") if url == "" { url = testdata.TEST_HTTP_URL } r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil) httpResp, err := http.DefaultClient.Do(r) if err != nil { return fmt.Errorf("request error: %v\n", err) } if httpResp.StatusCode != http.StatusUnauthorized { return errors.New("did not get unauthorized code when no SPNEGO header set") } err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { return fmt.Errorf("error setting client SPNEGO header: %v", err) } httpResp, err = http.DefaultClient.Do(r) if err != nil { return fmt.Errorf("request error: %v\n", err) } if httpResp.StatusCode != http.StatusOK { return errors.New("did not get OK code when SPNEGO header set") } return nil } func TestNewClientFromCCache(t *testing.T) { test.Integration(t) b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatalf("error decoding test data") } cc := new(credentials.CCache) err = cc.Unmarshal(b) if err != nil { t.Fatal("error getting test CCache") } c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl, err := client.NewClientFromCCache(cc, c) if err != nil { t.Fatalf("error creating client from CCache: %v", err) } if ok, err := cl.IsConfigured(); !ok { t.Fatalf("client was not configured from CCache: %v", err) } } // Login to the TEST.GOKRB5 domain and request service ticket for resource in the RESDOM.GOKRB5 domain. // There is a trust between the two domains. func TestClient_GetServiceTicket_Trusted_Resource_Domain(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } for i, r := range c.Realms { if r.Realm == "TEST.GOKRB5" { c.Realms[i].KDC = []string{addr + ":" + testdata.TEST_KDC} } if r.Realm == "RESDOM.GOKRB5" { c.Realms[i].KDC = []string{addr + ":" + testdata.TEST_KDC_RESDOM} } } c.LibDefaults.DefaultRealm = "TEST.GOKRB5" cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) c.LibDefaults.DefaultTktEnctypes = []string{"aes256-cts-hmac-sha1-96"} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["aes256-cts-hmac-sha1-96"]} c.LibDefaults.DefaultTGSEnctypes = []string{"aes256-cts-hmac-sha1-96"} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["aes256-cts-hmac-sha1-96"]} err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.resdom.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], key.KeyType) b, _ = hex.DecodeString(testdata.SYSHTTP_RESDOM_KEYTAB) skt := keytab.New() skt.Unmarshal(b) err = tkt.DecryptEncPart(skt, nil) if err != nil { t.Errorf("error decrypting ticket with service keytab: %v", err) } } const ( kinitCmd = "kinit" kvnoCmd = "kvno" spn = "HTTP/host.test.gokrb5" ) func login() error { file, err := os.Create("/etc/krb5.conf") if err != nil { return fmt.Errorf("cannot open krb5.conf: %v", err) } defer file.Close() fmt.Fprintf(file, testdata.TEST_KRB5CONF) cmd := exec.Command(kinitCmd, "testuser1@TEST.GOKRB5") stdinR, stdinW := io.Pipe() stderrR, stderrW := io.Pipe() cmd.Stdin = stdinR cmd.Stderr = stderrW err = cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kinitCmd, err) } go func() { io.WriteString(stdinW, "passwordvalue") stdinW.Close() }() errBuf := new(bytes.Buffer) go func() { io.Copy(errBuf, stderrR) stderrR.Close() }() err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v stderr: %s", kinitCmd, err, string(errBuf.Bytes())) } return nil } func getServiceTkt() error { cmd := exec.Command(kvnoCmd, spn) err := cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kvnoCmd, err) } err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v", kvnoCmd, err) } return nil } func loadCCache() (*credentials.CCache, error) { usr, _ := user.Current() cpath := "/tmp/krb5cc_" + usr.Uid return credentials.LoadCCache(cpath) } func TestGetServiceTicketFromCCacheTGT(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } cfg, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } cfg.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl, err := client.NewClientFromCCache(c, cfg) if err != nil { t.Fatalf("error generating client from ccache: %v", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) //Check cache use - should get the same values back again tkt2, key2, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, tkt.EncPart.Cipher, tkt2.EncPart.Cipher) assert.Equal(t, key.KeyValue, key2.KeyValue) url := os.Getenv("TEST_HTTP_URL") if url == "" { url = testdata.TEST_HTTP_URL } r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "status code in response to client SPNEGO request not as expected") } func TestGetServiceTicketFromCCacheWithoutKDC(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } err = getServiceTkt() if err != nil { t.Fatalf("error getting service ticket: %v", err) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } cfg, _ := config.NewConfigFromString("...") cl, err := client.NewClientFromCCache(c, cfg) if err != nil { t.Fatalf("error generating client from ccache: %v", err) } url := os.Getenv("TEST_HTTP_URL") if url == "" { url = testdata.TEST_HTTP_URL } r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "status code in response to client SPNEGO request not as expected") } func TestClient_ChangePasswd(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} c.Realms[0].KPasswdServer = []string{addr + ":464"} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) ok, err := cl.ChangePasswd("newpassword") if err != nil { t.Fatalf("error changing password: %v", err) } assert.True(t, ok, "password was not changed") cl = client.NewClientWithPassword("testuser1", "TEST.GOKRB5", "newpassword", c) ok, err = cl.ChangePasswd(testdata.TESTUSER1_PASSWORD) if err != nil { t.Fatalf("error changing password: %v", err) } assert.True(t, ok, "password was not changed back") cl = client.NewClientWithPassword("testuser1", "TEST.GOKRB5", testdata.TESTUSER1_PASSWORD, c) err = cl.Login() if err != nil { t.Fatalf("Could not log back in after reverting password: %v", err) } } func TestClient_Destroy(t *testing.T) { test.Integration(t) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_SHORTTICKETS} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" _, _, err = cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } n := runtime.NumGoroutine() time.Sleep(time.Second * 60) cl.Destroy() time.Sleep(time.Second * 5) assert.True(t, runtime.NumGoroutine() < n, "auto-renewal goroutine was not stopped when client destroyed") is, _ := cl.IsConfigured() assert.False(t, is, "client is still configured after it was destroyed") } gokrb5-8.4.3/client/client_test.go000066400000000000000000000007631427031340300170440ustar00rootroot00000000000000package client import ( "testing" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/keytab" ) func TestAssumePreauthentication(t *testing.T) { t.Parallel() cl := NewClientWithKeytab("username", "REALM", &keytab.Keytab{}, &config.Config{}, AssumePreAuthentication(true)) if !cl.settings.assumePreAuthentication { t.Fatal("assumePreAuthentication should be true") } if !cl.settings.AssumePreAuthentication() { t.Fatal("AssumePreAuthentication() should be true") } } gokrb5-8.4.3/client/network.go000066400000000000000000000132671427031340300162230ustar00rootroot00000000000000package client import ( "bytes" "encoding/binary" "errors" "fmt" "io" "net" "time" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/messages" ) // SendToKDC performs network actions to send data to the KDC. func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) { var rb []byte if cl.Config.LibDefaults.UDPPreferenceLimit == 1 { //1 means we should always use TCP rb, errtcp := cl.sendKDCTCP(realm, b) if errtcp != nil { if e, ok := errtcp.(messages.KRBError); ok { return rb, e } return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp) } return rb, nil } if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit { //Try UDP first, TCP second rb, errudp := cl.sendKDCUDP(realm, b) if errudp != nil { if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG { // Got a KRBError from KDC // If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP. return rb, e } // Try TCP r, errtcp := cl.sendKDCTCP(realm, b) if errtcp != nil { if e, ok := errtcp.(messages.KRBError); ok { // Got a KRBError return r, e } return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp) } rb = r } return rb, nil } //Try TCP first, UDP second rb, errtcp := cl.sendKDCTCP(realm, b) if errtcp != nil { if e, ok := errtcp.(messages.KRBError); ok { // Got a KRBError from KDC so returning and not trying UDP. return rb, e } rb, errudp := cl.sendKDCUDP(realm, b) if errudp != nil { if e, ok := errudp.(messages.KRBError); ok { // Got a KRBError return rb, e } return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp) } } return rb, nil } // dialKDCTCP establishes a UDP connection to a KDC. func dialKDCUDP(count int, kdcs map[int]string) (*net.UDPConn, error) { i := 1 for i <= count { udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i]) if err != nil { return nil, fmt.Errorf("error resolving KDC address: %v", err) } conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second) if err == nil { if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { return nil, err } // conn is guaranteed to be a UDPConn return conn.(*net.UDPConn), nil } i++ } return nil, errors.New("error in getting a UDP connection to any of the KDCs") } // dialKDCTCP establishes a TCP connection to a KDC. func dialKDCTCP(count int, kdcs map[int]string) (*net.TCPConn, error) { i := 1 for i <= count { tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i]) if err != nil { return nil, fmt.Errorf("error resolving KDC address: %v", err) } conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second) if err == nil { if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { return nil, err } // conn is guaranteed to be a TCPConn return conn.(*net.TCPConn), nil } i++ } return nil, errors.New("error in getting a TCP connection to any of the KDCs") } // sendKDCUDP sends bytes to the KDC via UDP. func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) { var r []byte count, kdcs, err := cl.Config.GetKDCs(realm, false) if err != nil { return r, err } conn, err := dialKDCUDP(count, kdcs) if err != nil { return r, err } r, err = cl.sendUDP(conn, b) if err != nil { return r, err } return checkForKRBError(r) } // sendKDCTCP sends bytes to the KDC via TCP. func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) { var r []byte count, kdcs, err := cl.Config.GetKDCs(realm, true) if err != nil { return r, err } conn, err := dialKDCTCP(count, kdcs) if err != nil { return r, err } rb, err := cl.sendTCP(conn, b) if err != nil { return r, err } return checkForKRBError(rb) } // sendUDP sends bytes to connection over UDP. func (cl *Client) sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) { var r []byte defer conn.Close() _, err := conn.Write(b) if err != nil { return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err) } udpbuf := make([]byte, 4096) n, _, err := conn.ReadFrom(udpbuf) r = udpbuf[:n] if err != nil { return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err) } if len(r) < 1 { return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String()) } return r, nil } // sendTCP sends bytes to connection over TCP. func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) { defer conn.Close() var r []byte // RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order. var buf bytes.Buffer err := binary.Write(&buf, binary.BigEndian, uint32(len(b))) if err != nil { return r, err } b = append(buf.Bytes(), b...) _, err = conn.Write(b) if err != nil { return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err) } sh := make([]byte, 4, 4) _, err = conn.Read(sh) if err != nil { return r, fmt.Errorf("error reading response size header: %v", err) } s := binary.BigEndian.Uint32(sh) rb := make([]byte, s, s) _, err = io.ReadFull(conn, rb) if err != nil { return r, fmt.Errorf("error reading response: %v", err) } if len(rb) < 1 { return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String()) } return rb, nil } // checkForKRBError checks if the response bytes from the KDC are a KRBError. func checkForKRBError(b []byte) ([]byte, error) { var KRBErr messages.KRBError if err := KRBErr.Unmarshal(b); err == nil { return b, KRBErr } return b, nil } gokrb5-8.4.3/client/passwd.go000066400000000000000000000047061427031340300160310ustar00rootroot00000000000000package client import ( "fmt" "net" "gopkg.in/jcmturner/gokrb5.v7/kadmin" "gopkg.in/jcmturner/gokrb5.v7/messages" ) // Kpasswd server response codes. const ( KRB5_KPASSWD_SUCCESS = 0 KRB5_KPASSWD_MALFORMED = 1 KRB5_KPASSWD_HARDERROR = 2 KRB5_KPASSWD_AUTHERROR = 3 KRB5_KPASSWD_SOFTERROR = 4 KRB5_KPASSWD_ACCESSDENIED = 5 KRB5_KPASSWD_BAD_VERSION = 6 KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7 ) // ChangePasswd changes the password of the client to the value provided. func (cl *Client) ChangePasswd(newPasswd string) (bool, error) { ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName()) if err != nil { return false, err } ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0) if err != nil { return false, err } msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key) if err != nil { return false, err } r, err := cl.sendToKPasswd(msg) if err != nil { return false, err } err = r.Decrypt(key) if err != nil { return false, err } if r.ResultCode != KRB5_KPASSWD_SUCCESS { return false, fmt.Errorf("error response from kadmin: code: %d; result: %s; krberror: %v", r.ResultCode, r.Result, r.KRBError) } cl.Credentials.WithPassword(newPasswd) return true, nil } func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) { _, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true) if err != nil { return } addr := kps[1] b, err := msg.Marshal() if err != nil { return } if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit { return cl.sendKPasswdUDP(b, addr) } return cl.sendKPasswdTCP(b, addr) } func (cl *Client) sendKPasswdTCP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) { tcpAddr, err := net.ResolveTCPAddr("tcp", kadmindAddr) if err != nil { return } conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil { return } rb, err := cl.sendTCP(conn, b) if err != nil { return } err = r.Unmarshal(rb) return } func (cl *Client) sendKPasswdUDP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) { udpAddr, err := net.ResolveUDPAddr("udp", kadmindAddr) if err != nil { return } conn, err := net.DialUDP("udp", nil, udpAddr) if err != nil { return } rb, err := cl.sendUDP(conn, b) if err != nil { return } err = r.Unmarshal(rb) return } gokrb5-8.4.3/client/session.go000066400000000000000000000157701427031340300162160ustar00rootroot00000000000000package client import ( "fmt" "strings" "sync" "time" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) // sessions hold TGTs and are keyed on the realm name type sessions struct { Entries map[string]*session mux sync.RWMutex } // destroy erases all sessions func (s *sessions) destroy() { s.mux.Lock() defer s.mux.Unlock() for k, e := range s.Entries { e.destroy() delete(s.Entries, k) } } // update replaces a session with the one provided or adds it as a new one func (s *sessions) update(sess *session) { s.mux.Lock() defer s.mux.Unlock() // if a session already exists for this, cancel its auto renew. if i, ok := s.Entries[sess.realm]; ok { if i != sess { // Session in the sessions cache is not the same as one provided. // Cancel the one in the cache and add this one. i.mux.Lock() defer i.mux.Unlock() i.cancel <- true s.Entries[sess.realm] = sess return } } // No session for this realm was found so just add it s.Entries[sess.realm] = sess } // get returns the session for the realm specified func (s *sessions) get(realm string) (*session, bool) { s.mux.RLock() defer s.mux.RUnlock() sess, ok := s.Entries[realm] return sess, ok } // session holds the TGT details for a realm type session struct { realm string authTime time.Time endTime time.Time renewTill time.Time tgt messages.Ticket sessionKey types.EncryptionKey sessionKeyExpiration time.Time cancel chan bool mux sync.RWMutex } // AddSession adds a session for a realm with a TGT to the client's session cache. // A goroutine is started to automatically renew the TGT before expiry. func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) { if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" { // Not a TGT return } realm := tgt.SName.NameString[len(tgt.SName.NameString)-1] s := &session{ realm: realm, authTime: dep.AuthTime, endTime: dep.EndTime, renewTill: dep.RenewTill, tgt: tgt, sessionKey: dep.Key, sessionKeyExpiration: dep.KeyExpiration, } cl.sessions.update(s) cl.enableAutoSessionRenewal(s) cl.Log("TGT session added for %s (EndTime: %v)", realm, dep.EndTime) } // update overwrites the session details with those from the TGT and decrypted encPart func (s *session) update(tgt messages.Ticket, dep messages.EncKDCRepPart) { s.mux.Lock() defer s.mux.Unlock() s.authTime = dep.AuthTime s.endTime = dep.EndTime s.renewTill = dep.RenewTill s.tgt = tgt s.sessionKey = dep.Key s.sessionKeyExpiration = dep.KeyExpiration } // destroy will cancel any auto renewal of the session and set the expiration times to the current time func (s *session) destroy() { s.mux.Lock() defer s.mux.Unlock() if s.cancel != nil { s.cancel <- true } s.endTime = time.Now().UTC() s.renewTill = s.endTime s.sessionKeyExpiration = s.endTime } // valid informs if the TGT is still within the valid time window func (s *session) valid() bool { s.mux.RLock() defer s.mux.RUnlock() t := time.Now().UTC() if t.Before(s.endTime) && s.authTime.Before(t) { return true } return false } // tgtDetails is a thread safe way to get the session's realm, TGT and session key values func (s *session) tgtDetails() (string, messages.Ticket, types.EncryptionKey) { s.mux.RLock() defer s.mux.RUnlock() return s.realm, s.tgt, s.sessionKey } // timeDetails is a thread safe way to get the session's validity time values func (s *session) timeDetails() (string, time.Time, time.Time, time.Time, time.Time) { s.mux.RLock() defer s.mux.RUnlock() return s.realm, s.authTime, s.endTime, s.renewTill, s.sessionKeyExpiration } // enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session. func (cl *Client) enableAutoSessionRenewal(s *session) { var timer *time.Timer s.mux.Lock() s.cancel = make(chan bool, 1) s.mux.Unlock() go func(s *session) { for { s.mux.RLock() w := (s.endTime.Sub(time.Now().UTC()) * 5) / 6 s.mux.RUnlock() if w < 0 { return } timer = time.NewTimer(w) select { case <-timer.C: renewal, err := cl.refreshSession(s) if err != nil { cl.Log("error refreshing session: %v", err) } if !renewal && err == nil { // end this goroutine as there will have been a new login and new auto renewal goroutine created. return } case <-s.cancel: // cancel has been called. Stop the timer and exit. timer.Stop() return } } }(s) } // renewTGT renews the client's TGT session. func (cl *Client) renewTGT(s *session) error { realm, tgt, skey := s.tgtDetails() spn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } _, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT for %s", realm) } s.update(tgsRep.Ticket, tgsRep.DecryptedEncPart) cl.sessions.update(s) cl.Log("TGT session renewed for %s (EndTime: %v)", realm, tgsRep.DecryptedEncPart.EndTime) return nil } // refreshSession updates either through renewal or creating a new login. // The boolean indicates if the update was a renewal. func (cl *Client) refreshSession(s *session) (bool, error) { s.mux.RLock() realm := s.realm renewTill := s.renewTill s.mux.RUnlock() cl.Log("refreshing TGT session for %s", realm) if time.Now().UTC().Before(renewTill) { err := cl.renewTGT(s) return true, err } err := cl.realmLogin(realm) return false, err } // ensureValidSession makes sure there is a valid session for the realm func (cl *Client) ensureValidSession(realm string) error { s, ok := cl.sessions.get(realm) if ok { s.mux.RLock() d := s.endTime.Sub(s.authTime) / 6 if s.endTime.Sub(time.Now().UTC()) > d { s.mux.RUnlock() return nil } s.mux.RUnlock() _, err := cl.refreshSession(s) return err } return cl.realmLogin(realm) } // sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error) { err = cl.ensureValidSession(realm) if err != nil { return } s, ok := cl.sessions.get(realm) if !ok { err = fmt.Errorf("could not find TGT session for %s", realm) return } _, tgt, sessionKey = s.tgtDetails() return } func (cl *Client) sessionTimes(realm string) (authTime, endTime, renewTime, sessionExp time.Time, err error) { s, ok := cl.sessions.get(realm) if !ok { err = fmt.Errorf("could not find TGT session for %s", realm) return } _, authTime, endTime, renewTime, sessionExp = s.timeDetails() return } // spnRealm resolves the realm name of a service principal name func (cl *Client) spnRealm(spn types.PrincipalName) string { return cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1]) } gokrb5-8.4.3/client/session_test.go000066400000000000000000000060731427031340300172510ustar00rootroot00000000000000package client import ( "encoding/hex" "fmt" "io/ioutil" "os" "runtime" "sync" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestMultiThreadedClientSession(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("failed to log in: %v", err) } s, ok := cl.sessions.get("TEST.GOKRB5") if !ok { t.Fatal("error initially getting session") } go func() { for { err := cl.renewTGT(s) if err != nil { t.Logf("error renewing TGT: %v", err) } time.Sleep(time.Millisecond * 100) } }() var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { go func() { defer wg.Done() tgt, _, err := cl.sessionTGT("TEST.GOKRB5") if err != nil || tgt.Realm != "TEST.GOKRB5" { t.Logf("error getting session: %v", err) } _, _, _, r, _ := cl.sessionTimes("TEST.GOKRB5") fmt.Fprintf(ioutil.Discard, "%v", r) }() time.Sleep(time.Second) } wg.Wait() } func TestClient_AutoRenew_Goroutine(t *testing.T) { test.Integration(t) // Tests that the auto renew of client credentials is not spawning goroutines out of control. addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_SHORTTICKETS} c.LibDefaults.PreferredPreauthTypes = []int{int(etypeID.DES3_CBC_SHA1_KD)} // a preauth etype the KDC does not support. Test this does not cause renewal to fail. cl := NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on logging in: %v\n", err) } n := runtime.NumGoroutine() for i := 0; i < 24; i++ { time.Sleep(time.Second * 5) _, endTime, _, _, err := cl.sessionTimes("TEST.GOKRB5") if err != nil { t.Errorf("could not get client's session: %v", err) } if time.Now().UTC().After(endTime) { t.Fatalf("session auto update failed") } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) skt := keytab.New() skt.Unmarshal(b) tkt.DecryptEncPart(skt, nil) assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) if runtime.NumGoroutine() > n { t.Fatalf("number of goroutines is increasing: should not be more than %d, is %d", n, runtime.NumGoroutine()) } } } gokrb5-8.4.3/client/settings.go000066400000000000000000000033261427031340300163650ustar00rootroot00000000000000package client import "log" // Settings holds optional client settings. type Settings struct { disablePAFXFast bool assumePreAuthentication bool preAuthEType int32 logger *log.Logger } // NewSettings creates a new client settings struct. func NewSettings(settings ...func(*Settings)) *Settings { s := new(Settings) for _, set := range settings { set(s) } return s } // DisablePAFXFAST used to configure the client to not use PA_FX_FAST. // // s := NewSettings(DisablePAFXFAST(true)) func DisablePAFXFAST(b bool) func(*Settings) { return func(s *Settings) { s.disablePAFXFast = b } } // DisablePAFXFAST indicates is the client should disable the use of PA_FX_FAST. func (s *Settings) DisablePAFXFAST() bool { return s.disablePAFXFast } // AssumePreAuthentication used to configure the client to assume pre-authentication is required. // // s := NewSettings(AssumePreAuthentication(true)) func AssumePreAuthentication(b bool) func(*Settings) { return func(s *Settings) { s.assumePreAuthentication = b } } // AssumePreAuthentication indicates if the client should proactively assume using pre-authentication. func (s *Settings) AssumePreAuthentication() bool { return s.assumePreAuthentication } // Logger used to configure client with a logger. // // s := NewSettings(kt, Logger(l)) func Logger(l *log.Logger) func(*Settings) { return func(s *Settings) { s.logger = l } } // Logger returns the client logger instance. func (s *Settings) Logger() *log.Logger { return s.logger } // Log will write to the service's logger if it is configured. func (cl *Client) Log(format string, v ...interface{}) { if cl.settings.Logger() != nil { cl.settings.Logger().Printf(format, v...) } } gokrb5-8.4.3/config/000077500000000000000000000000001427031340300141615ustar00rootroot00000000000000gokrb5-8.4.3/config/error.go000066400000000000000000000011461427031340300156430ustar00rootroot00000000000000package config import "fmt" // UnsupportedDirective error. type UnsupportedDirective struct { text string } // Error implements the error interface for unsupported directives. func (e UnsupportedDirective) Error() string { return e.text } // Invalid config error. type Invalid struct { text string } // Error implements the error interface for invalid config error. func (e Invalid) Error() string { return e.text } // InvalidErrorf creates a new Invalid error. func InvalidErrorf(format string, a ...interface{}) Invalid { return Invalid{ text: fmt.Sprintf("invalid krb5 config "+format, a...), } } gokrb5-8.4.3/config/hosts.go000066400000000000000000000064651427031340300156630ustar00rootroot00000000000000package config import ( "fmt" "math/rand" "net" "strconv" "strings" "gopkg.in/jcmturner/dnsutils.v1" ) // GetKDCs returns the count of KDCs available and a map of KDC host names keyed on preference order. func (c *Config) GetKDCs(realm string, tcp bool) (int, map[int]string, error) { if realm == "" { realm = c.LibDefaults.DefaultRealm } kdcs := make(map[int]string) var count int // Get the KDCs from the krb5.conf. var ks []string for _, r := range c.Realms { if r.Realm != realm { continue } ks = r.KDC } count = len(ks) if count > 0 { // Order the kdcs randomly for preference. kdcs = randServOrder(ks) return count, kdcs, nil } if !c.LibDefaults.DNSLookupKDC { return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm) } // Use DNS to resolve kerberos SRV records. proto := "udp" if tcp { proto = "tcp" } index, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm) if err != nil { return count, kdcs, err } if len(addrs) < 1 { return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm) } count = index for k, v := range addrs { kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port)) } return count, kdcs, nil } // GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order. // https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section func (c *Config) GetKpasswdServers(realm string, tcp bool) (int, map[int]string, error) { kdcs := make(map[int]string) var count int // Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf. if c.LibDefaults.DNSLookupKDC { proto := "udp" if tcp { proto = "tcp" } c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm) if err != nil { return count, kdcs, err } if c < 1 { c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm) if err != nil { return count, kdcs, err } } if len(addrs) < 1 { return count, kdcs, fmt.Errorf("no kpasswd or kadmin SRV records found for realm %s", realm) } count = c for k, v := range addrs { kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port)) } } else { // Get the KDCs from the krb5.conf an order them randomly for preference. var ks []string var ka []string for _, r := range c.Realms { if r.Realm == realm { ks = r.KPasswdServer ka = r.AdminServer break } } if len(ks) < 1 { for _, k := range ka { h, _, err := net.SplitHostPort(k) if err != nil { continue } ks = append(ks, h+":464") } } count = len(ks) if count < 1 { return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm) } kdcs = randServOrder(ks) } return count, kdcs, nil } func randServOrder(ks []string) map[int]string { kdcs := make(map[int]string) count := len(ks) i := 1 if count > 1 { l := len(ks) for l > 0 { ri := rand.Intn(l) kdcs[i] = ks[ri] if l > 1 { // Remove the entry from the source slice by swapping with the last entry and truncating ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1] ks = ks[:len(ks)-1] l = len(ks) } else { l = 0 } i++ } } else { kdcs[i] = ks[0] } return kdcs } gokrb5-8.4.3/config/hosts_test.go000066400000000000000000000044441427031340300167150ustar00rootroot00000000000000package config import ( "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestConfig_GetKDCsUsesConfiguredKDC(t *testing.T) { t.Parallel() // This test is meant to cover the fix for // https://github.com/jcmturner/gokrb5/issues/332 krb5ConfWithKDCAndDNSLookupKDC := ` [libdefaults] dns_lookup_kdc = true [realms] TEST.GOKRB5 = { kdc = kdc2b.test.gokrb5:88 } ` c, err := NewConfigFromString(krb5ConfWithKDCAndDNSLookupKDC) if err != nil { t.Fatalf("Error loading config: %v", err) } count, kdcs, err := c.GetKDCs("TEST.GOKRB5", false) if err != nil { t.Fatal(err) } if count != 1 { t.Fatalf("expected 1 but received %d", count) } if kdcs[1] != "kdc2b.test.gokrb5:88" { t.Fatalf("expected kdc2b.test.gokrb5:88 but received %s", kdcs[1]) } } func TestResolveKDC(t *testing.T) { test.Privileged(t) c, err := NewConfigFromString(testdata.TEST_KRB5CONF) if err != nil { t.Fatal(err) } // Store the original value for realms since we'll use them in our // second test. originalRealms := c.Realms // For our first test, let's check that we discover the expected // KDCs when they're not provided and we should be looking them up. c.LibDefaults.DNSLookupKDC = true c.Realms = make([]Realm, 0) count, res, err := c.GetKDCs(c.LibDefaults.DefaultRealm, true) if err != nil { t.Errorf("error resolving KDC via DNS TCP: %v", err) } assert.Equal(t, 5, count, "Number of SRV records not as expected: %v", res) assert.Equal(t, count, len(res), "Map size does not match: %v", res) expected := []string{ "kdc.test.gokrb5:88", "kdc1a.test.gokrb5:88", "kdc2a.test.gokrb5:88", "kdc1b.test.gokrb5:88", "kdc2b.test.gokrb5:88", } for _, s := range expected { var found bool for _, v := range res { if s == v { found = true break } } assert.True(t, found, "Record %s not found in results", s) } // For our second check, verify that when we shouldn't be looking them up, // we get the expected value. c.LibDefaults.DNSLookupKDC = false c.Realms = originalRealms _, res, err = c.GetKDCs(c.LibDefaults.DefaultRealm, true) if err != nil { t.Errorf("error resolving KDCs from config: %v", err) } assert.Equal(t, "127.0.0.1:88", res[1], "KDC not read from config as expected") } gokrb5-8.4.3/config/krb5conf.go000066400000000000000000000543621427031340300162330ustar00rootroot00000000000000// Package config implements KRB5 client and service configuration as described at https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html package config import ( "bufio" "encoding/hex" "errors" "fmt" "io" "net" "os" "os/user" "regexp" "strconv" "strings" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // Config represents the KRB5 configuration. type Config struct { LibDefaults LibDefaults Realms []Realm DomainRealm DomainRealm //CaPaths //AppDefaults //Plugins } // WeakETypeList is a list of encryption types that have been deemed weak. const WeakETypeList = "des-cbc-crc des-cbc-md4 des-cbc-md5 des-cbc-raw des3-cbc-raw des-hmac-sha1 arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp des" // NewConfig creates a new config struct instance. func NewConfig() *Config { d := make(DomainRealm) return &Config{ LibDefaults: newLibDefaults(), DomainRealm: d, } } // LibDefaults represents the [libdefaults] section of the configuration. type LibDefaults struct { AllowWeakCrypto bool //default false // ap_req_checksum_type int //unlikely to support this Canonicalize bool //default false CCacheType int //default is 4. unlikely to implement older Clockskew time.Duration //max allowed skew in seconds, default 300 //Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab DefaultKeytabName string //default /etc/krb5.keytab DefaultRealm string DefaultTGSEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DefaultTktEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DefaultTGSEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DefaultTktEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DNSCanonicalizeHostname bool //default true DNSLookupKDC bool //default false DNSLookupRealm bool ExtraAddresses []net.IP //Not implementing yet Forwardable bool //default false IgnoreAcceptorHostname bool //default false K5LoginAuthoritative bool //default false K5LoginDirectory string //default user's home directory. Must be owned by the user or root KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK) KDCTimeSync int //default 1 //kdc_req_checksum_type int //unlikely to implement as for very old KDCs NoAddresses bool //default true PermittedEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 PermittedEnctypeIDs []int32 //plugin_base_dir string //not supporting plugins PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported Proxiable bool //default false RDNS bool //default true RealmTryDomains int //default -1 RenewLifetime time.Duration //default 0 SafeChecksumType int //default 8 TicketLifetime time.Duration //default 1 day UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700. VerifyAPReqNofail bool //default false } // Create a new LibDefaults struct. func newLibDefaults() LibDefaults { uid := "0" var hdir string usr, _ := user.Current() if usr != nil { uid = usr.Uid hdir = usr.HomeDir } opts := asn1.BitString{} opts.Bytes, _ = hex.DecodeString("00000010") opts.BitLength = len(opts.Bytes) * 8 return LibDefaults{ CCacheType: 4, Clockskew: time.Duration(300) * time.Second, DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid), DefaultKeytabName: "/etc/krb5.keytab", DefaultTGSEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, DefaultTktEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, DNSCanonicalizeHostname: true, K5LoginDirectory: hdir, KDCDefaultOptions: opts, KDCTimeSync: 1, NoAddresses: true, PermittedEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, RDNS: true, RealmTryDomains: -1, SafeChecksumType: 8, TicketLifetime: time.Duration(24) * time.Hour, UDPPreferenceLimit: 1465, PreferredPreauthTypes: []int{17, 16, 15, 14}, } } // Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct. func (l *LibDefaults) parseLines(lines []string) error { for _, line := range lines { //Remove comments after the values if idx := strings.IndexAny(line, "#;"); idx != -1 { line = line[:idx] } line = strings.TrimSpace(line) if line == "" { continue } if !strings.Contains(line, "=") { return InvalidErrorf("libdefaults section line (%s)", line) } p := strings.Split(line, "=") key := strings.TrimSpace(strings.ToLower(p[0])) switch key { case "allow_weak_crypto": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.AllowWeakCrypto = v case "canonicalize": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Canonicalize = v case "ccache_type": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseUint(p[1], 10, 32) if err != nil || v < 0 || v > 4 { return InvalidErrorf("libdefaults section line (%s)", line) } l.CCacheType = int(v) case "clockskew": d, err := parseDuration(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Clockskew = d case "default_client_keytab_name": l.DefaultClientKeytabName = strings.TrimSpace(p[1]) case "default_keytab_name": l.DefaultKeytabName = strings.TrimSpace(p[1]) case "default_realm": l.DefaultRealm = strings.TrimSpace(p[1]) case "default_tgs_enctypes": l.DefaultTGSEnctypes = strings.Fields(p[1]) case "default_tkt_enctypes": l.DefaultTktEnctypes = strings.Fields(p[1]) case "dns_canonicalize_hostname": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.DNSCanonicalizeHostname = v case "dns_lookup_kdc": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.DNSLookupKDC = v case "dns_lookup_realm": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.DNSLookupRealm = v case "extra_addresses": ipStr := strings.TrimSpace(p[1]) for _, ip := range strings.Split(ipStr, ",") { if eip := net.ParseIP(ip); eip != nil { l.ExtraAddresses = append(l.ExtraAddresses, eip) } } case "forwardable": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Forwardable = v case "ignore_acceptor_hostname": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.IgnoreAcceptorHostname = v case "k5login_authoritative": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.K5LoginAuthoritative = v case "k5login_directory": l.K5LoginDirectory = strings.TrimSpace(p[1]) case "kdc_default_options": v := strings.TrimSpace(p[1]) v = strings.Replace(v, "0x", "", -1) b, err := hex.DecodeString(v) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.KDCDefaultOptions.Bytes = b l.KDCDefaultOptions.BitLength = len(b) * 8 case "kdc_timesync": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseInt(p[1], 10, 32) if err != nil || v < 0 { return InvalidErrorf("libdefaults section line (%s)", line) } l.KDCTimeSync = int(v) case "noaddresses": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.NoAddresses = v case "permitted_enctypes": l.PermittedEnctypes = strings.Fields(p[1]) case "preferred_preauth_types": p[1] = strings.TrimSpace(p[1]) t := strings.Split(p[1], ",") var v []int for _, s := range t { i, err := strconv.ParseInt(s, 10, 32) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } v = append(v, int(i)) } l.PreferredPreauthTypes = v case "proxiable": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Proxiable = v case "rdns": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.RDNS = v case "realm_try_domains": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseInt(p[1], 10, 32) if err != nil || v < -1 { return InvalidErrorf("libdefaults section line (%s)", line) } l.RealmTryDomains = int(v) case "renew_lifetime": d, err := parseDuration(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.RenewLifetime = d case "safe_checksum_type": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseInt(p[1], 10, 32) if err != nil || v < 0 { return InvalidErrorf("libdefaults section line (%s)", line) } l.SafeChecksumType = int(v) case "ticket_lifetime": d, err := parseDuration(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.TicketLifetime = d case "udp_preference_limit": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseUint(p[1], 10, 32) if err != nil || v > 32700 { return InvalidErrorf("libdefaults section line (%s)", line) } l.UDPPreferenceLimit = int(v) case "verify_ap_req_nofail": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.VerifyAPReqNofail = v default: //Ignore the line continue } } l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto) l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto) l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto) return nil } // Realm represents an entry in the [realms] section of the configuration. type Realm struct { Realm string AdminServer []string //auth_to_local //Not implementing for now //auth_to_local_names //Not implementing for now DefaultDomain string KDC []string KPasswdServer []string //default admin_server:464 MasterKDC []string } // Parse the lines of a [realms] entry into the Realm struct. func (r *Realm) parseLines(name string, lines []string) (err error) { r.Realm = name var adminServerFinal bool var KDCFinal bool var kpasswdServerFinal bool var masterKDCFinal bool var ignore bool var c int // counts the depth of blocks within brackets { } for _, line := range lines { if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") { continue } //Remove comments after the values if idx := strings.IndexAny(line, "#;"); idx != -1 { line = line[:idx] } line = strings.TrimSpace(line) if line == "" { continue } if !strings.Contains(line, "=") && !strings.Contains(line, "}") { return InvalidErrorf("realms section line (%s)", line) } if strings.Contains(line, "v4_") { ignore = true err = UnsupportedDirective{"v4 configurations are not supported"} } if strings.Contains(line, "{") { c++ if ignore { continue } } if strings.Contains(line, "}") { c-- if c < 0 { return InvalidErrorf("unpaired curly brackets") } if ignore { if c < 1 { c = 0 ignore = false } continue } } p := strings.Split(line, "=") key := strings.TrimSpace(strings.ToLower(p[0])) v := strings.TrimSpace(p[1]) switch key { case "admin_server": appendUntilFinal(&r.AdminServer, v, &adminServerFinal) case "default_domain": r.DefaultDomain = v case "kdc": if !strings.Contains(v, ":") { // No port number specified default to 88 if strings.HasSuffix(v, `*`) { v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*" } else { v = strings.TrimSpace(v) + ":88" } } appendUntilFinal(&r.KDC, v, &KDCFinal) case "kpasswd_server": appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal) case "master_kdc": appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal) default: //Ignore the line continue } } //default for Kpasswd_server = admin_server:464 if len(r.KPasswdServer) < 1 { for _, a := range r.AdminServer { s := strings.Split(a, ":") r.KPasswdServer = append(r.KPasswdServer, s[0]+":464") } } return } // Parse the lines of the [realms] section of the configuration into an slice of Realm structs. func parseRealms(lines []string) (realms []Realm, err error) { var name string var start int var c int for i, l := range lines { //Remove comments after the values if idx := strings.IndexAny(l, "#;"); idx != -1 { l = l[:idx] } l = strings.TrimSpace(l) if l == "" { continue } //if strings.Contains(l, "v4_") { // return nil, errors.New("v4 configurations are not supported in Realms section") //} if strings.Contains(l, "{") { c++ if !strings.Contains(l, "=") { return nil, fmt.Errorf("realm configuration line invalid: %s", l) } if c == 1 { start = i p := strings.Split(l, "=") name = strings.TrimSpace(p[0]) } } if strings.Contains(l, "}") { if c < 1 { // but not started a block!!! return nil, errors.New("invalid Realms section in configuration") } c-- if c == 0 { var r Realm e := r.parseLines(name, lines[start+1:i]) if e != nil { if _, ok := e.(UnsupportedDirective); !ok { err = e return } err = e } realms = append(realms, r) } } } return } // DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration. type DomainRealm map[string]string // Parse the lines of the [domain_realm] section of the configuration and add to the mapping. func (d *DomainRealm) parseLines(lines []string) error { for _, line := range lines { //Remove comments after the values if idx := strings.IndexAny(line, "#;"); idx != -1 { line = line[:idx] } if strings.TrimSpace(line) == "" { continue } if !strings.Contains(line, "=") { return InvalidErrorf("realm line (%s)", line) } p := strings.Split(line, "=") domain := strings.TrimSpace(strings.ToLower(p[0])) realm := strings.TrimSpace(p[1]) d.addMapping(domain, realm) } return nil } // Add a domain to realm mapping. func (d *DomainRealm) addMapping(domain, realm string) { (*d)[domain] = realm } // Delete a domain to realm mapping. func (d *DomainRealm) deleteMapping(domain, realm string) { delete(*d, domain) } // ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping. // The most specific mapping is returned. func (c *Config) ResolveRealm(domainName string) string { domainName = strings.TrimSuffix(domainName, ".") // Try to match the entire hostname first if r, ok := c.DomainRealm[domainName]; ok { return r } // Try to match all DNS domain parts periods := strings.Count(domainName, ".") + 1 for i := 2; i <= periods; i++ { z := strings.SplitN(domainName, ".", i) if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok { return r } } return c.LibDefaults.DefaultRealm } // Load the KRB5 configuration from the specified file path. func Load(cfgPath string) (*Config, error) { fh, err := os.Open(cfgPath) if err != nil { return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error()) } defer fh.Close() scanner := bufio.NewScanner(fh) return NewConfigFromScanner(scanner) } // NewConfigFromString creates a new Config struct from a string. func NewConfigFromString(s string) (*Config, error) { reader := strings.NewReader(s) return NewConfigFromReader(reader) } // NewConfigFromReader creates a new Config struct from an io.Reader. func NewConfigFromReader(r io.Reader) (*Config, error) { scanner := bufio.NewScanner(r) return NewConfigFromScanner(scanner) } // NewConfigFromScanner creates a new Config struct from a bufio.Scanner. func NewConfigFromScanner(scanner *bufio.Scanner) (*Config, error) { c := NewConfig() var e error sections := make(map[int]string) var sectionLineNum []int var lines []string for scanner.Scan() { // Skip comments and blank lines if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched { continue } if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched { sections[len(lines)] = "libdefaults" sectionLineNum = append(sectionLineNum, len(lines)) continue } if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched { sections[len(lines)] = "realms" sectionLineNum = append(sectionLineNum, len(lines)) continue } if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched { sections[len(lines)] = "domain_realm" sectionLineNum = append(sectionLineNum, len(lines)) continue } if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched { sections[len(lines)] = "unknown_section" sectionLineNum = append(sectionLineNum, len(lines)) continue } lines = append(lines, scanner.Text()) } for i, start := range sectionLineNum { var end int if i+1 >= len(sectionLineNum) { end = len(lines) } else { end = sectionLineNum[i+1] } switch section := sections[start]; section { case "libdefaults": err := c.LibDefaults.parseLines(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { return nil, fmt.Errorf("error processing libdefaults section: %v", err) } e = err } case "realms": realms, err := parseRealms(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { return nil, fmt.Errorf("error processing realms section: %v", err) } e = err } c.Realms = realms case "domain_realm": err := c.DomainRealm.parseLines(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { return nil, fmt.Errorf("error processing domaain_realm section: %v", err) } e = err } default: continue } } return c, e } // Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes. func parseETypes(s []string, w bool) []int32 { var eti []int32 for _, et := range s { if !w { var weak bool for _, wet := range strings.Fields(WeakETypeList) { if et == wet { weak = true break } } if weak { continue } } i := etypeID.EtypeSupported(et) if i != 0 { eti = append(eti, i) } } return eti } // Parse a time duration string in the configuration to a golang time.Duration. func parseDuration(s string) (time.Duration, error) { s = strings.Replace(strings.TrimSpace(s), " ", "", -1) // handle Nd[NmNs] if strings.Contains(s, "d") { ds := strings.SplitN(s, "d", 2) dn, err := strconv.ParseUint(ds[0], 10, 32) if err != nil { return time.Duration(0), errors.New("invalid time duration") } d := time.Duration(dn*24) * time.Hour if ds[1] != "" { dp, err := time.ParseDuration(ds[1]) if err != nil { return time.Duration(0), errors.New("invalid time duration") } d = d + dp } return d, nil } // handle Nm[Ns] d, err := time.ParseDuration(s) if err == nil { return d, nil } // handle N v, err := strconv.ParseUint(s, 10, 32) if err == nil && v > 0 { return time.Duration(v) * time.Second, nil } // handle h:m[:s] if strings.Contains(s, ":") { t := strings.Split(s, ":") if 2 > len(t) || len(t) > 3 { return time.Duration(0), errors.New("invalid time duration value") } var i []int for _, n := range t { j, err := strconv.ParseInt(n, 10, 16) if err != nil { return time.Duration(0), errors.New("invalid time duration value") } i = append(i, int(j)) } d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute if len(i) == 3 { d = d + time.Duration(i[2])*time.Second } return d, nil } return time.Duration(0), errors.New("invalid time duration value") } // Parse possible boolean values to golang bool. func parseBoolean(s string) (bool, error) { s = strings.TrimSpace(s) v, err := strconv.ParseBool(s) if err == nil { return v, nil } switch strings.ToLower(s) { case "yes": return true, nil case "y": return true, nil case "no": return false, nil case "n": return false, nil } return false, errors.New("invalid boolean value") } // Parse array of strings but stop if an asterisk is placed at the end of a line. func appendUntilFinal(s *[]string, value string, final *bool) { if *final { return } if last := len(value) - 1; last >= 0 && value[last] == '*' { *final = true value = value[:len(value)-1] } *s = append(*s, value) } gokrb5-8.4.3/config/krb5conf_test.go000066400000000000000000000470531427031340300172710ustar00rootroot00000000000000package config import ( "io/ioutil" "os" "testing" "time" "github.com/stretchr/testify/assert" ) const ( krb5Conf = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 ; comment to be ignored dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h ;comment to be ignored forwardable = yes #comment to be ignored default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 # comment to be ignored [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 #comment to be ignored kdc = assume.port.num ;comment to be ignored kdc = some.other.port:1234 # comment to be ignored kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 ; comment to be ignored default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } lowercase.org = { kdc = kerberos.lowercase.org admin_server = kerberos.lowercase.org } [domain_realm] .test.gokrb5 = TEST.GOKRB5 #comment to be ignored test.gokrb5 = TEST.GOKRB5 ;comment to be ignored .example.com = EXAMPLE.COM # comment to be ignored hostname1.example.com = EXAMPLE.COM ; comment to be ignored hostname2.example.com = TEST.GOKRB5 .testlowercase.org = lowercase.org [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } ` krb5Conf2 = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] noaddresses = true default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com } ` krb5ConfNoBlankLines = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 ` krb5ConfTabs = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 .example.com = EXAMPLE.COM hostname1.example.com = EXAMPLE.COM hostname2.example.com = TEST.GOKRB5 [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false }` krb5ConfV4Lines = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 v4_name_convert = { host = { rcmd = host } } } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 .example.com = EXAMPLE.COM hostname1.example.com = EXAMPLE.COM hostname2.example.com = TEST.GOKRB5 [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } ` ) func TestLoad(t *testing.T) { t.Parallel() cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5.conf") defer os.Remove(cf.Name()) cf.WriteString(krb5Conf) c, err := Load(cf.Name()) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 3, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestLoadWithV4Lines(t *testing.T) { t.Parallel() cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5.conf") defer os.Remove(cf.Name()) cf.WriteString(krb5ConfV4Lines) c, err := Load(cf.Name()) if err == nil { t.Fatalf("error should not be nil for config that includes v4 lines") } if _, ok := err.(UnsupportedDirective); !ok { t.Fatalf("error should be of type UnsupportedDirective: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestLoad2(t *testing.T) { t.Parallel() c, err := NewConfigFromString(krb5Conf2) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") assert.True(t, c.LibDefaults.NoAddresses, "No address not set as true") } func TestLoadNoBlankLines(t *testing.T) { t.Parallel() c, err := NewConfigFromString(krb5ConfNoBlankLines) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestLoadTabs(t *testing.T) { t.Parallel() cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5.conf") defer os.Remove(cf.Name()) cf.WriteString(krb5ConfTabs) c, err := Load(cf.Name()) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestParseDuration(t *testing.T) { t.Parallel() // https://web.mit.edu/kerberos/krb5-1.12/doc/basic/date_format.html#duration hms, _ := time.ParseDuration("12h30m15s") hm, _ := time.ParseDuration("12h30m") h, _ := time.ParseDuration("12h") var tests = []struct { timeStr string duration time.Duration }{ {"100", time.Duration(100) * time.Second}, {"12:30", hm}, {"12:30:15", hms}, {"1d12h30m15s", time.Duration(24)*time.Hour + hms}, {"1d12h30m", time.Duration(24)*time.Hour + hm}, {"1d12h", time.Duration(24)*time.Hour + h}, {"1d", time.Duration(24) * time.Hour}, } for _, test := range tests { d, err := parseDuration(test.timeStr) if err != nil { t.Errorf("error parsing %s: %v", test.timeStr, err) } assert.Equal(t, test.duration, d, "Duration not as expected for: "+test.timeStr) } } func TestResolveRealm(t *testing.T) { t.Parallel() c, err := NewConfigFromString(krb5Conf) if err != nil { t.Fatalf("Error loading config: %v", err) } tests := []struct { domainName string want string }{ {"unknown.com", "TEST.GOKRB5"}, {"hostname1.example.com", "EXAMPLE.COM"}, {"hostname2.example.com", "TEST.GOKRB5"}, {"one.two.three.example.com", "EXAMPLE.COM"}, {".test.gokrb5", "TEST.GOKRB5"}, {"foo.testlowercase.org", "lowercase.org"}, } for _, tt := range tests { t.Run(tt.domainName, func(t *testing.T) { if got := c.ResolveRealm(tt.domainName); got != tt.want { t.Errorf("config.ResolveRealm() = %v, want %v", got, tt.want) } }) } } gokrb5-8.4.3/credentials/000077500000000000000000000000001427031340300152115ustar00rootroot00000000000000gokrb5-8.4.3/credentials/ccache.go000066400000000000000000000212341427031340300167500ustar00rootroot00000000000000package credentials import ( "bytes" "encoding/binary" "errors" "io/ioutil" "strings" "time" "unsafe" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( headerFieldTagKDCOffset = 1 ) // CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html type CCache struct { Version uint8 Header header DefaultPrincipal principal Credentials []*Credential Path string } type header struct { length uint16 fields []headerField } type headerField struct { tag uint16 length uint16 value []byte } // Credential cache entry principal struct. type principal struct { Realm string PrincipalName types.PrincipalName } // Credential holds a Kerberos client's ccache credential information. type Credential struct { Client principal Server principal Key types.EncryptionKey AuthTime time.Time StartTime time.Time EndTime time.Time RenewTill time.Time IsSKey bool TicketFlags asn1.BitString Addresses []types.HostAddress AuthData []types.AuthorizationDataEntry Ticket []byte SecondTicket []byte } // LoadCCache loads a credential cache file into a CCache type. func LoadCCache(cpath string) (*CCache, error) { c := new(CCache) b, err := ioutil.ReadFile(cpath) if err != nil { return c, err } err = c.Unmarshal(b) return c, err } // Unmarshal a byte slice of credential cache data into CCache type. func (c *CCache) Unmarshal(b []byte) error { p := 0 //The first byte of the file always has the value 5 if int8(b[p]) != 5 { return errors.New("Invalid credential cache data. First byte does not equal 5") } p++ //Get credential cache version //The second byte contains the version number (1 to 4) c.Version = b[p] if c.Version < 1 || c.Version > 4 { return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4") } p++ //Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order var endian binary.ByteOrder endian = binary.BigEndian if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() { endian = binary.LittleEndian } if c.Version == 4 { err := parseHeader(b, &p, c, &endian) if err != nil { return err } } c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian) for p < len(b) { cred, err := parseCredential(b, &p, c, &endian) if err != nil { return err } c.Credentials = append(c.Credentials, cred) } return nil } func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error { if c.Version != 4 { return errors.New("Credentials cache version is not 4 so there is no header to parse.") } h := header{} h.length = uint16(readInt16(b, p, e)) for *p <= int(h.length) { f := headerField{} f.tag = uint16(readInt16(b, p, e)) f.length = uint16(readInt16(b, p, e)) f.value = b[*p : *p+int(f.length)] *p += int(f.length) if !f.valid() { return errors.New("Invalid credential cache header found") } h.fields = append(h.fields, f) } c.Header = h return nil } // Parse the Keytab bytes of a principal into a Keytab entry's principal. func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ principal) { if c.Version != 1 { //Name Type is omitted in version 1 princ.PrincipalName.NameType = readInt32(b, p, e) } nc := int(readInt32(b, p, e)) if c.Version == 1 { //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2 nc-- } lenRealm := readInt32(b, p, e) princ.Realm = string(readBytes(b, p, int(lenRealm), e)) for i := 0; i < nc; i++ { l := readInt32(b, p, e) princ.PrincipalName.NameString = append(princ.PrincipalName.NameString, string(readBytes(b, p, int(l), e))) } return princ } func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) { cred = new(Credential) cred.Client = parsePrincipal(b, p, c, e) cred.Server = parsePrincipal(b, p, c, e) key := types.EncryptionKey{} key.KeyType = int32(readInt16(b, p, e)) if c.Version == 3 { //repeated twice in version 3 key.KeyType = int32(readInt16(b, p, e)) } key.KeyValue = readData(b, p, e) cred.Key = key cred.AuthTime = readTimestamp(b, p, e) cred.StartTime = readTimestamp(b, p, e) cred.EndTime = readTimestamp(b, p, e) cred.RenewTill = readTimestamp(b, p, e) if ik := readInt8(b, p, e); ik == 0 { cred.IsSKey = false } else { cred.IsSKey = true } cred.TicketFlags = types.NewKrbFlags() cred.TicketFlags.Bytes = readBytes(b, p, 4, e) l := int(readInt32(b, p, e)) cred.Addresses = make([]types.HostAddress, l, l) for i := range cred.Addresses { cred.Addresses[i] = readAddress(b, p, e) } l = int(readInt32(b, p, e)) cred.AuthData = make([]types.AuthorizationDataEntry, l, l) for i := range cred.AuthData { cred.AuthData[i] = readAuthDataEntry(b, p, e) } cred.Ticket = readData(b, p, e) cred.SecondTicket = readData(b, p, e) return } // GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for. func (c *CCache) GetClientPrincipalName() types.PrincipalName { return c.DefaultPrincipal.PrincipalName } // GetClientRealm returns the reals of the client the credentials cache is for. func (c *CCache) GetClientRealm() string { return c.DefaultPrincipal.Realm } // GetClientCredentials returns a Credentials object representing the client of the credentials cache. func (c *CCache) GetClientCredentials() *Credentials { return &Credentials{ username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(), realm: c.GetClientRealm(), cname: c.DefaultPrincipal.PrincipalName, } } // Contains tests if the cache contains a credential for the provided server PrincipalName func (c *CCache) Contains(p types.PrincipalName) bool { for _, cred := range c.Credentials { if cred.Server.PrincipalName.Equal(p) { return true } } return false } // GetEntry returns a specific credential for the PrincipalName provided. func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) { cred := new(Credential) var found bool for i := range c.Credentials { if c.Credentials[i].Server.PrincipalName.Equal(p) { cred = c.Credentials[i] found = true break } } if !found { return cred, false } return cred, true } // GetEntries filters out configuration entries an returns a slice of credentials. func (c *CCache) GetEntries() []*Credential { creds := make([]*Credential, 0) for _, cred := range c.Credentials { // Filter out configuration entries if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") { continue } creds = append(creds, cred) } return creds } func (h *headerField) valid() bool { // See https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html - Header format switch h.tag { case headerFieldTagKDCOffset: if h.length != 8 || len(h.value) != 8 { return false } return true } return false } func readData(b []byte, p *int, e *binary.ByteOrder) []byte { l := readInt32(b, p, e) return readBytes(b, p, int(l), e) } func readAddress(b []byte, p *int, e *binary.ByteOrder) types.HostAddress { a := types.HostAddress{} a.AddrType = int32(readInt16(b, p, e)) a.Address = readData(b, p, e) return a } func readAuthDataEntry(b []byte, p *int, e *binary.ByteOrder) types.AuthorizationDataEntry { a := types.AuthorizationDataEntry{} a.ADType = int32(readInt16(b, p, e)) a.ADData = readData(b, p, e) return a } // Read bytes representing a timestamp. func readTimestamp(b []byte, p *int, e *binary.ByteOrder) time.Time { return time.Unix(int64(readInt32(b, p, e)), 0) } // Read bytes representing an eight bit integer. func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8) { buf := bytes.NewBuffer(b[*p : *p+1]) binary.Read(buf, *e, &i) *p++ return } // Read bytes representing a sixteen bit integer. func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16) { buf := bytes.NewBuffer(b[*p : *p+2]) binary.Read(buf, *e, &i) *p += 2 return } // Read bytes representing a thirty two bit integer. func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32) { buf := bytes.NewBuffer(b[*p : *p+4]) binary.Read(buf, *e, &i) *p += 4 return } func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte { buf := bytes.NewBuffer(b[*p : *p+s]) r := make([]byte, s) binary.Read(buf, *e, &r) *p += s return r } func isNativeEndianLittle() bool { var x = 0x012345678 var p = unsafe.Pointer(&x) var bp = (*[4]byte)(p) var endian bool if 0x01 == bp[0] { endian = false } else if (0x78 & 0xff) == (bp[0] & 0xff) { endian = true } else { // Default to big endian endian = false } return endian } gokrb5-8.4.3/credentials/ccache_integration_test.go000066400000000000000000000070761427031340300224220ustar00rootroot00000000000000package credentials import ( "bufio" "bytes" "fmt" "io" "os" "os/exec" "os/user" "sync" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( kinitCmd = "kinit" kvnoCmd = "kvno" klistCmd = "klist" spn = "HTTP/host.test.gokrb5" ) type output struct { buf *bytes.Buffer lines []string *sync.Mutex } func newOutput() *output { return &output{ buf: &bytes.Buffer{}, lines: []string{}, Mutex: &sync.Mutex{}, } } func (rw *output) Write(p []byte) (int, error) { rw.Lock() defer rw.Unlock() return rw.buf.Write(p) } func (rw *output) Lines() []string { rw.Lock() defer rw.Unlock() s := bufio.NewScanner(rw.buf) for s.Scan() { rw.lines = append(rw.lines, s.Text()) } return rw.lines } func login() error { file, err := os.Create("/etc/krb5.conf") if err != nil { return fmt.Errorf("cannot open krb5.conf: %v", err) } defer file.Close() fmt.Fprintf(file, testdata.TEST_KRB5CONF) cmd := exec.Command(kinitCmd, "testuser1@TEST.GOKRB5") stdinR, stdinW := io.Pipe() stderrR, stderrW := io.Pipe() cmd.Stdin = stdinR cmd.Stderr = stderrW err = cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kinitCmd, err) } go func() { io.WriteString(stdinW, "passwordvalue") stdinW.Close() }() errBuf := new(bytes.Buffer) go func() { io.Copy(errBuf, stderrR) stderrR.Close() }() err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v stderr: %s", kinitCmd, err, string(errBuf.Bytes())) } return nil } func getServiceTkt() error { cmd := exec.Command(kvnoCmd, spn) err := cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kvnoCmd, err) } err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v", kvnoCmd, err) } return nil } func klist() ([]string, error) { cmd := exec.Command(klistCmd, "-Aef") stdout := newOutput() cmd.Stdout = stdout err := cmd.Start() if err != nil { return []string{}, fmt.Errorf("could not start %s command: %v", klistCmd, err) } err = cmd.Wait() if err != nil { return []string{}, fmt.Errorf("%s did not run successfully: %v", klistCmd, err) } return stdout.Lines(), nil } func loadCCache() (*CCache, error) { usr, _ := user.Current() cpath := "/tmp/krb5cc_" + usr.Uid return LoadCCache(cpath) } func TestLoadCCache(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } pn := c.GetClientPrincipalName() assert.Equal(t, "testuser1", pn.PrincipalNameString(), "principal not as expected") assert.Equal(t, "TEST.GOKRB5", c.GetClientRealm(), "realm not as expected") } func TestCCacheEntries(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } err = getServiceTkt() if err != nil { t.Fatalf("error getting service ticket: %v", err) } clist, _ := klist() t.Log("OS Creds Cache contents:") for _, l := range clist { t.Log(l) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } creds := c.GetEntries() var found bool n := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) for _, cred := range creds { if cred.Server.PrincipalName.Equal(n) { found = true break } } if !found { t.Errorf("Entry for %s not found in CCache", spn) } } gokrb5-8.4.3/credentials/ccache_test.go000066400000000000000000000077111427031340300200130ustar00rootroot00000000000000package credentials import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) func TestParse(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } assert.Equal(t, uint8(4), c.Version, "Version not as expected") assert.Equal(t, 1, len(c.Header.fields), "Number of header fields not as expected") assert.Equal(t, uint16(1), c.Header.fields[0].tag, "Header tag not as expected") assert.Equal(t, uint16(8), c.Header.fields[0].length, "Length of header not as expected") assert.Equal(t, "TEST.GOKRB5", c.DefaultPrincipal.Realm, "Default client principal realm not as expected") assert.Equal(t, "testuser1", c.DefaultPrincipal.PrincipalName.PrincipalNameString(), "Default client principaal name not as expected") assert.Equal(t, 3, len(c.Credentials), "Number of credentials not as expected") tgtpn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", "TEST.GOKRB5"}, } assert.True(t, c.Contains(tgtpn), "Cache does not contain TGT credential") httppn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"HTTP", "host.test.gokrb5"}, } assert.True(t, c.Contains(httppn), "Cache does not contain HTTP SPN credential") } func TestCCache_GetClientPrincipalName(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } pn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser1"}, } assert.Equal(t, pn, c.GetClientPrincipalName(), "Client PrincipalName not as expected") } func TestCCache_GetClientCredentials(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } pn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser1"}, } cred := c.GetClientCredentials() assert.Equal(t, "TEST.GOKRB5", cred.Domain(), "Client realm in credential not as expected") assert.Equal(t, pn, cred.CName(), "Client Principal Name not as expected") assert.Equal(t, "testuser1", cred.UserName(), "Username not as expected") } func TestCCache_GetClientRealm(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.GetClientRealm(), "Client realm not as expected") } func TestCCache_GetEntry(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } httppn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"HTTP", "host.test.gokrb5"}, } cred, ok := c.GetEntry(httppn) if !ok { t.Fatal("Could not get entry from CCache as not found") } assert.Equal(t, httppn, cred.Server.PrincipalName, "Credential does not have the right server principal name") } func TestCCache_GetEntries(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } creds := c.GetEntries() assert.Equal(t, 2, len(creds), "Number of credentials entries not as expected") } gokrb5-8.4.3/credentials/credentials.go000066400000000000000000000204021427031340300200330ustar00rootroot00000000000000// Package credentials provides credentials management for Kerberos 5 authentication. package credentials import ( "time" "github.com/hashicorp/go-uuid" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( // AttributeKeyADCredentials assigned number for AD credentials. AttributeKeyADCredentials = "gokrb5AttributeKeyADCredentials" ) // Credentials struct for a user. // Contains either a keytab, password or both. // Keytabs are used over passwords if both are defined. type Credentials struct { username string displayName string realm string cname types.PrincipalName keytab *keytab.Keytab password string attributes map[string]interface{} validUntil time.Time authenticated bool human bool authTime time.Time groupMembership map[string]bool sessionID string } // ADCredentials contains information obtained from the PAC. type ADCredentials struct { EffectiveName string FullName string UserID int PrimaryGroupID int LogOnTime time.Time LogOffTime time.Time PasswordLastSet time.Time GroupMembershipSIDs []string LogonDomainName string LogonDomainID string LogonServer string } // New creates a new Credentials instance. func New(username string, realm string) *Credentials { uid, err := uuid.GenerateUUID() if err != nil { uid = "00unique-sess-ions-uuid-unavailable0" } return &Credentials{ username: username, displayName: username, realm: realm, cname: types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username), keytab: keytab.New(), attributes: make(map[string]interface{}), groupMembership: make(map[string]bool), sessionID: uid, human: true, } } // NewFromPrincipalName creates a new Credentials instance with the user details provides as a PrincipalName type. func NewFromPrincipalName(cname types.PrincipalName, realm string) *Credentials { uid, err := uuid.GenerateUUID() if err != nil { uid = "00unique-sess-ions-uuid-unavailable0" } return &Credentials{ username: cname.PrincipalNameString(), displayName: cname.PrincipalNameString(), realm: realm, cname: cname, keytab: keytab.New(), attributes: make(map[string]interface{}), groupMembership: make(map[string]bool), sessionID: uid, human: true, } } // WithKeytab sets the Keytab in the Credentials struct. func (c *Credentials) WithKeytab(kt *keytab.Keytab) *Credentials { c.keytab = kt c.password = "" return c } // Keytab returns the credential's Keytab. func (c *Credentials) Keytab() *keytab.Keytab { return c.keytab } // HasKeytab queries if the Credentials has a keytab defined. func (c *Credentials) HasKeytab() bool { if c.keytab != nil && len(c.keytab.Entries) > 0 { return true } return false } // WithPassword sets the password in the Credentials struct. func (c *Credentials) WithPassword(password string) *Credentials { c.password = password c.keytab = keytab.New() // clear any keytab return c } // Password returns the credential's password. func (c *Credentials) Password() string { return c.password } // HasPassword queries if the Credentials has a password defined. func (c *Credentials) HasPassword() bool { if c.password != "" { return true } return false } // SetValidUntil sets the expiry time of the credentials func (c *Credentials) SetValidUntil(t time.Time) { c.validUntil = t } // SetADCredentials adds ADCredentials attributes to the credentials func (c *Credentials) SetADCredentials(a ADCredentials) { c.SetAttribute(AttributeKeyADCredentials, a) if a.FullName != "" { c.SetDisplayName(a.FullName) } if a.EffectiveName != "" { c.SetUserName(a.EffectiveName) } for i := range a.GroupMembershipSIDs { c.AddAuthzAttribute(a.GroupMembershipSIDs[i]) } } // Methods to implement goidentity.Identity interface // UserName returns the credential's username. func (c *Credentials) UserName() string { return c.username } // SetUserName sets the username value on the credential. func (c *Credentials) SetUserName(s string) { c.username = s } // CName returns the credential's client principal name. func (c *Credentials) CName() types.PrincipalName { return c.cname } // SetCName sets the client principal name on the credential. func (c *Credentials) SetCName(pn types.PrincipalName) { c.cname = pn } // Domain returns the credential's domain. func (c *Credentials) Domain() string { return c.realm } // SetDomain sets the domain value on the credential. func (c *Credentials) SetDomain(s string) { c.realm = s } // Realm returns the credential's realm. Same as the domain. func (c *Credentials) Realm() string { return c.Domain() } // SetRealm sets the realm value on the credential. Same as the domain func (c *Credentials) SetRealm(s string) { c.SetDomain(s) } // DisplayName returns the credential's display name. func (c *Credentials) DisplayName() string { return c.displayName } // SetDisplayName sets the display name value on the credential. func (c *Credentials) SetDisplayName(s string) { c.displayName = s } // Human returns if the credential represents a human or not. func (c *Credentials) Human() bool { return c.human } // SetHuman sets the credential as human. func (c *Credentials) SetHuman(b bool) { c.human = b } // AuthTime returns the time the credential was authenticated. func (c *Credentials) AuthTime() time.Time { return c.authTime } // SetAuthTime sets the time the credential was authenticated. func (c *Credentials) SetAuthTime(t time.Time) { c.authTime = t } // AuthzAttributes returns the credentials authorizing attributes. func (c *Credentials) AuthzAttributes() []string { s := make([]string, len(c.groupMembership)) i := 0 for a := range c.groupMembership { s[i] = a i++ } return s } // Authenticated indicates if the credential has been successfully authenticated or not. func (c *Credentials) Authenticated() bool { return c.authenticated } // SetAuthenticated sets the credential as having been successfully authenticated. func (c *Credentials) SetAuthenticated(b bool) { c.authenticated = b } // AddAuthzAttribute adds an authorization attribute to the credential. func (c *Credentials) AddAuthzAttribute(a string) { c.groupMembership[a] = true } // RemoveAuthzAttribute removes an authorization attribute from the credential. func (c *Credentials) RemoveAuthzAttribute(a string) { if _, ok := c.groupMembership[a]; !ok { return } delete(c.groupMembership, a) } // EnableAuthzAttribute toggles an authorization attribute to an enabled state on the credential. func (c *Credentials) EnableAuthzAttribute(a string) { if enabled, ok := c.groupMembership[a]; ok && !enabled { c.groupMembership[a] = true } } // DisableAuthzAttribute toggles an authorization attribute to a disabled state on the credential. func (c *Credentials) DisableAuthzAttribute(a string) { if enabled, ok := c.groupMembership[a]; ok && enabled { c.groupMembership[a] = false } } // Authorized indicates if the credential has the specified authorizing attribute. func (c *Credentials) Authorized(a string) bool { if enabled, ok := c.groupMembership[a]; ok && enabled { return true } return false } // SessionID returns the credential's session ID. func (c *Credentials) SessionID() string { return c.sessionID } // Expired indicates if the credential has expired. func (c *Credentials) Expired() bool { if !c.validUntil.IsZero() && time.Now().UTC().After(c.validUntil) { return true } return false } // ValidUntil returns the credential's valid until date func (c *Credentials) ValidUntil() time.Time { return c.validUntil } // Attributes returns the Credentials' attributes map. func (c *Credentials) Attributes() map[string]interface{} { return c.attributes } // SetAttribute sets the value of an attribute. func (c *Credentials) SetAttribute(k string, v interface{}) { c.attributes[k] = v } // SetAttributes replaces the attributes map with the one provided. func (c *Credentials) SetAttributes(a map[string]interface{}) { c.attributes = a } // RemoveAttribute deletes an attribute from the attribute map that has the key provided. func (c *Credentials) RemoveAttribute(k string) { delete(c.attributes, k) } gokrb5-8.4.3/credentials/credentials_test.go000066400000000000000000000005161427031340300210760ustar00rootroot00000000000000package credentials import ( "github.com/stretchr/testify/assert" goidentity "gopkg.in/jcmturner/goidentity.v3" "testing" ) func TestImplementsInterface(t *testing.T) { t.Parallel() u := new(Credentials) i := new(goidentity.Identity) assert.Implements(t, i, u, "Credentials type does not implement the Identity interface") } gokrb5-8.4.3/crypto/000077500000000000000000000000001427031340300142345ustar00rootroot00000000000000gokrb5-8.4.3/crypto/aes128-cts-hmac-sha1-96.go000066400000000000000000000105271427031340300203560ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha1" "hash" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // RFC 3962 // Aes128CtsHmacSha96 implements Kerberos encryption type aes128-cts-hmac-sha1-96 type Aes128CtsHmacSha96 struct { } // GetETypeID returns the EType ID number. func (e Aes128CtsHmacSha96) GetETypeID() int32 { return etypeID.AES128_CTS_HMAC_SHA1_96 } // GetHashID returns the checksum type ID number. func (e Aes128CtsHmacSha96) GetHashID() int32 { return chksumtype.HMAC_SHA1_96_AES128 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes128CtsHmacSha96) GetKeyByteSize() int { return 128 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes128CtsHmacSha96) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes128CtsHmacSha96) GetHashFunc() func() hash.Hash { return sha1.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes128CtsHmacSha96) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes128CtsHmacSha96) GetDefaultStringToKeyParams() string { return "00001000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes128CtsHmacSha96) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes128CtsHmacSha96) GetHMACBitLength() int { return 96 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes128CtsHmacSha96) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes128CtsHmacSha96) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { return rfc3962.StringToKey(secret, salt, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes128CtsHmacSha96) RandomToKey(b []byte) []byte { return rfc3961.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes128CtsHmacSha96) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc3962.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes128CtsHmacSha96) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc3962.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes128CtsHmacSha96) DecryptData(key, data []byte) ([]byte, error) { return rfc3962.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes128CtsHmacSha96) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc3962.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes128CtsHmacSha96) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveKey(protocolKey, usage, e) } // DeriveRandom generates data needed for key generation. func (e Aes128CtsHmacSha96) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e Aes128CtsHmacSha96) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes128CtsHmacSha96) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes128CtsHmacSha96) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/crypto/aes128-cts-hmac-sha1-96_test.go000066400000000000000000000036451427031340300214200ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962" ) func TestAes128CtsHmacSha196_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 3962 Appendix B b, _ := hex.DecodeString("1234567878563412") s := string(b) b, _ = hex.DecodeString("f09d849e") s2 := string(b) var tests = []struct { iterations int64 phrase string salt string pbkdf2 string key string }{ {1, "password", "ATHENA.MIT.EDUraeburn", "cdedb5281bb2f801565a1122b2563515", "42263c6e89f4fc28b8df68ee09799f15"}, {2, "password", "ATHENA.MIT.EDUraeburn", "01dbee7f4a9e243e988b62c73cda935d", "c651bf29e2300ac27fa469d693bdda13"}, {1200, "password", "ATHENA.MIT.EDUraeburn", "5c08eb61fdf71e4e4ec3cf6ba1f5512b", "4c01cd46d632d01e6dbe230a01ed642a"}, {5, "password", s, "d1daa78615f287e6a1c8b120d7062a49", "e9b23d52273747dd5c35cb55be619d8e"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase equals block size", "139c30c0966bc32ba55fdbf212530ac9", "59d1bb789a828b1aa54ef9c2883f69ed"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase exceeds block size", "9ccad6d468770cd51b10e6a68721be61", "cb8005dc5f90179a7f02104c0018751d"}, {50, s2, "EXAMPLE.COMpianist", "6b9cf26d45455a43a5b8bb276a403b39", "f149c1f2e154a73452d43e7fe62a56e5"}, } var e Aes128CtsHmacSha96 for i, test := range tests { assert.Equal(t, test.pbkdf2, hex.EncodeToString(rfc3962.StringToPBKDF2(test.phrase, test.salt, test.iterations, e)), "PBKDF2 not as expected") k, err := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(uint32(test.iterations))) if err != nil { t.Errorf("error in processing string to key for test %d: %v", i, err) } assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } gokrb5-8.4.3/crypto/aes128-cts-hmac-sha256-128.go000066400000000000000000000113161427031340300206030ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha256" "hash" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // RFC https://tools.ietf.org/html/rfc8009 // Aes128CtsHmacSha256128 implements Kerberos encryption type aes128-cts-hmac-sha256-128 type Aes128CtsHmacSha256128 struct { } // GetETypeID returns the EType ID number. func (e Aes128CtsHmacSha256128) GetETypeID() int32 { return etypeID.AES128_CTS_HMAC_SHA256_128 } // GetHashID returns the checksum type ID number. func (e Aes128CtsHmacSha256128) GetHashID() int32 { return chksumtype.HMAC_SHA256_128_AES128 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes128CtsHmacSha256128) GetKeyByteSize() int { return 128 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes128CtsHmacSha256128) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes128CtsHmacSha256128) GetHashFunc() func() hash.Hash { return sha256.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes128CtsHmacSha256128) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes128CtsHmacSha256128) GetDefaultStringToKeyParams() string { return "00008000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes128CtsHmacSha256128) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes128CtsHmacSha256128) GetHMACBitLength() int { return 128 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes128CtsHmacSha256128) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes128CtsHmacSha256128) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { saltp := rfc8009.GetSaltP(salt, "aes128-cts-hmac-sha256-128") return rfc8009.StringToKey(secret, saltp, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes128CtsHmacSha256128) RandomToKey(b []byte) []byte { return rfc8009.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes128CtsHmacSha256128) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc8009.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes128CtsHmacSha256128) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc8009.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes128CtsHmacSha256128) DecryptData(key, data []byte) ([]byte, error) { return rfc8009.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes128CtsHmacSha256128) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc8009.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes128CtsHmacSha256128) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveKey(protocolKey, usage, e), nil } // DeriveRandom generates data needed for key generation. func (e Aes128CtsHmacSha256128) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the ciphertext message. // As the hash is calculated over the iv concatenated with the AES cipher output not the plaintext the pt value to this // interface method is not use. Pass any []byte. func (e Aes128CtsHmacSha256128) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { // We don't need ib just there for the interface return rfc8009.VerifyIntegrity(protocolKey, ct, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes128CtsHmacSha256128) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes128CtsHmacSha256128) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/crypto/aes128-cts-hmac-sha256-128_test.go000066400000000000000000000146171427031340300216510ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009" ) func TestAes128CtsHmacSha256128_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A // Random 16bytes in test vector as string r, _ := hex.DecodeString("10DF9DD783E5BC8ACEA1730E74355F61") s := string(r) var tests = []struct { iterations uint32 phrase string salt string saltp string key string }{ {32768, "password", s + "ATHENA.MIT.EDUraeburn", "6165733132382d6374732d686d61632d7368613235362d3132380010df9dd783e5bc8acea1730e74355f61415448454e412e4d49542e4544557261656275726e", "089bca48b105ea6ea77ca5d2f39dc5e7"}, } var e Aes128CtsHmacSha256128 for _, test := range tests { saltp := rfc8009.GetSaltP(test.salt, "aes128-cts-hmac-sha256-128") assert.Equal(t, test.saltp, hex.EncodeToString([]byte(saltp)), "SaltP not as expected") k, _ := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(test.iterations)) assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } func TestAes128CtsHmacSha256128_DeriveKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A protocolBaseKey, _ := hex.DecodeString("3705d96080c17728a0e800eab6e0d23c") testUsage := uint32(2) var e Aes128CtsHmacSha256128 k, err := e.DeriveKey(protocolBaseKey, common.GetUsageKc(testUsage)) if err != nil { t.Fatalf("Error deriving checksum key: %v", err) } assert.Equal(t, "b31a018a48f54776f403e9a396325dc3", hex.EncodeToString(k), "Checksum derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKe(testUsage)) if err != nil { t.Fatalf("Error deriving encryption key: %v", err) } assert.Equal(t, "9b197dd1e8c5609d6e67c3e37c62c72e", hex.EncodeToString(k), "Encryption derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKi(testUsage)) if err != nil { t.Fatalf("Error deriving integrity key: %v", err) } assert.Equal(t, "9fda0e56ab2d85e1569a688696c26a6c", hex.EncodeToString(k), "Integrity derived key not as epxected") } func TestAes128CtsHmacSha256128_VerifyIntegrity(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 protocolBaseKey, _ := hex.DecodeString("3705d96080c17728a0e800eab6e0d23c") testUsage := uint32(2) var e Aes128CtsHmacSha256128 var tests = []struct { kc string pt string chksum string }{ {"b31a018a48f54776f403e9a396325dc3", "000102030405060708090a0b0c0d0e0f1011121314", "d78367186643d67b411cba9139fc1dee"}, } for _, test := range tests { p, _ := hex.DecodeString(test.pt) b, err := e.GetChecksumHash(protocolBaseKey, p, testUsage) if err != nil { t.Errorf("error generating checksum: %v", err) } assert.Equal(t, test.chksum, hex.EncodeToString(b), "Checksum not as expected") } } func TestAes128CtsHmacSha256128_Cypto(t *testing.T) { t.Parallel() protocolBaseKey, _ := hex.DecodeString("3705d96080c17728a0e800eab6e0d23c") testUsage := uint32(2) var tests = []struct { plain string confounder string ke string ki string encrypted string // AESOutput hash string // TruncatedHMACOutput cipher string // Ciphertext(AESOutput|HMACOutput) }{ // Test vectors from RFC 8009 Appendix A {"", "7e5895eaf2672435bad817f545a37148", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "ef85fb890bb8472f4dab20394dca781d", "ad877eda39d50c870c0d5a0a8e48c718", "ef85fb890bb8472f4dab20394dca781dad877eda39d50c870c0d5a0a8e48c718"}, {"000102030405", "7bca285e2fd4130fb55b1a5c83bc5b24", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "84d7f30754ed987bab0bf3506beb09cfb55402cef7e6", "877ce99e247e52d16ed4421dfdf8976c", "84d7f30754ed987bab0bf3506beb09cfb55402cef7e6877ce99e247e52d16ed4421dfdf8976c"}, {"000102030405060708090a0b0c0d0e0f", "56ab21713ff62c0a1457200f6fa9948f", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "3517d640f50ddc8ad3628722b3569d2ae07493fa8263254080ea65c1008e8fc2", "95fb4852e7d83e1e7c48c37eebe6b0d3", "3517d640f50ddc8ad3628722b3569d2ae07493fa8263254080ea65c1008e8fc295fb4852e7d83e1e7c48c37eebe6b0d3"}, {"000102030405060708090a0b0c0d0e0f1011121314", "a7a4e29a4728ce10664fb64e49ad3fac", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "720f73b18d9859cd6ccb4346115cd336c70f58edc0c4437c5573544c31c813bce1e6d072c1", "86b39a413c2f92ca9b8334a287ffcbfc", "720f73b18d9859cd6ccb4346115cd336c70f58edc0c4437c5573544c31c813bce1e6d072c186b39a413c2f92ca9b8334a287ffcbfc"}, } var e Aes128CtsHmacSha256128 for i, test := range tests { m, _ := hex.DecodeString(test.plain) b, _ := hex.DecodeString(test.encrypted) ke, _ := hex.DecodeString(test.ke) cf, _ := hex.DecodeString(test.confounder) ct, _ := hex.DecodeString(test.cipher) cfm := append(cf, m...) // Test encryption to raw encrypted bytes _, c, err := e.EncryptData(ke, cfm) if err != nil { t.Errorf("encryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.encrypted, hex.EncodeToString(c), "Encrypted result not as expected - test %v", i) // Test decryption of raw encrypted bytes p, err := e.DecryptData(ke, b) //Remove the confounder bytes p = p[e.GetConfounderByteSize():] if err != nil { t.Errorf("decryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.plain, hex.EncodeToString(p), "Decrypted result not as expected - test %v", i) // Test integrity check of complete ciphertext message assert.True(t, e.VerifyIntegrity(protocolBaseKey, ct, ct, testUsage), "Integrity check of cipher text failed") // Test encrypting and decrypting a complete cipertext message (with confounder, integrity hash) _, cm, err := e.EncryptMessage(protocolBaseKey, m, testUsage) if err != nil { t.Errorf("encryption to message failed for test %v: %v", i+1, err) } dm, err := e.DecryptMessage(protocolBaseKey, cm, testUsage) if err != nil { t.Errorf("decrypting complete encrypted message failed for test %v: %v", i+1, err) } assert.Equal(t, m, dm, "Message not as expected after encrypting and decrypting for test %v: %v", i+1, err) // Test the integrity hash ivz := make([]byte, e.GetConfounderByteSize()) hm := append(ivz, b...) mac, _ := common.GetIntegrityHash(hm, protocolBaseKey, testUsage, e) assert.Equal(t, test.hash, hex.EncodeToString(mac), "HMAC result not as expected - test %v", i) } } gokrb5-8.4.3/crypto/aes256-cts-hmac-sha1-96.go000066400000000000000000000105271427031340300203600ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha1" "hash" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // RFC 3962 // Aes256CtsHmacSha96 implements Kerberos encryption type aes256-cts-hmac-sha1-96 type Aes256CtsHmacSha96 struct { } // GetETypeID returns the EType ID number. func (e Aes256CtsHmacSha96) GetETypeID() int32 { return etypeID.AES256_CTS_HMAC_SHA1_96 } // GetHashID returns the checksum type ID number. func (e Aes256CtsHmacSha96) GetHashID() int32 { return chksumtype.HMAC_SHA1_96_AES256 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes256CtsHmacSha96) GetKeyByteSize() int { return 256 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes256CtsHmacSha96) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes256CtsHmacSha96) GetHashFunc() func() hash.Hash { return sha1.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes256CtsHmacSha96) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes256CtsHmacSha96) GetDefaultStringToKeyParams() string { return "00001000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes256CtsHmacSha96) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes256CtsHmacSha96) GetHMACBitLength() int { return 96 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes256CtsHmacSha96) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes256CtsHmacSha96) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { return rfc3962.StringToKey(secret, salt, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes256CtsHmacSha96) RandomToKey(b []byte) []byte { return rfc3961.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes256CtsHmacSha96) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc3962.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes256CtsHmacSha96) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc3962.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes256CtsHmacSha96) DecryptData(key, data []byte) ([]byte, error) { return rfc3962.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes256CtsHmacSha96) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc3962.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes256CtsHmacSha96) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveKey(protocolKey, usage, e) } // DeriveRandom generates data needed for key generation. func (e Aes256CtsHmacSha96) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e Aes256CtsHmacSha96) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes256CtsHmacSha96) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes256CtsHmacSha96) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/crypto/aes256-cts-hmac-sha1-96_test.go000066400000000000000000000045451427031340300214220ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962" ) func TestAes256CtsHmacSha196_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 3962 Appendix B b, _ := hex.DecodeString("1234567878563412") s := string(b) b, _ = hex.DecodeString("f09d849e") s2 := string(b) var tests = []struct { iterations int64 phrase string salt string pbkdf2 string key string }{ {1, "password", "ATHENA.MIT.EDUraeburn", "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837", "fe697b52bc0d3ce14432ba036a92e65bbb52280990a2fa27883998d72af30161"}, {2, "password", "ATHENA.MIT.EDUraeburn", "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86", "a2e16d16b36069c135d5e9d2e25f896102685618b95914b467c67622225824ff"}, {1200, "password", "ATHENA.MIT.EDUraeburn", "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13", "55a6ac740ad17b4846941051e1e8b0a7548d93b0ab30a8bc3ff16280382b8c2a"}, {5, "password", s, "d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee", "97a4e786be20d81a382d5ebc96d5909cabcdadc87ca48f574504159f16c36e31"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase equals block size", "139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1", "89adee3608db8bc71f1bfbfe459486b05618b70cbae22092534e56c553ba4b34"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase exceeds block size", "9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a", "d78c5c9cb872a8c9dad4697f0bb5b2d21496c82beb2caeda2112fceea057401b"}, {50, s2, "EXAMPLE.COMpianist", "6b9cf26d45455a43a5b8bb276a403b39e7fe37a0c41e02c281ff3069e1e94f52", "4b6d9839f84406df1f09cc166db4b83c571848b784a3d6bdc346589a3e393f9e"}, } var e Aes256CtsHmacSha96 for i, test := range tests { assert.Equal(t, test.pbkdf2, hex.EncodeToString(rfc3962.StringToPBKDF2(test.phrase, test.salt, test.iterations, e)), "PBKDF2 not as expected") k, err := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(uint32(test.iterations))) if err != nil { t.Errorf("error in processing string to key for test %d: %v", i, err) } assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } gokrb5-8.4.3/crypto/aes256-cts-hmac-sha384-192.go000066400000000000000000000113211427031340300206040ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha512" "hash" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // RFC https://tools.ietf.org/html/rfc8009 // Aes256CtsHmacSha384192 implements Kerberos encryption type aes256-cts-hmac-sha384-192 type Aes256CtsHmacSha384192 struct { } // GetETypeID returns the EType ID number. func (e Aes256CtsHmacSha384192) GetETypeID() int32 { return etypeID.AES256_CTS_HMAC_SHA384_192 } // GetHashID returns the checksum type ID number. func (e Aes256CtsHmacSha384192) GetHashID() int32 { return chksumtype.HMAC_SHA384_192_AES256 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes256CtsHmacSha384192) GetKeyByteSize() int { return 192 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes256CtsHmacSha384192) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes256CtsHmacSha384192) GetHashFunc() func() hash.Hash { return sha512.New384 } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes256CtsHmacSha384192) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes256CtsHmacSha384192) GetDefaultStringToKeyParams() string { return "00008000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes256CtsHmacSha384192) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes256CtsHmacSha384192) GetHMACBitLength() int { return 192 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes256CtsHmacSha384192) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes256CtsHmacSha384192) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { saltp := rfc8009.GetSaltP(salt, "aes256-cts-hmac-sha384-192") return rfc8009.StringToKey(secret, saltp, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes256CtsHmacSha384192) RandomToKey(b []byte) []byte { return rfc8009.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes256CtsHmacSha384192) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc8009.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes256CtsHmacSha384192) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc8009.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes256CtsHmacSha384192) DecryptData(key, data []byte) ([]byte, error) { return rfc8009.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes256CtsHmacSha384192) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc8009.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes256CtsHmacSha384192) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveKey(protocolKey, usage, e), nil } // DeriveRandom generates data needed for key generation. func (e Aes256CtsHmacSha384192) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the ciphertext message. // As the hash is calculated over the iv concatenated with the AES cipher output not the plaintext the pt value to this // interface method is not use. Pass any []byte. func (e Aes256CtsHmacSha384192) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { // We don't need ib just there for the interface return rfc8009.VerifyIntegrity(protocolKey, ct, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes256CtsHmacSha384192) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes256CtsHmacSha384192) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/crypto/aes256-cts-hmac-sha384-192_test.go000066400000000000000000000156561427031340300216620ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009" ) func TestAes256CtsHmacSha384192_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A // Random 16bytes in test vector as string r, _ := hex.DecodeString("10DF9DD783E5BC8ACEA1730E74355F61") s := string(r) var tests = []struct { iterations uint32 phrase string salt string saltp string key string }{ {32768, "password", s + "ATHENA.MIT.EDUraeburn", "6165733235362d6374732d686d61632d7368613338342d3139320010df9dd783e5bc8acea1730e74355f61415448454e412e4d49542e4544557261656275726e", "45bd806dbf6a833a9cffc1c94589a222367a79bc21c413718906e9f578a78467"}, } var e Aes256CtsHmacSha384192 for _, test := range tests { saltp := rfc8009.GetSaltP(test.salt, "aes256-cts-hmac-sha384-192") assert.Equal(t, test.saltp, hex.EncodeToString([]byte(saltp)), "SaltP not as expected") k, _ := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(test.iterations)) assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } func TestAes256CtsHmacSha384192_DeriveKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A protocolBaseKey, _ := hex.DecodeString("6d404d37faf79f9df0d33568d320669800eb4836472ea8a026d16b7182460c52") testUsage := uint32(2) var e Aes256CtsHmacSha384192 k, err := e.DeriveKey(protocolBaseKey, common.GetUsageKc(testUsage)) if err != nil { t.Fatalf("Error deriving checksum key: %v", err) } assert.Equal(t, "ef5718be86cc84963d8bbb5031e9f5c4ba41f28faf69e73d", hex.EncodeToString(k), "Checksum derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKe(testUsage)) if err != nil { t.Fatalf("Error deriving encryption key: %v", err) } assert.Equal(t, "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", hex.EncodeToString(k), "Encryption derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKi(testUsage)) if err != nil { t.Fatalf("Error deriving integrity key: %v", err) } assert.Equal(t, "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", hex.EncodeToString(k), "Integrity derived key not as epxected") } func TestAes256CtsHmacSha384192_Cypto(t *testing.T) { t.Parallel() protocolBaseKey, _ := hex.DecodeString("6d404d37faf79f9df0d33568d320669800eb4836472ea8a026d16b7182460c52") testUsage := uint32(2) var tests = []struct { plain string confounder string ke string ki string encrypted string // AESOutput hash string // TruncatedHMACOutput cipher string // Ciphertext(AESOutput|HMACOutput) }{ // Test vectors from RFC 8009 Appendix A {"", "f764e9fa15c276478b2c7d0c4e5f58e4", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "41f53fa5bfe7026d91faf9be959195a0", "58707273a96a40f0a01960621ac612748b9bbfbe7eb4ce3c", "41f53fa5bfe7026d91faf9be959195a058707273a96a40f0a01960621ac612748b9bbfbe7eb4ce3c"}, {"000102030405", "b80d3251c1f6471494256ffe712d0b9a", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "4ed7b37c2bcac8f74f23c1cf07e62bc7b75fb3f637b9", "f559c7f664f69eab7b6092237526ea0d1f61cb20d69d10f2", "4ed7b37c2bcac8f74f23c1cf07e62bc7b75fb3f637b9f559c7f664f69eab7b6092237526ea0d1f61cb20d69d10f2"}, {"000102030405060708090a0b0c0d0e0f", "53bf8a0d105265d4e276428624ce5e63", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "bc47ffec7998eb91e8115cf8d19dac4bbbe2e163e87dd37f49beca92027764f6", "8cf51f14d798c2273f35df574d1f932e40c4ff255b36a266", "bc47ffec7998eb91e8115cf8d19dac4bbbe2e163e87dd37f49beca92027764f68cf51f14d798c2273f35df574d1f932e40c4ff255b36a266"}, {"000102030405060708090a0b0c0d0e0f1011121314", "763e65367e864f02f55153c7e3b58af1", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "40013e2df58e8751957d2878bcd2d6fe101ccfd556cb1eae79db3c3ee86429f2b2a602ac86", "fef6ecb647d6295fae077a1feb517508d2c16b4192e01f62", "40013e2df58e8751957d2878bcd2d6fe101ccfd556cb1eae79db3c3ee86429f2b2a602ac86fef6ecb647d6295fae077a1feb517508d2c16b4192e01f62"}, } var e Aes256CtsHmacSha384192 for i, test := range tests { m, _ := hex.DecodeString(test.plain) b, _ := hex.DecodeString(test.encrypted) ke, _ := hex.DecodeString(test.ke) cf, _ := hex.DecodeString(test.confounder) ct, _ := hex.DecodeString(test.cipher) cfm := append(cf, m...) // Test encryption to raw encrypted bytes _, c, err := e.EncryptData(ke, cfm) if err != nil { t.Errorf("encryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.encrypted, hex.EncodeToString(c), "Encrypted result not as expected - test %v", i) // Test decryption of raw encrypted bytes p, err := e.DecryptData(ke, b) //Remove the confounder bytes p = p[e.GetConfounderByteSize():] if err != nil { t.Errorf("decryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.plain, hex.EncodeToString(p), "Decrypted result not as expected - test %v", i) // Test integrity check of complete ciphertext message assert.True(t, e.VerifyIntegrity(protocolBaseKey, ct, ct, testUsage), "Integrity check of cipher text failed") // Test encrypting and decrypting a complete cipertext message (with confounder, integrity hash) _, cm, err := e.EncryptMessage(protocolBaseKey, m, testUsage) if err != nil { t.Errorf("encryption to message failed for test %v: %v", i+1, err) } dm, err := e.DecryptMessage(protocolBaseKey, cm, testUsage) if err != nil { t.Errorf("decrypting complete encrypted message failed for test %v: %v", i+1, err) } assert.Equal(t, m, dm, "Message not as expected after encrypting and decrypting for test %v: %v", i+1, err) // Test the integrity hash ivz := make([]byte, e.GetConfounderByteSize()) hm := append(ivz, b...) mac, _ := common.GetIntegrityHash(hm, protocolBaseKey, testUsage, e) assert.Equal(t, test.hash, hex.EncodeToString(mac), "HMAC result not as expected - test %v", i) } } func TestAes256CtsHmacSha384192_VerifyIntegrity(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 protocolBaseKey, _ := hex.DecodeString("6d404d37faf79f9df0d33568d320669800eb4836472ea8a026d16b7182460c52") testUsage := uint32(2) var e Aes256CtsHmacSha384192 var tests = []struct { kc string pt string chksum string }{ {"ef5718be86cc84963d8bbb5031e9f5c4ba41f28faf69e73d", "000102030405060708090a0b0c0d0e0f1011121314", "45ee791567eefca37f4ac1e0222de80d43c3bfa06699672a"}, } for _, test := range tests { p, _ := hex.DecodeString(test.pt) b, err := e.GetChecksumHash(protocolBaseKey, p, testUsage) if err != nil { t.Errorf("error generating checksum: %v", err) } assert.Equal(t, test.chksum, hex.EncodeToString(b), "Checksum not as expected") } } gokrb5-8.4.3/crypto/common/000077500000000000000000000000001427031340300155245ustar00rootroot00000000000000gokrb5-8.4.3/crypto/common/common.go000066400000000000000000000076421427031340300173540ustar00rootroot00000000000000// Package common provides encryption methods common across encryption types package common import ( "bytes" "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" ) // ZeroPad pads bytes with zeros to nearest multiple of message size m. func ZeroPad(b []byte, m int) ([]byte, error) { if m <= 0 { return nil, errors.New("Invalid message block size when padding") } if b == nil || len(b) == 0 { return nil, errors.New("Data not valid to pad: Zero size") } if l := len(b) % m; l != 0 { n := m - l z := make([]byte, n) b = append(b, z...) } return b, nil } // PKCS7Pad pads bytes according to RFC 2315 to nearest multiple of message size m. func PKCS7Pad(b []byte, m int) ([]byte, error) { if m <= 0 { return nil, errors.New("Invalid message block size when padding") } if b == nil || len(b) == 0 { return nil, errors.New("Data not valid to pad: Zero size") } n := m - (len(b) % m) pb := make([]byte, len(b)+n) copy(pb, b) copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) return pb, nil } // PKCS7Unpad removes RFC 2315 padding from byes where message size is m. func PKCS7Unpad(b []byte, m int) ([]byte, error) { if m <= 0 { return nil, errors.New("invalid message block size when unpadding") } if b == nil || len(b) == 0 { return nil, errors.New("padded data not valid: Zero size") } if len(b)%m != 0 { return nil, errors.New("padded data not valid: Not multiple of message block size") } c := b[len(b)-1] n := int(c) if n == 0 || n > len(b) { return nil, errors.New("padded data not valid: Data may not have been padded") } for i := 0; i < n; i++ { if b[len(b)-n+i] != c { return nil, errors.New("padded data not valid") } } return b[:len(b)-n], nil } // GetHash generates the keyed hash value according to the etype's hash function. func GetHash(pt, key []byte, usage []byte, etype etype.EType) ([]byte, error) { k, err := etype.DeriveKey(key, usage) if err != nil { return nil, fmt.Errorf("unable to derive key for checksum: %v", err) } mac := hmac.New(etype.GetHashFunc(), k) p := make([]byte, len(pt)) copy(p, pt) mac.Write(p) return mac.Sum(nil)[:etype.GetHMACBitLength()/8], nil } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func GetChecksumHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) { return GetHash(b, key, GetUsageKc(usage), etype) } // GetIntegrityHash returns a keyed integrity hash of the bytes provided. func GetIntegrityHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) { return GetHash(b, key, GetUsageKi(usage), etype) } // VerifyChecksum compares the checksum of the msg bytes is the same as the checksum provided. func VerifyChecksum(key, chksum, msg []byte, usage uint32, etype etype.EType) bool { //The encrypted message is a concatenation of the encrypted output and the hash HMAC. expectedMAC, _ := GetChecksumHash(msg, key, usage, etype) return hmac.Equal(chksum, expectedMAC) } // GetUsageKc returns the checksum key usage value for the usage number un. // // See RFC 3961 5.3 key-derivation function definition. func GetUsageKc(un uint32) []byte { return getUsage(un, 0x99) } // GetUsageKe returns the encryption key usage value for the usage number un // // See RFC 3961 5.3 key-derivation function definition. func GetUsageKe(un uint32) []byte { return getUsage(un, 0xAA) } // GetUsageKi returns the integrity key usage value for the usage number un // // See RFC 3961 5.3 key-derivation function definition. func GetUsageKi(un uint32) []byte { return getUsage(un, 0x55) } func getUsage(un uint32, o byte) []byte { var buf bytes.Buffer binary.Write(&buf, binary.BigEndian, un) return append(buf.Bytes(), o) } // IterationsToS2Kparams converts the number of iterations as an integer to a string representation. func IterationsToS2Kparams(i uint32) string { b := make([]byte, 4, 4) binary.BigEndian.PutUint32(b, i) return hex.EncodeToString(b) } gokrb5-8.4.3/crypto/crypto.go000066400000000000000000000117111427031340300161040ustar00rootroot00000000000000// Package crypto implements cryptographic functions for Kerberos 5 implementation. package crypto import ( "encoding/hex" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/types" ) // GetEtype returns an instances of the required etype struct for the etype ID. func GetEtype(id int32) (etype.EType, error) { switch id { case etypeID.AES128_CTS_HMAC_SHA1_96: var et Aes128CtsHmacSha96 return et, nil case etypeID.AES256_CTS_HMAC_SHA1_96: var et Aes256CtsHmacSha96 return et, nil case etypeID.AES128_CTS_HMAC_SHA256_128: var et Aes128CtsHmacSha256128 return et, nil case etypeID.AES256_CTS_HMAC_SHA384_192: var et Aes256CtsHmacSha384192 return et, nil case etypeID.DES3_CBC_SHA1_KD: var et Des3CbcSha1Kd return et, nil case etypeID.RC4_HMAC: var et RC4HMAC return et, nil default: return nil, fmt.Errorf("unknown or unsupported EType: %d", id) } } // GetChksumEtype returns an instances of the required etype struct for the checksum ID. func GetChksumEtype(id int32) (etype.EType, error) { switch id { case chksumtype.HMAC_SHA1_96_AES128: var et Aes128CtsHmacSha96 return et, nil case chksumtype.HMAC_SHA1_96_AES256: var et Aes256CtsHmacSha96 return et, nil case chksumtype.HMAC_SHA256_128_AES128: var et Aes128CtsHmacSha256128 return et, nil case chksumtype.HMAC_SHA384_192_AES256: var et Aes256CtsHmacSha384192 return et, nil case chksumtype.HMAC_SHA1_DES3_KD: var et Des3CbcSha1Kd return et, nil case chksumtype.KERB_CHECKSUM_HMAC_MD5: var et RC4HMAC return et, nil //case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED: // var et RC4HMAC // return et, nil default: return nil, fmt.Errorf("unknown or unsupported checksum type: %d", id) } } // GetKeyFromPassword generates an encryption key from the principal's password. func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) { var key types.EncryptionKey et, err := GetEtype(etypeID) if err != nil { return key, et, fmt.Errorf("error getting encryption type: %v", err) } sk2p := et.GetDefaultStringToKeyParams() var salt string var paID int32 for _, pa := range pas { switch pa.PADataType { case patype.PA_PW_SALT: if paID > pa.PADataType { continue } salt = string(pa.PADataValue) case patype.PA_ETYPE_INFO: if paID > pa.PADataType { continue } var eti types.ETypeInfo err := eti.Unmarshal(pa.PADataValue) if err != nil { return key, et, fmt.Errorf("error unmashaling PA Data to PA-ETYPE-INFO2: %v", err) } if etypeID != eti[0].EType { et, err = GetEtype(eti[0].EType) if err != nil { return key, et, fmt.Errorf("error getting encryption type: %v", err) } } salt = string(eti[0].Salt) case patype.PA_ETYPE_INFO2: if paID > pa.PADataType { continue } var et2 types.ETypeInfo2 err := et2.Unmarshal(pa.PADataValue) if err != nil { return key, et, fmt.Errorf("error unmashalling PA Data to PA-ETYPE-INFO2: %v", err) } if etypeID != et2[0].EType { et, err = GetEtype(et2[0].EType) if err != nil { return key, et, fmt.Errorf("error getting encryption type: %v", err) } } if len(et2[0].S2KParams) == 4 { sk2p = hex.EncodeToString(et2[0].S2KParams) } salt = et2[0].Salt } } if salt == "" { salt = cname.GetSalt(realm) } k, err := et.StringToKey(passwd, salt, sk2p) if err != nil { return key, et, fmt.Errorf("error deriving key from string: %+v", err) } key = types.EncryptionKey{ KeyType: etypeID, KeyValue: k, } return key, et, nil } // GetEncryptedData encrypts the data provided and returns and EncryptedData type. // Pass a usage value of zero to use the key provided directly rather than deriving one. func GetEncryptedData(plainBytes []byte, key types.EncryptionKey, usage uint32, kvno int) (types.EncryptedData, error) { var ed types.EncryptedData et, err := GetEtype(key.KeyType) if err != nil { return ed, fmt.Errorf("error getting etype: %v", err) } _, b, err := et.EncryptMessage(key.KeyValue, plainBytes, usage) if err != nil { return ed, err } ed = types.EncryptedData{ EType: key.KeyType, Cipher: b, KVNO: kvno, } return ed, nil } // DecryptEncPart decrypts the EncryptedData. func DecryptEncPart(ed types.EncryptedData, key types.EncryptionKey, usage uint32) ([]byte, error) { return DecryptMessage(ed.Cipher, key, usage) } // DecryptMessage decrypts the ciphertext and verifies the integrity. func DecryptMessage(ciphertext []byte, key types.EncryptionKey, usage uint32) ([]byte, error) { et, err := GetEtype(key.KeyType) if err != nil { return []byte{}, fmt.Errorf("error decrypting: %v", err) } b, err := et.DecryptMessage(key.KeyValue, ciphertext, usage) if err != nil { return nil, fmt.Errorf("error decrypting: %v", err) } return b, nil } gokrb5-8.4.3/crypto/des3-cbc-sha1-kd.go000066400000000000000000000107531427031340300174020ustar00rootroot00000000000000package crypto import ( "crypto/des" "crypto/hmac" "crypto/sha1" "errors" "hash" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) //RFC: 3961 Section 6.3 // Des3CbcSha1Kd implements Kerberos encryption type des3-cbc-hmac-sha1-kd type Des3CbcSha1Kd struct { } // GetETypeID returns the EType ID number. func (e Des3CbcSha1Kd) GetETypeID() int32 { return etypeID.DES3_CBC_SHA1_KD } // GetHashID returns the checksum type ID number. func (e Des3CbcSha1Kd) GetHashID() int32 { return chksumtype.HMAC_SHA1_DES3_KD } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Des3CbcSha1Kd) GetKeyByteSize() int { return 24 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Des3CbcSha1Kd) GetKeySeedBitLength() int { return 21 * 8 } // GetHashFunc returns the hash function for this etype. func (e Des3CbcSha1Kd) GetHashFunc() func() hash.Hash { return sha1.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Des3CbcSha1Kd) GetMessageBlockByteSize() int { //For traditional CBC mode with padding, it would be the underlying cipher's block size return des.BlockSize } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Des3CbcSha1Kd) GetDefaultStringToKeyParams() string { var s string return s } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Des3CbcSha1Kd) GetConfounderByteSize() int { return des.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Des3CbcSha1Kd) GetHMACBitLength() int { return e.GetHashFunc()().Size() * 8 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Des3CbcSha1Kd) GetCypherBlockBitLength() int { return des.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Des3CbcSha1Kd) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { if s2kparams != "" { return []byte{}, errors.New("s2kparams must be an empty string") } return rfc3961.DES3StringToKey(secret, salt, e) } // RandomToKey returns a key from the bytes provided. func (e Des3CbcSha1Kd) RandomToKey(b []byte) []byte { return rfc3961.DES3RandomToKey(b) } // DeriveRandom generates data needed for key generation. func (e Des3CbcSha1Kd) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { r, err := rfc3961.DeriveRandom(protocolKey, usage, e) return r, err } // DeriveKey derives a key from the protocol key based on the usage value. func (e Des3CbcSha1Kd) DeriveKey(protocolKey, usage []byte) ([]byte, error) { r, err := e.DeriveRandom(protocolKey, usage) if err != nil { return nil, err } return e.RandomToKey(r), nil } // EncryptData encrypts the data provided. func (e Des3CbcSha1Kd) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc3961.DES3EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Des3CbcSha1Kd) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc3961.DES3EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Des3CbcSha1Kd) DecryptData(key, data []byte) ([]byte, error) { return rfc3961.DES3DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Des3CbcSha1Kd) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc3961.DES3DecryptMessage(key, ciphertext, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e Des3CbcSha1Kd) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Des3CbcSha1Kd) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Des3CbcSha1Kd) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/crypto/des3-cbc-sha1-kd_test.go000066400000000000000000000062621427031340300204410ustar00rootroot00000000000000package crypto import ( "encoding/hex" "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestDes3CbcSha1Kd_DR_DK(t *testing.T) { t.Parallel() // Test vectors from RFC 3961 Appendix A3 var tests = []struct { key string usage string dr string dk string }{ {"dce06b1f64c857a11c3db57c51899b2cc1791008ce973b92", "0000000155", "935079d14490a75c3093c4a6e8c3b049c71e6ee705", "925179d04591a79b5d3192c4a7e9c289b049c71f6ee604cd"}, {"5e13d31c70ef765746578531cb51c15bf11ca82c97cee9f2", "00000001aa", "9f58e5a047d894101c469845d67ae3c5249ed812f2", "9e58e5a146d9942a101c469845d67a20e3c4259ed913f207"}, {"98e6fd8a04a4b6859b75a176540b9752bad3ecd610a252bc", "0000000155", "12fff90c773f956d13fc2ca0d0840349dbd39908eb", "13fef80d763e94ec6d13fd2ca1d085070249dad39808eabf"}, {"622aec25a2fe2cad7094680b7c64940280084c1a7cec92b5", "00000001aa", "f8debf05b097e7dc0603686aca35d91fd9a5516a70", "f8dfbf04b097e6d9dc0702686bcb3489d91fd9a4516b703e"}, {"d3f8298ccb166438dcb9b93ee5a7629286a491f838f802fb", "6b65726265726f73", "2270db565d2a3d64cfbfdc5305d4f778a6de42d9da", "2370da575d2a3da864cebfdc5204d56df779a7df43d9da43"}, {"c1081649ada74362e6a1459d01dfd30d67c2234c940704da", "0000000155", "348056ec98fcc517171d2b4d7a9493af482d999175", "348057ec98fdc48016161c2a4c7a943e92ae492c989175f7"}, {"5d154af238f46713155719d55e2f1f790dd661f279a7917c", "00000001aa", "a8818bc367dadacbe9a6c84627fb60c294b01215e5", "a8808ac267dada3dcbe9a7c84626fbc761c294b01315e5c1"}, {"798562e049852f57dc8c343ba17f2ca1d97394efc8adc443", "0000000155", "c813f88b3be2b2f75424ce9175fbc8483b88c8713a", "c813f88a3be3b334f75425ce9175fbe3c8493b89c8703b49"}, {"26dce334b545292f2feab9a8701a89a4b99eb9942cecd016", "00000001aa", "f58efc6f83f93e55e695fd252cf8fe59f7d5ba37ec", "f48ffd6e83f83e7354e694fd252cf83bfe58f7d5ba37ec5d"}, } for _, test := range tests { var e Des3CbcSha1Kd key, _ := hex.DecodeString(test.key) usage, _ := hex.DecodeString(test.usage) derivedRandom, err := e.DeriveRandom(key, usage) if err != nil { t.Fatal(fmt.Sprintf("Error in deriveRandom: %v", err)) } assert.Equal(t, test.dr, hex.EncodeToString(derivedRandom), "DR not as expected") derivedKey, err := e.DeriveKey(key, usage) if err != nil { t.Fatal(fmt.Sprintf("Error in deriveKey: %v", err)) } assert.Equal(t, test.dk, hex.EncodeToString(derivedKey), "DK not as expected") } } func TestDes3CbcSha1Kd_StringToKey(t *testing.T) { t.Parallel() var tests = []struct { salt string secret string key string }{ {"ATHENA.MIT.EDUraeburn", "password", "850bb51358548cd05e86768c313e3bfef7511937dcf72c3e"}, {"WHITEHOUSE.GOVdanny", "potatoe", "dfcd233dd0a43204ea6dc437fb15e061b02979c1f74f377a"}, {"EXAMPLE.COMbuckaroo", "penny", "6d2fcdf2d6fbbc3ddcadb5da5710a23489b0d3b69d5d9d4a"}, {"ATHENA.MIT.EDUJuri" + "\u0161" + "i" + "\u0107", "\u00DF", "16d5a40e1ce3bacb61b9dce00470324c831973a7b952feb0"}, {"EXAMPLE.COMpianist", "𝄞", "85763726585dbc1cce6ec43e1f751f07f1c4cbb098f40b19"}, } var e Des3CbcSha1Kd for _, test := range tests { key, err := e.StringToKey(test.secret, test.salt, "") if err != nil { t.Errorf("error in StringToKey: %v", err) } assert.Equal(t, test.key, hex.EncodeToString(key), "StringToKey not as expected") } } gokrb5-8.4.3/crypto/etype/000077500000000000000000000000001427031340300153625ustar00rootroot00000000000000gokrb5-8.4.3/crypto/etype/etype.go000066400000000000000000000021201427031340300170320ustar00rootroot00000000000000// Package etype provides the Kerberos Encryption Type interface package etype import "hash" // EType is the interface defining the Encryption Type. type EType interface { GetETypeID() int32 GetHashID() int32 GetKeyByteSize() int GetKeySeedBitLength() int GetDefaultStringToKeyParams() string StringToKey(string, salt, s2kparams string) ([]byte, error) RandomToKey(b []byte) []byte GetHMACBitLength() int GetMessageBlockByteSize() int EncryptData(key, data []byte) ([]byte, []byte, error) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) DecryptData(key, data []byte) ([]byte, error) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) GetCypherBlockBitLength() int GetConfounderByteSize() int DeriveKey(protocolKey, usage []byte) ([]byte, error) DeriveRandom(protocolKey, usage []byte) ([]byte, error) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool GetHashFunc() func() hash.Hash } gokrb5-8.4.3/crypto/rc4-hmac.go000066400000000000000000000101441427031340300161610ustar00rootroot00000000000000package crypto import ( "bytes" "crypto/hmac" "crypto/md5" "hash" "io" "golang.org/x/crypto/md4" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961" "gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // RC4HMAC implements Kerberos encryption type rc4-hmac type RC4HMAC struct { } // GetETypeID returns the EType ID number. func (e RC4HMAC) GetETypeID() int32 { return etypeID.RC4_HMAC } // GetHashID returns the checksum type ID number. func (e RC4HMAC) GetHashID() int32 { return chksumtype.KERB_CHECKSUM_HMAC_MD5 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e RC4HMAC) GetKeyByteSize() int { return 16 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e RC4HMAC) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e RC4HMAC) GetHashFunc() func() hash.Hash { return md5.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e RC4HMAC) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e RC4HMAC) GetDefaultStringToKeyParams() string { return "" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e RC4HMAC) GetConfounderByteSize() int { return 8 } // GetHMACBitLength returns the bit count size of the integrity hash. func (e RC4HMAC) GetHMACBitLength() int { return md5.Size * 8 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e RC4HMAC) GetCypherBlockBitLength() int { return 8 // doesn't really apply } // StringToKey returns a key derived from the string provided. func (e RC4HMAC) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { return rfc4757.StringToKey(secret) } // RandomToKey returns a key from the bytes provided. func (e RC4HMAC) RandomToKey(b []byte) []byte { r := bytes.NewReader(b) h := md4.New() io.Copy(h, r) return h.Sum(nil) } // EncryptData encrypts the data provided. func (e RC4HMAC) EncryptData(key, data []byte) ([]byte, []byte, error) { b, err := rfc4757.EncryptData(key, data, e) return []byte{}, b, err } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e RC4HMAC) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { b, err := rfc4757.EncryptMessage(key, message, usage, false, e) return []byte{}, b, err } // DecryptData decrypts the data provided. func (e RC4HMAC) DecryptData(key, data []byte) ([]byte, error) { return rfc4757.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e RC4HMAC) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc4757.DecryptMessage(key, ciphertext, usage, false, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e RC4HMAC) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc4757.HMAC(protocolKey, usage), nil } // DeriveRandom generates data needed for key generation. func (e RC4HMAC) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e RC4HMAC) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc4757.VerifyIntegrity(protocolKey, pt, ct, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e RC4HMAC) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return rfc4757.Checksum(protocolKey, usage, data) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e RC4HMAC) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { checksum, err := rfc4757.Checksum(protocolKey, usage, data) if err != nil { return false } return hmac.Equal(checksum, chksum) } gokrb5-8.4.3/crypto/rfc3961/000077500000000000000000000000001427031340300153315ustar00rootroot00000000000000gokrb5-8.4.3/crypto/rfc3961/encryption.go000066400000000000000000000101141427031340300200470ustar00rootroot00000000000000// Package rfc3961 provides encryption and checksum methods as specified in RFC 3961 package rfc3961 import ( "crypto/cipher" "crypto/des" "crypto/hmac" "crypto/rand" "errors" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" ) // DES3EncryptData encrypts the data provided using DES3 and methods specific to the etype provided. func DES3EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) { if len(key) != e.GetKeyByteSize() { return nil, nil, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } data, _ = common.ZeroPad(data, e.GetMessageBlockByteSize()) block, err := des.NewTripleDESCipher(key) if err != nil { return nil, nil, fmt.Errorf("error creating cipher: %v", err) } //RFC 3961: initial cipher state All bits zero ivz := make([]byte, des.BlockSize) ct := make([]byte, len(data)) mode := cipher.NewCBCEncrypter(block, ivz) mode.CryptBlocks(ct, data) return ct[len(ct)-e.GetMessageBlockByteSize():], ct, nil } // DES3EncryptMessage encrypts the message provided using DES3 and methods specific to the etype provided. // The encrypted data is concatenated with its integrity hash to create an encrypted message. func DES3EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) { //confounder c := make([]byte, e.GetConfounderByteSize()) _, err := rand.Read(c) if err != nil { return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err) } plainBytes := append(c, message...) plainBytes, _ = common.ZeroPad(plainBytes, e.GetMessageBlockByteSize()) // Derive key for encryption from usage var k []byte if usage != 0 { k, err = e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err) } } iv, b, err := e.EncryptData(k, plainBytes) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } // Generate and append integrity hash ih, err := common.GetIntegrityHash(plainBytes, key, usage, e) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } b = append(b, ih...) return iv, b, nil } // DES3DecryptData decrypts the data provided using DES3 and methods specific to the etype provided. func DES3DecryptData(key, data []byte, e etype.EType) ([]byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } if len(data) < des.BlockSize || len(data)%des.BlockSize != 0 { return []byte{}, errors.New("ciphertext is not a multiple of the block size") } block, err := des.NewTripleDESCipher(key) if err != nil { return []byte{}, fmt.Errorf("error creating cipher: %v", err) } pt := make([]byte, len(data)) ivz := make([]byte, des.BlockSize) mode := cipher.NewCBCDecrypter(block, ivz) mode.CryptBlocks(pt, data) return pt, nil } // DES3DecryptMessage decrypts the message provided using DES3 and methods specific to the etype provided. // The integrity of the message is also verified. func DES3DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) { //Derive the key k, err := e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return nil, fmt.Errorf("error deriving key: %v", err) } // Strip off the checksum from the end b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8]) if err != nil { return nil, fmt.Errorf("error decrypting: %v", err) } //Verify checksum if !e.VerifyIntegrity(key, ciphertext, b, usage) { return nil, errors.New("error decrypting: integrity verification failed") } //Remove the confounder bytes return b[e.GetConfounderByteSize():], nil } // VerifyIntegrity verifies the integrity of cipertext bytes ct. func VerifyIntegrity(key, ct, pt []byte, usage uint32, etype etype.EType) bool { h := make([]byte, etype.GetHMACBitLength()/8) copy(h, ct[len(ct)-etype.GetHMACBitLength()/8:]) expectedMAC, _ := common.GetIntegrityHash(pt, key, usage, etype) return hmac.Equal(h, expectedMAC) } gokrb5-8.4.3/crypto/rfc3961/keyDerivation.go000066400000000000000000000112711427031340300204770ustar00rootroot00000000000000package rfc3961 import ( "bytes" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" ) const ( prfconstant = "prf" ) // DeriveRandom implements the RFC 3961 section 5.1 defined DR function // // key: base key or protocol key. Likely to be a key from a keytab file. // // usage: a constant. // // n: block size in bits (not bytes) - note if you use something like aes.BlockSize this is in bytes. // // k: key length / key seed length in bits. Eg. for AES256 this value is 256. // // e: the encryption etype function to use. func DeriveRandom(key, usage []byte, e etype.EType) ([]byte, error) { n := e.GetCypherBlockBitLength() k := e.GetKeySeedBitLength() //Ensure the usage constant is at least the size of the cypher block size. Pass it through the nfold algorithm that will "stretch" it if needs be. nFoldUsage := Nfold(usage, n) //k-truncate implemented by creating a byte array the size of k (k is in bits hence /8) out := make([]byte, k/8) // Keep feeding the output back into the encryption function until it is no longer short than k. _, K, err := e.EncryptData(key, nFoldUsage) if err != nil { return out, err } for i := copy(out, K); i < len(out); { _, K, _ = e.EncryptData(key, K) i = i + copy(out[i:], K) } return out, nil } // DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods. func DeriveKey(protocolKey, usage []byte, e etype.EType) ([]byte, error) { r, err := e.DeriveRandom(protocolKey, usage) if err != nil { return nil, err } return e.RandomToKey(r), nil } // RandomToKey returns a key from the bytes provided according to the definition in RFC 3961. func RandomToKey(b []byte) []byte { return b } // DES3RandomToKey returns a key from the bytes provided according to the definition in RFC 3961 for DES3 etypes. func DES3RandomToKey(b []byte) []byte { r := fixWeakKey(stretch56Bits(b[:7])) r2 := fixWeakKey(stretch56Bits(b[7:14])) r = append(r, r2...) r3 := fixWeakKey(stretch56Bits(b[14:21])) r = append(r, r3...) return r } // DES3StringToKey returns a key derived from the string provided according to the definition in RFC 3961 for DES3 etypes. func DES3StringToKey(secret, salt string, e etype.EType) ([]byte, error) { s := secret + salt tkey := e.RandomToKey(Nfold([]byte(s), e.GetKeySeedBitLength())) return e.DeriveKey(tkey, []byte("kerberos")) } // PseudoRandom function as defined in RFC 3961 func PseudoRandom(key, b []byte, e etype.EType) ([]byte, error) { h := e.GetHashFunc()() h.Write(b) tmp := h.Sum(nil)[:e.GetMessageBlockByteSize()] k, err := e.DeriveKey(key, []byte(prfconstant)) if err != nil { return []byte{}, err } _, prf, err := e.EncryptData(k, tmp) if err != nil { return []byte{}, err } return prf, nil } func stretch56Bits(b []byte) []byte { d := make([]byte, len(b), len(b)) copy(d, b) var lb byte for i, v := range d { bv, nb := calcEvenParity(v) d[i] = nb if bv != 0 { lb = lb | (1 << uint(i+1)) } else { lb = lb &^ (1 << uint(i+1)) } } _, lb = calcEvenParity(lb) d = append(d, lb) return d } func calcEvenParity(b byte) (uint8, uint8) { lowestbit := b & 0x01 // c counter of 1s in the first 7 bits of the byte var c int // Iterate over the highest 7 bits (hence p starts at 1 not zero) and count the 1s. for p := 1; p < 8; p++ { v := b & (1 << uint(p)) if v != 0 { c++ } } if c%2 == 0 { //Even number of 1s so set parity to 1 b = b | 1 } else { //Odd number of 1s so set parity to 0 b = b &^ 1 } return lowestbit, b } func fixWeakKey(b []byte) []byte { if weak(b) { b[7] ^= 0xF0 } return b } func weak(b []byte) bool { // weak keys from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-67r1.pdf weakKeys := [4][]byte{ {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, {0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE}, {0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1}, {0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E}, } semiWeakKeys := [12][]byte{ {0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E}, {0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01}, {0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1}, {0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01}, {0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE}, {0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01}, {0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1}, {0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E}, {0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE}, {0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E}, {0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE}, {0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1}, } for _, k := range weakKeys { if bytes.Equal(b, k) { return true } } for _, k := range semiWeakKeys { if bytes.Equal(b, k) { return true } } return false } gokrb5-8.4.3/crypto/rfc3961/keyDerivation_test.go000066400000000000000000000027071427031340300215420ustar00rootroot00000000000000package rfc3961 import "testing" func TestFixWeakKey(t *testing.T) { var weakKeys = []struct { key []byte lastbyte byte }{ {[]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, byte(0xF1)}, {[]byte{0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE}, byte(0x0E)}, {[]byte{0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1}, byte(0x01)}, {[]byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E}, byte(0xFE)}, {[]byte{0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E}, byte(0xFE)}, {[]byte{0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01}, byte(0xF1)}, {[]byte{0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1}, byte(0x01)}, {[]byte{0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01}, byte(0xF1)}, {[]byte{0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE}, byte(0x0E)}, {[]byte{0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01}, byte(0xF1)}, {[]byte{0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1}, byte(0x01)}, {[]byte{0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E}, byte(0xFE)}, {[]byte{0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE}, byte(0x0E)}, {[]byte{0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E}, byte(0xFE)}, {[]byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE}, byte(0x0E)}, {[]byte{0xFE, 0x2F, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1}, byte(0xF1)}, // Non weak key } for i, k := range weakKeys { b := fixWeakKey(k.key) if b[7] != weakKeys[i].lastbyte { t.Errorf("key not fixed correctly %X - %X", b, weakKeys[i].lastbyte) } } } gokrb5-8.4.3/crypto/rfc3961/nfold.go000066400000000000000000000052021427031340300167610ustar00rootroot00000000000000package rfc3961 // Implementation of the n-fold algorithm as defined in RFC 3961. /* Credits This golang implementation of nfold used the following project for help with implementation detail. Although their source is in java it was helpful as a reference implementation of the RFC. You can find the source code of their open source project along with license information below. We acknowledge and are grateful to these developers for their contributions to open source Project: Apache Directory (http://http://directory.apache.org/) https://svn.apache.org/repos/asf/directory/apacheds/tags/1.5.1/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/crypto/encryption/NFold.java License: http://www.apache.org/licenses/LICENSE-2.0 */ // Nfold expands the key to ensure it is not smaller than one cipher block. // Defined in RFC 3961. // // m input bytes that will be "stretched" to the least common multiple of n bits and the bit length of m. func Nfold(m []byte, n int) []byte { k := len(m) * 8 //Get the lowest common multiple of the two bit sizes lcm := lcm(n, k) relicate := lcm / k var sumBytes []byte for i := 0; i < relicate; i++ { rotation := 13 * i sumBytes = append(sumBytes, rotateRight(m, rotation)...) } nfold := make([]byte, n/8) sum := make([]byte, n/8) for i := 0; i < lcm/n; i++ { for j := 0; j < n/8; j++ { sum[j] = sumBytes[j+(i*len(sum))] } nfold = onesComplementAddition(nfold, sum) } return nfold } func onesComplementAddition(n1, n2 []byte) []byte { numBits := len(n1) * 8 out := make([]byte, numBits/8) carry := 0 for i := numBits - 1; i > -1; i-- { n1b := getBit(&n1, i) n2b := getBit(&n2, i) s := n1b + n2b + carry if s == 0 || s == 1 { setBit(&out, i, s) carry = 0 } else if s == 2 { carry = 1 } else if s == 3 { setBit(&out, i, 1) carry = 1 } } if carry == 1 { carryArray := make([]byte, len(n1)) carryArray[len(carryArray)-1] = 1 out = onesComplementAddition(out, carryArray) } return out } func rotateRight(b []byte, step int) []byte { out := make([]byte, len(b)) bitLen := len(b) * 8 for i := 0; i < bitLen; i++ { v := getBit(&b, i) setBit(&out, (i+step)%bitLen, v) } return out } func lcm(x, y int) int { return (x * y) / gcd(x, y) } func gcd(x, y int) int { for y != 0 { x, y = y, x%y } return x } func getBit(b *[]byte, p int) int { pByte := p / 8 pBit := uint(p % 8) vByte := (*b)[pByte] vInt := int(vByte >> (8 - (pBit + 1)) & 0x0001) return vInt } func setBit(b *[]byte, p, v int) { pByte := p / 8 pBit := uint(p % 8) oldByte := (*b)[pByte] var newByte byte newByte = byte(v<<(8-(pBit+1))) | oldByte (*b)[pByte] = newByte } gokrb5-8.4.3/crypto/rfc3961/nfold_test.go000066400000000000000000000015071427031340300200240ustar00rootroot00000000000000package rfc3961 import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" ) func Test_nfold(t *testing.T) { t.Parallel() var tests = []struct { n int b []byte folded string }{ {64, []byte("012345"), "be072631276b1955"}, {56, []byte("password"), "78a07b6caf85fa"}, {64, []byte("Rough Consensus, and Running Code"), "bb6ed30870b7f0e0"}, {168, []byte("password"), "59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e"}, {192, []byte("MASSACHVSETTS INSTITVTE OF TECHNOLOGY"), "db3b0d8f0b061e603282b308a50841229ad798fab9540c1b"}, {168, []byte("Q"), "518a54a215a8452a518a54a215a8452a518a54a215"}, {168, []byte("ba"), "fb25d531ae8974499f52fd92ea9857c4ba24cf297e"}, } for _, test := range tests { assert.Equal(t, test.folded, hex.EncodeToString(Nfold(test.b, test.n)), "Folded not as expected") } } gokrb5-8.4.3/crypto/rfc3962/000077500000000000000000000000001427031340300153325ustar00rootroot00000000000000gokrb5-8.4.3/crypto/rfc3962/encryption.go000066400000000000000000000062221427031340300200550ustar00rootroot00000000000000// Package rfc3962 provides encryption and checksum methods as specified in RFC 3962 package rfc3962 import ( "crypto/rand" "errors" "fmt" "gopkg.in/jcmturner/aescts.v1" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" ) // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 3962. func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } ivz := make([]byte, e.GetCypherBlockBitLength()/8) return aescts.Encrypt(key, ivz, data) } // EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 3962. // The encrypted data is concatenated with its integrity hash to create an encrypted message. func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } //confounder c := make([]byte, e.GetConfounderByteSize()) _, err := rand.Read(c) if err != nil { return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err) } plainBytes := append(c, message...) // Derive key for encryption from usage var k []byte if usage != 0 { k, err = e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err) } } // Encrypt the data iv, b, err := e.EncryptData(k, plainBytes) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } // Generate and append integrity hash ih, err := common.GetIntegrityHash(plainBytes, key, usage, e) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } b = append(b, ih...) return iv, b, nil } // DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 3962. func DecryptData(key, data []byte, e etype.EType) ([]byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } ivz := make([]byte, e.GetCypherBlockBitLength()/8) return aescts.Decrypt(key, ivz, data) } // DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 3962. // The integrity of the message is also verified. func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) { //Derive the key k, err := e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return nil, fmt.Errorf("error deriving key: %v", err) } // Strip off the checksum from the end b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8]) if err != nil { return nil, err } //Verify checksum if !e.VerifyIntegrity(key, ciphertext, b, usage) { return nil, errors.New("integrity verification failed") } //Remove the confounder bytes return b[e.GetConfounderByteSize():], nil } gokrb5-8.4.3/crypto/rfc3962/keyDerivation.go000066400000000000000000000034451427031340300205040ustar00rootroot00000000000000package rfc3962 import ( "encoding/binary" "encoding/hex" "errors" "github.com/jcmturner/gofork/x/crypto/pbkdf2" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" ) const ( s2kParamsZero = 4294967296 ) // StringToKey returns a key derived from the string provided according to the definition in RFC 3961. func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) { i, err := S2KparamsToItertions(s2kparams) if err != nil { return nil, err } return StringToKeyIter(secret, salt, i, e) } // StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0 func StringToPBKDF2(secret, salt string, iterations int64, e etype.EType) []byte { return pbkdf2.Key64([]byte(secret), []byte(salt), iterations, int64(e.GetKeyByteSize()), e.GetHashFunc()) } // StringToKeyIter returns a key derived from the string provided according to the definition in RFC 3961. func StringToKeyIter(secret, salt string, iterations int64, e etype.EType) ([]byte, error) { tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e)) return e.DeriveKey(tkey, []byte("kerberos")) } // S2KparamsToItertions converts the string representation of iterations to an integer func S2KparamsToItertions(s2kparams string) (int64, error) { //The s2kparams string should be hex string representing 4 bytes //The 4 bytes represent a number in big endian order //If the value is zero then the number of iterations should be 4,294,967,296 (2^32) var i uint32 if len(s2kparams) != 8 { return int64(s2kParamsZero), errors.New("invalid s2kparams length") } b, err := hex.DecodeString(s2kparams) if err != nil { return int64(s2kParamsZero), errors.New("invalid s2kparams, cannot decode string to bytes") } i = binary.BigEndian.Uint32(b) return int64(i), nil } gokrb5-8.4.3/crypto/rfc4757/000077500000000000000000000000001427031340300153355ustar00rootroot00000000000000gokrb5-8.4.3/crypto/rfc4757/checksum.go000066400000000000000000000015141427031340300174670ustar00rootroot00000000000000package rfc4757 import ( "bytes" "crypto/hmac" "crypto/md5" "io" ) // Checksum returns a hash of the data in accordance with RFC 4757 func Checksum(key []byte, usage uint32, data []byte) ([]byte, error) { // Create hashing key s := append([]byte(`signaturekey`), byte(0x00)) //includes zero octet at end mac := hmac.New(md5.New, key) mac.Write(s) Ksign := mac.Sum(nil) // Format data tb := UsageToMSMsgType(usage) p := append(tb, data...) h := md5.New() rb := bytes.NewReader(p) _, err := io.Copy(h, rb) if err != nil { return []byte{}, err } tmp := h.Sum(nil) // Generate HMAC mac = hmac.New(md5.New, Ksign) mac.Write(tmp) return mac.Sum(nil), nil } // HMAC returns a keyed MD5 checksum of the data func HMAC(key []byte, data []byte) []byte { mac := hmac.New(md5.New, key) mac.Write(data) return mac.Sum(nil) } gokrb5-8.4.3/crypto/rfc4757/encryption.go000066400000000000000000000053651427031340300200670ustar00rootroot00000000000000// Package rfc4757 provides encryption and checksum methods as specified in RFC 4757 package rfc4757 import ( "crypto/hmac" "crypto/rand" "crypto/rc4" "errors" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" ) // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 4757. func EncryptData(key, data []byte, e etype.EType) ([]byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } rc4Cipher, err := rc4.NewCipher(key) if err != nil { return []byte{}, fmt.Errorf("error creating RC4 cipher: %v", err) } ed := make([]byte, len(data)) copy(ed, data) rc4Cipher.XORKeyStream(ed, ed) rc4Cipher.Reset() return ed, nil } // DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 4757. func DecryptData(key, data []byte, e etype.EType) ([]byte, error) { return EncryptData(key, data, e) } // EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 4757. // The encrypted data is concatenated with its RC4 header containing integrity checksum and confounder to create an encrypted message. func EncryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) { confounder := make([]byte, e.GetConfounderByteSize()) // size = 8 _, err := rand.Read(confounder) if err != nil { return []byte{}, fmt.Errorf("error generating confounder: %v", err) } k1 := key k2 := HMAC(k1, UsageToMSMsgType(usage)) toenc := append(confounder, data...) chksum := HMAC(k2, toenc) k3 := HMAC(k2, chksum) ed, err := EncryptData(k3, toenc, e) if err != nil { return []byte{}, fmt.Errorf("error encrypting data: %v", err) } msg := append(chksum, ed...) return msg, nil } // DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 4757. // The integrity of the message is also verified. func DecryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) { checksum := data[:e.GetHMACBitLength()/8] ct := data[e.GetHMACBitLength()/8:] _, k2, k3 := deriveKeys(key, checksum, usage, export) pt, err := DecryptData(k3, ct, e) if err != nil { return []byte{}, fmt.Errorf("error decrypting data: %v", err) } if !VerifyIntegrity(k2, pt, data, e) { return []byte{}, errors.New("integrity checksum incorrect") } return pt[e.GetConfounderByteSize():], nil } // VerifyIntegrity checks the integrity checksum of the data matches that calculated from the decrypted data. func VerifyIntegrity(key, pt, data []byte, e etype.EType) bool { chksum := HMAC(key, pt) return hmac.Equal(chksum, data[:e.GetHMACBitLength()/8]) } gokrb5-8.4.3/crypto/rfc4757/keyDerivation.go000066400000000000000000000016131427031340300205020ustar00rootroot00000000000000package rfc4757 import ( "bytes" "encoding/hex" "errors" "fmt" "io" "golang.org/x/crypto/md4" ) // StringToKey returns a key derived from the string provided according to the definition in RFC 4757. func StringToKey(secret string) ([]byte, error) { b := make([]byte, len(secret)*2, len(secret)*2) for i, r := range secret { u := fmt.Sprintf("%04x", r) c, err := hex.DecodeString(u) if err != nil { return []byte{}, errors.New("character could not be encoded") } // Swap round the two bytes to make little endian as we put into byte slice b[2*i] = c[1] b[2*i+1] = c[0] } r := bytes.NewReader(b) h := md4.New() _, err := io.Copy(h, r) if err != nil { return []byte{}, err } return h.Sum(nil), nil } func deriveKeys(key, checksum []byte, usage uint32, export bool) (k1, k2, k3 []byte) { k1 = key k2 = HMAC(k1, UsageToMSMsgType(usage)) k3 = HMAC(k2, checksum) return } gokrb5-8.4.3/crypto/rfc4757/keyDerivation_test.go000066400000000000000000000006511427031340300215420ustar00rootroot00000000000000package rfc4757 import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" ) const ( testPassword = "foo" testKey = "ac8e657f83df82beea5d43bdaf7800cc" ) func TestStringToKey(t *testing.T) { t.Parallel() kb, err := StringToKey(testPassword) if err != nil { t.Fatalf("Error deriving key from string: %v", err) } k := hex.EncodeToString(kb) assert.Equal(t, testKey, k, "Key not as expected") } gokrb5-8.4.3/crypto/rfc4757/msgtype.go000066400000000000000000000007731427031340300173630ustar00rootroot00000000000000package rfc4757 import "encoding/binary" // UsageToMSMsgType converts Kerberos key usage numbers to Microsoft message type encoded as a little-endian four byte slice. func UsageToMSMsgType(usage uint32) []byte { // Translate usage numbers to the Microsoft T numbers switch usage { case 3: usage = 8 case 9: usage = 8 case 23: usage = 13 } // Now convert to bytes tb := make([]byte, 4) // We force an int32 input so we can't go over 4 bytes binary.PutUvarint(tb, uint64(usage)) return tb } gokrb5-8.4.3/crypto/rfc8009/000077500000000000000000000000001427031340300153275ustar00rootroot00000000000000gokrb5-8.4.3/crypto/rfc8009/encryption.go000066400000000000000000000104771427031340300200610ustar00rootroot00000000000000// Package rfc8009 provides encryption and checksum methods as specified in RFC 8009 package rfc8009 import ( "crypto/aes" "crypto/hmac" "crypto/rand" "errors" "fmt" "gopkg.in/jcmturner/aescts.v1" "gopkg.in/jcmturner/gokrb5.v7/crypto/common" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 8009. func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } if len(key) != kl { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } ivz := make([]byte, aes.BlockSize) return aescts.Encrypt(key, ivz, data) } // EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 8009. // The encrypted data is concatenated with its integrity hash to create an encrypted message. func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } if len(key) != kl { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key)) } if len(key) != e.GetKeyByteSize() { } //confounder c := make([]byte, e.GetConfounderByteSize()) _, err := rand.Read(c) if err != nil { return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err) } plainBytes := append(c, message...) // Derive key for encryption from usage var k []byte if usage != 0 { k, err = e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err) } } // Encrypt the data iv, b, err := e.EncryptData(k, plainBytes) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } ivz := make([]byte, e.GetConfounderByteSize()) ih, err := GetIntegityHash(ivz, b, key, usage, e) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } b = append(b, ih...) return iv, b, nil } // DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 8009. func DecryptData(key, data []byte, e etype.EType) ([]byte, error) { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } if len(key) != kl { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key)) } ivz := make([]byte, aes.BlockSize) return aescts.Decrypt(key, ivz, data) } // DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 8009. // The integrity of the message is also verified. func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) { //Derive the key k, err := e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return nil, fmt.Errorf("error deriving key: %v", err) } // Strip off the checksum from the end b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8]) if err != nil { return nil, err } //Verify checksum if !e.VerifyIntegrity(key, ciphertext, b, usage) { return nil, errors.New("integrity verification failed") } //Remove the confounder bytes return b[e.GetConfounderByteSize():], nil } // GetIntegityHash returns a keyed integrity hash of the bytes provided as defined in RFC 8009 func GetIntegityHash(iv, c, key []byte, usage uint32, e etype.EType) ([]byte, error) { // Generate and append integrity hash // Rather than calculating the hash over the confounder and plaintext // it is calculated over the iv concatenated with the AES cipher output. ib := append(iv, c...) return common.GetIntegrityHash(ib, key, usage, e) } // VerifyIntegrity verifies the integrity of cipertext bytes ct. func VerifyIntegrity(key, ct []byte, usage uint32, etype etype.EType) bool { h := make([]byte, etype.GetHMACBitLength()/8) copy(h, ct[len(ct)-etype.GetHMACBitLength()/8:]) ivz := make([]byte, etype.GetConfounderByteSize()) ib := append(ivz, ct[:len(ct)-(etype.GetHMACBitLength()/8)]...) expectedMAC, _ := common.GetIntegrityHash(ib, key, usage, etype) return hmac.Equal(h, expectedMAC) } gokrb5-8.4.3/crypto/rfc8009/keyDerivation.go000066400000000000000000000101321427031340300204700ustar00rootroot00000000000000package rfc8009 import ( "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "golang.org/x/crypto/pbkdf2" "gopkg.in/jcmturner/gokrb5.v7/crypto/etype" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" ) const ( s2kParamsZero = 32768 ) // DeriveRandom for key derivation as defined in RFC 8009 func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) { h := e.GetHashFunc()() return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil } // DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods. // // https://tools.ietf.org/html/rfc8009#section-5 func DeriveKey(protocolKey, label []byte, e etype.EType) []byte { var context []byte var kl int // Key length is longer for aes256-cts-hmac-sha384-192 is it is a Ke or from StringToKey (where label is "kerberos") if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { switch label[len(label)-1] { case 0x73: // 0x73 is "s" so label could be kerberos meaning StringToKey so now check if the label is "kerberos" kerblabel := []byte("kerberos") if len(label) != len(kerblabel) { break } for i, b := range label { if b != kerblabel[i] { kl = e.GetKeySeedBitLength() break } } if kl == 0 { // This is StringToKey kl = 256 } case 0xAA: // This is a Ke kl = 256 } } if kl == 0 { kl = e.GetKeySeedBitLength() } return e.RandomToKey(KDF_HMAC_SHA2(protocolKey, label, context, kl, e)) } // RandomToKey returns a key from the bytes provided according to the definition in RFC 8009. func RandomToKey(b []byte) []byte { return b } // StringToKey returns a key derived from the string provided according to the definition in RFC 8009. func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) { i, err := S2KparamsToItertions(s2kparams) if err != nil { return nil, err } return StringToKeyIter(secret, salt, i, e) } // StringToKeyIter returns a key derived from the string provided according to the definition in RFC 8009. func StringToKeyIter(secret, salt string, iterations int, e etype.EType) ([]byte, error) { tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e)) return e.DeriveKey(tkey, []byte("kerberos")) } // StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0 func StringToPBKDF2(secret, salt string, iterations int, e etype.EType) []byte { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } return pbkdf2.Key([]byte(secret), []byte(salt), iterations, kl, e.GetHashFunc()) } // KDF_HMAC_SHA2 key derivation: https://tools.ietf.org/html/rfc8009#section-3 func KDF_HMAC_SHA2(protocolKey, label, context []byte, kl int, e etype.EType) []byte { //k: Length in bits of the key to be outputted, expressed in big-endian binary representation in 4 bytes. k := make([]byte, 4, 4) binary.BigEndian.PutUint32(k, uint32(kl)) c := make([]byte, 4, 4) binary.BigEndian.PutUint32(c, uint32(1)) c = append(c, label...) c = append(c, byte(0)) if len(context) > 0 { c = append(c, context...) } c = append(c, k...) mac := hmac.New(e.GetHashFunc(), protocolKey) mac.Write(c) return mac.Sum(nil)[:(kl / 8)] } // GetSaltP returns the salt value based on the etype name: https://tools.ietf.org/html/rfc8009#section-4 func GetSaltP(salt, ename string) string { b := []byte(ename) b = append(b, byte(0)) b = append(b, []byte(salt)...) return string(b) } // S2KparamsToItertions converts the string representation of iterations to an integer for RFC 8009. func S2KparamsToItertions(s2kparams string) (int, error) { var i uint32 if len(s2kparams) != 8 { return s2kParamsZero, errors.New("Invalid s2kparams length") } b, err := hex.DecodeString(s2kparams) if err != nil { return s2kParamsZero, errors.New("Invalid s2kparams, cannot decode string to bytes") } i = binary.BigEndian.Uint32(b) //buf := bytes.NewBuffer(b) //err = binary.Read(buf, binary.BigEndian, &i) if err != nil { return s2kParamsZero, errors.New("Invalid s2kparams, cannot convert to big endian int32") } return int(i), nil } gokrb5-8.4.3/examples/000077500000000000000000000000001427031340300145325ustar00rootroot00000000000000gokrb5-8.4.3/examples/example-AD.go000066400000000000000000000075721427031340300170110ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "fmt" "io/ioutil" "log" "net/http" "net/http/httptest" "os" goidentity "gopkg.in/jcmturner/goidentity.v3" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/spnego" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func main() { s := httpServer() defer s.Close() b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.KRB5_CONF_AD) cl := client.NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true)) httpRequest(s.URL, cl) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_USER_GOKRB5) kt = keytab.New() kt.Unmarshal(b) c, _ = config.NewConfigFromString(testdata.KRB5_CONF_AD) cl = client.NewClientWithKeytab("testuser2", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true)) httpRequest(s.URL, cl) //httpRequest("http://host.test.gokrb5/index.html") } func httpRequest(url string, cl *client.Client) { l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) err := cl.Login() if err != nil { l.Printf("Error on AS_REQ: %v\n", err) } r, _ := http.NewRequest("GET", url, nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.res.gokrb5") if err != nil { l.Printf("Error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { l.Printf("Request error: %v\n", err) } fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode) content, _ := ioutil.ReadAll(httpResp.Body) fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content) } func httpServer() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile) b, _ := hex.DecodeString(testdata.KEYTAB_SYSHTTP_RES_GOKRB5) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l))) return s } func testAppHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fmt.Fprint(w, "\n

TEST.GOKRB5 Handler

\n") if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser { if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok { fmt.Fprintf(w, "
  • Authenticed user: %s
  • \n", creds.UserName()) fmt.Fprintf(w, "
  • User's realm: %s
  • \n", creds.Domain()) fmt.Fprint(w, "
  • Authz Attributes (Group Memberships):
    • \n") for _, s := range creds.AuthzAttributes() { fmt.Fprintf(w, "
    • %v
    • \n", s) } fmt.Fprint(w, "
    \n") if ADCreds, ok := creds.Attributes()[credentials.AttributeKeyADCredentials].(credentials.ADCredentials); ok { // Now access the fields of the ADCredentials struct. For example: fmt.Fprintf(w, "
  • EffectiveName: %v
  • \n", ADCreds.EffectiveName) fmt.Fprintf(w, "
  • FullName: %v
  • \n", ADCreds.FullName) fmt.Fprintf(w, "
  • UserID: %v
  • \n", ADCreds.UserID) fmt.Fprintf(w, "
  • PrimaryGroupID: %v
  • \n", ADCreds.PrimaryGroupID) fmt.Fprintf(w, "
  • Group SIDs: %v
  • \n", ADCreds.GroupMembershipSIDs) fmt.Fprintf(w, "
  • LogOnTime: %v
  • \n", ADCreds.LogOnTime) fmt.Fprintf(w, "
  • LogOffTime: %v
  • \n", ADCreds.LogOffTime) fmt.Fprintf(w, "
  • PasswordLastSet: %v
  • \n", ADCreds.PasswordLastSet) fmt.Fprintf(w, "
  • LogonServer: %v
  • \n", ADCreds.LogonServer) fmt.Fprintf(w, "
  • LogonDomainName: %v
  • \n", ADCreds.LogonDomainName) fmt.Fprintf(w, "
  • LogonDomainID: %v
  • \n", ADCreds.LogonDomainID) } fmt.Fprint(w, "
") } } else { w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Authentication failed") } fmt.Fprint(w, "") return } gokrb5-8.4.3/examples/example.go000066400000000000000000000052261427031340300165210ustar00rootroot00000000000000//go:build examples // +build examples // Package examples provides simple examples of gokrb5 use. package main import ( "encoding/hex" "fmt" "io/ioutil" "log" "net/http" "net/http/httptest" "os" goidentity "gopkg.in/jcmturner/goidentity.v3" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/spnego" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func main() { s := httpServer() defer s.Close() b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) c.LibDefaults.NoAddresses = true cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) httpRequest(s.URL, cl) b, _ = hex.DecodeString(testdata.TESTUSER2_KEYTAB) kt = keytab.New() kt.Unmarshal(b) c, _ = config.NewConfigFromString(testdata.TEST_KRB5CONF) c.LibDefaults.NoAddresses = true cl = client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c) httpRequest(s.URL, cl) } func httpRequest(url string, cl *client.Client) { l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) err := cl.Login() if err != nil { l.Printf("Error on AS_REQ: %v\n", err) } r, _ := http.NewRequest("GET", url, nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { l.Printf("Error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { l.Printf("Request error: %v\n", err) } fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode) content, _ := ioutil.ReadAll(httpResp.Body) fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content) } func httpServer() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile) b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l))) return s } func testAppHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fmt.Fprint(w, "\n

TEST.GOKRB5 Handler

\n") if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser { if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok { fmt.Fprintf(w, "
  • Authenticed user: %s
  • \n", creds.UserName()) fmt.Fprintf(w, "
  • User's realm: %s
\n", creds.Domain()) } } else { w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Authentication failed") } fmt.Fprint(w, "") return } gokrb5-8.4.3/examples/httpClient.go000066400000000000000000000041021427031340300171740ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "fmt" "io/ioutil" "log" "net/http" "os" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/spnego" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) const ( port = ":9080" kRB5CONF = `[libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 127.0.0.1:88 admin_server = 127.0.0.1:749 default_domain = test.gokrb5 } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 ` ) func main() { l := log.New(os.Stderr, "GOKRB5 Client: ", log.LstdFlags) //defer profile.Start(profile.TraceProfile).Stop() // Load the keytab kb, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB) kt := keytab.New() err := kt.Unmarshal(kb) if err != nil { l.Fatalf("could not load client keytab: %v", err) } // Load the client krb5 config conf, err := config.NewConfigFromString(kRB5CONF) if err != nil { l.Fatalf("could not load krb5.conf: %v", err) } addr := os.Getenv("TEST_KDC_ADDR") if addr != "" { conf.Realms[0].KDC = []string{addr + ":88"} } // Create the client with the keytab cl := client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, conf, client.Logger(l), client.DisablePAFXFAST(true)) // Log in the client err = cl.Login() if err != nil { l.Fatalf("could not login client: %v", err) } // Form the request url := "http://localhost" + port r, err := http.NewRequest("GET", url, nil) if err != nil { l.Fatalf("could create request: %v", err) } spnegoCl := spnego.NewClient(cl, nil, "HTTP/host.test.gokrb5") // Make the request resp, err := spnegoCl.Do(r) if err != nil { l.Fatalf("error making request: %v", err) } b, err := ioutil.ReadAll(resp.Body) if err != nil { l.Fatalf("error reading response body: %v", err) } fmt.Println(string(b)) } gokrb5-8.4.3/examples/httpServer.go000066400000000000000000000026731427031340300172370ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "fmt" "log" "net/http" "os" goidentity "gopkg.in/jcmturner/goidentity.v3" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/spnego" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) const ( port = ":9080" ) func main() { //defer profile.Start(profile.TraceProfile).Stop() // Create logger l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile) // Load the service's keytab b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) // Create the application's specific handler th := http.HandlerFunc(testAppHandler) // Set up handler mappings wrapping in the SPNEGOKRB5Authenticate handler wrapper mux := http.NewServeMux() mux.Handle("/", spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l))) // Start up the web server log.Fatal(http.ListenAndServe(port, mux)) } // Simple application specific handler func testAppHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) ctx := r.Context() creds := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity) fmt.Fprintf(w, `

GOKRB5 Handler

  • Authenticed user: %s
  • User's realm: %s
  • Authn time: %v
  • Session ID: %s
    • `, creds.UserName(), creds.Domain(), creds.AuthTime(), creds.SessionID(), ) return } gokrb5-8.4.3/examples/longRunningClient.go000066400000000000000000000032411427031340300205200ustar00rootroot00000000000000package main import ( "encoding/hex" "log" "os" "time" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) const ( kRB5CONF = `[libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 ` ) func main() { l := log.New(os.Stderr, "GOKRB5 Client: ", log.LstdFlags) //defer profile.Start(profile.TraceProfile).Stop() // Load the keytab kb, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB) kt := keytab.New() err := kt.Unmarshal(kb) if err != nil { l.Fatalf("could not load client keytab: %v", err) } // Load the client krb5 config conf, err := config.NewConfigFromString(kRB5CONF) if err != nil { l.Fatalf("could not load krb5.conf: %v", err) } addr := os.Getenv("TEST_KDC_ADDR") if addr != "" { conf.Realms[0].KDC = []string{addr + ":88"} } // Create the client with the keytab cl := client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, conf, client.Logger(l), client.DisablePAFXFAST(true)) // Log in the client err = cl.Login() if err != nil { l.Fatalf("could not login client: %v", err) } for { _, _, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") if err != nil { l.Printf("failed to get service ticket: %v\n", err) } time.Sleep(time.Minute * 5) } } gokrb5-8.4.3/gokrb5.go000066400000000000000000000010621427031340300144330ustar00rootroot00000000000000/* Package gokrb5 provides a Kerberos 5 implementation for Go. This is a pure Go implementation and does not have dependencies on native libraries. Feature include: Server Side HTTP handler wrapper implements SPNEGO Kerberos authentication. HTTP handler wrapper decodes Microsoft AD PAC authorization data. Client Side Client that can authenticate to an SPNEGO Kerberos authenticated web service. Ability to change client's password. General Kerberos libraries for custom integration. Parsing Keytab files. Parsing krb5.conf files. */ package gokrb5 gokrb5-8.4.3/gssapi/000077500000000000000000000000001427031340300142025ustar00rootroot00000000000000gokrb5-8.4.3/gssapi/MICToken.go000066400000000000000000000135651427031340300161540ustar00rootroot00000000000000package gssapi import ( "bytes" "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/types" ) // RFC 4121, section 4.2.6.1 const ( // MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor. When not set, it indicates the sender is the context initiator MICTokenFlagSentByAcceptor = 1 << iota // MICTokenFlagSealed - this flag indicates confidentiality is provided for. It SHALL NOT be set in MIC tokens MICTokenFlagSealed // MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message MICTokenFlagAcceptorSubkey ) const ( micHdrLen = 16 // Length of the MIC Token's header ) // MICToken represents a GSS API MIC token, as defined in RFC 4121. // It contains the header fields, the payload (this is not transmitted) and // the checksum, and provides the logic for converting to/from bytes plus // computing and verifying checksums type MICToken struct { // const GSS Token ID: 0x0404 Flags byte // contains three flags: acceptor, sealed, acceptor subkey // const Filler: 0xFF 0xFF 0xFF 0xFF 0xFF SndSeqNum uint64 // sender's sequence number. big-endian Payload []byte // your data! :) Checksum []byte // checksum of { payload | header } } // Return the 2 bytes identifying a GSS API MIC token func getGSSMICTokenID() *[2]byte { return &[2]byte{0x04, 0x04} } // Return the filler bytes used in header func fillerBytes() *[5]byte { return &[5]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF} } // Marshal the MICToken into a byte slice. // The payload should have been set and the checksum computed, otherwise an error is returned. func (mt *MICToken) Marshal() ([]byte, error) { if mt.Checksum == nil { return nil, errors.New("checksum has not been set") } bytes := make([]byte, micHdrLen+len(mt.Checksum)) copy(bytes[0:micHdrLen], mt.getMICChecksumHeader()[:]) copy(bytes[micHdrLen:], mt.Checksum) return bytes, nil } // SetChecksum uses the passed encryption key and key usage to compute the checksum over the payload and // the header, and sets the Checksum field of this MICToken. // If the payload has not been set or the checksum has already been set, an error is returned. func (mt *MICToken) SetChecksum(key types.EncryptionKey, keyUsage uint32) error { if mt.Checksum != nil { return errors.New("checksum has already been computed") } checksum, err := mt.checksum(key, keyUsage) if err != nil { return err } mt.Checksum = checksum return nil } // Compute and return the checksum of this token, computed using the passed key and key usage. // Note: This will NOT update the struct's Checksum field. func (mt *MICToken) checksum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) { if mt.Payload == nil { return nil, errors.New("cannot compute checksum with uninitialized payload") } d := make([]byte, micHdrLen+len(mt.Payload)) copy(d[0:], mt.Payload) copy(d[len(mt.Payload):], mt.getMICChecksumHeader()) encType, err := crypto.GetEtype(key.KeyType) if err != nil { return nil, err } return encType.GetChecksumHash(key.KeyValue, d, keyUsage) } // Build a header suitable for a checksum computation func (mt *MICToken) getMICChecksumHeader() []byte { header := make([]byte, micHdrLen) copy(header[0:2], getGSSMICTokenID()[:]) header[2] = mt.Flags copy(header[3:8], fillerBytes()[:]) binary.BigEndian.PutUint64(header[8:16], mt.SndSeqNum) return header } // Verify computes the token's checksum with the provided key and usage, // and compares it to the checksum present in the token. // In case of any failure, (false, err) is returned, with err an explanatory error. func (mt *MICToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) { computed, err := mt.checksum(key, keyUsage) if err != nil { return false, err } if !hmac.Equal(computed, mt.Checksum) { return false, fmt.Errorf( "checksum mismatch. Computed: %s, Contained in token: %s", hex.EncodeToString(computed), hex.EncodeToString(mt.Checksum)) } return true, nil } // Unmarshal bytes into the corresponding MICToken. // If expectFromAcceptor is true we expect the token to have been emitted by the gss acceptor, // and will check the according flag, returning an error if the token does not match the expectation. func (mt *MICToken) Unmarshal(b []byte, expectFromAcceptor bool) error { if len(b) < micHdrLen { return errors.New("bytes shorter than header length") } if !bytes.Equal(getGSSMICTokenID()[:], b[0:2]) { return fmt.Errorf("wrong Token ID, Expected %s, was %s", hex.EncodeToString(getGSSMICTokenID()[:]), hex.EncodeToString(b[0:2])) } flags := b[2] isFromAcceptor := flags&MICTokenFlagSentByAcceptor != 0 if isFromAcceptor && !expectFromAcceptor { return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor") } if !isFromAcceptor && expectFromAcceptor { return errors.New("unexpected acceptor flag is not set: expecting a token from the acceptor, not in the initiator") } if !bytes.Equal(b[3:8], fillerBytes()[:]) { return fmt.Errorf("unexpected filler bytes: expecting %s, was %s", hex.EncodeToString(fillerBytes()[:]), hex.EncodeToString(b[3:8])) } mt.Flags = flags mt.SndSeqNum = binary.BigEndian.Uint64(b[8:16]) mt.Checksum = b[micHdrLen:] return nil } // NewInitiatorMICToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum. // Other flags are set to 0. // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier. // This is currently not supported. func NewInitiatorMICToken(payload []byte, key types.EncryptionKey) (*MICToken, error) { token := MICToken{ Flags: 0x00, SndSeqNum: 0, Payload: payload, } if err := token.SetChecksum(key, keyusage.GSSAPI_INITIATOR_SIGN); err != nil { return nil, err } return &token, nil } gokrb5-8.4.3/gssapi/MICToken_test.go000066400000000000000000000140171427031340300172040ustar00rootroot00000000000000package gssapi import ( "encoding/binary" "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( testMICPayload = "deadbeef" // What a kerberized server might send testMICChallengeFromAcceptor = "040401ffffffffff00000000575e85d6c34d12ba3e5b1b1310cd9cb3" // What an initiator client could reply testMICChallengeReplyFromInitiator = "040400ffffffffff00000000000000009649ca09d2f1bc51ff6e5ca3" acceptorSign = keyusage.GSSAPI_ACCEPTOR_SIGN initiatorSign = keyusage.GSSAPI_INITIATOR_SIGN ) func getMICChallengeReference() *MICToken { challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) return &MICToken{ Flags: MICTokenFlagSentByAcceptor, SndSeqNum: binary.BigEndian.Uint64(challenge[8:16]), Payload: nil, Checksum: challenge[16:], } } func getMICChallengeReferenceNoChksum() *MICToken { c := getMICChallengeReference() c.Checksum = nil return c } func getMICResponseReference() *MICToken { response, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) return &MICToken{ Flags: 0x00, SndSeqNum: 0, Payload: nil, Checksum: response[16:], } } func getMICResponseReferenceNoChkSum() *MICToken { r := getMICResponseReference() r.Checksum = nil return r } func TestUnmarshal_MICChallenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken err := mt.Unmarshal(challenge, true) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getMICChallengeReference(), &mt, "Token not decoded as expected.") } func TestUnmarshalFailure_MICChallenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken err := mt.Unmarshal(challenge, false) assert.NotNil(t, err, "Expected error did not occur: a message from the acceptor cannot be expected to be sent from the initiator.") assert.Nil(t, mt.Payload, "Token fields should not have been initialised") assert.Nil(t, mt.Checksum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), mt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint64(0), mt.SndSeqNum, "Token fields should not have been initialised") } func TestUnmarshal_MICChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) var mt MICToken err := mt.Unmarshal(response, false) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getMICResponseReference(), &mt, "Token not decoded as expected.") } func TestUnmarshalFailure_MICChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) var mt MICToken err := mt.Unmarshal(response, true) assert.NotNil(t, err, "Expected error did not occur: a message from the initiator cannot be expected to be sent from the acceptor.") assert.Nil(t, mt.Payload, "Token fields should not have been initialised") assert.Nil(t, mt.Checksum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), mt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint64(0), mt.SndSeqNum, "Token fields should not have been initialised") } func TestMICChallengeChecksumVerification(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken mt.Unmarshal(challenge, true) mt.Payload, _ = hex.DecodeString(testMICPayload) challengeOk, cErr := mt.Verify(getSessionKey(), acceptorSign) assert.Nil(t, cErr, "Error occurred during checksum verification.") assert.True(t, challengeOk, "Checksum verification failed.") } func TestMICResponseChecksumVerification(t *testing.T) { t.Parallel() reply, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) var mt MICToken mt.Unmarshal(reply, false) mt.Payload, _ = hex.DecodeString(testMICPayload) replyOk, rErr := mt.Verify(getSessionKey(), initiatorSign) assert.Nil(t, rErr, "Error occurred during checksum verification.") assert.True(t, replyOk, "Checksum verification failed.") } func TestMICChecksumVerificationFailure(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken mt.Unmarshal(challenge, true) // Test a failure with the correct key but wrong keyusage: challengeOk, cErr := mt.Verify(getSessionKey(), initiatorSign) assert.NotNil(t, cErr, "Expected error did not occur.") assert.False(t, challengeOk, "Checksum verification succeeded when it should have failed.") wrongKeyVal, _ := hex.DecodeString("14f9bde6b50ec508201a97f74c4effff") badKey := types.EncryptionKey{ KeyType: sessionKeyType, KeyValue: wrongKeyVal, } // Test a failure with the wrong key but correct keyusage: wrongKeyOk, wkErr := mt.Verify(badKey, acceptorSign) assert.NotNil(t, wkErr, "Expected error did not occur.") assert.False(t, wrongKeyOk, "Checksum verification succeeded when it should have failed.") } func TestMarshal_MICChallenge(t *testing.T) { t.Parallel() bytes, _ := getMICChallengeReference().Marshal() assert.Equal(t, testMICChallengeFromAcceptor, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_MICChallengeReply(t *testing.T) { t.Parallel() bytes, _ := getMICResponseReference().Marshal() assert.Equal(t, testMICChallengeReplyFromInitiator, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_MICFailures(t *testing.T) { t.Parallel() noChkSum := getMICResponseReferenceNoChkSum() chkBytes, chkErr := noChkSum.Marshal() assert.Nil(t, chkBytes, "No bytes should be returned.") assert.NotNil(t, chkErr, "Expected an error as no checksum was set") } func TestNewInitiatorMICTokenSignatureAndMarshalling(t *testing.T) { t.Parallel() bytes, _ := hex.DecodeString(testMICPayload) token, tErr := NewInitiatorMICToken(bytes, getSessionKey()) token.Payload = nil assert.Nil(t, tErr, "Unexpected error.") assert.Equal(t, getMICResponseReference(), token, "Token failed to be marshalled to the expected bytes.") } gokrb5-8.4.3/gssapi/README.md000066400000000000000000000024461427031340300154670ustar00rootroot00000000000000# Notes on GSS-API Negotiation Mechanism https://tools.ietf.org/html/rfc4178 Client sends an initial negotiation message to the server which specifies the list of mechanisms the client can support in order of decreasing preference. This message is generated with the ``NewNegTokenInitKrb5`` method. The message generated by this function specifies only a kerberos v5 mechanism is supported. The RFC states that this message can optionally contain the initial mechanism token for the preferred mechanism (KRB5 in this case) of the client. The ``NewNegTokenInitKrb5`` includes this in the message. The server side responds to this message with a one of four messages: | Message Type/State | Description | |--------------------|-------------| | accept-completed | indicates that the initiator-selected mechanism was acceptable to the target, and that the security mechanism token embedded in the first negotiation message was sufficient to complete the authentication | | accept-incomplete | At least one more message is needed from the client to establish security context. | | reject | Negotiation is being terminated. | | request-mic | (this state can only be present in the first reply message from the target) indicates that the MIC token exchange is REQUIRED if per-message integrity services are available |gokrb5-8.4.3/gssapi/contextFlags.go000066400000000000000000000010321427031340300171660ustar00rootroot00000000000000package gssapi import "github.com/jcmturner/gofork/encoding/asn1" // GSS-API context flags assigned numbers. const ( ContextFlagDeleg = 1 ContextFlagMutual = 2 ContextFlagReplay = 4 ContextFlagSequence = 8 ContextFlagConf = 16 ContextFlagInteg = 32 ContextFlagAnon = 64 ) // ContextFlags flags for GSSAPI type ContextFlags asn1.BitString // NewContextFlags creates a new ContextFlags instance. func NewContextFlags() ContextFlags { var c ContextFlags c.BitLength = 32 c.Bytes = make([]byte, 4) return c } gokrb5-8.4.3/gssapi/gssapi.go000066400000000000000000000155401427031340300160240ustar00rootroot00000000000000// Package gssapi implements Generic Security Services Application Program Interface required for SPNEGO kerberos authentication. package gssapi import ( "context" "fmt" "github.com/jcmturner/gofork/encoding/asn1" ) // GSS-API OID names const ( // GSS-API OID names OIDKRB5 OIDName = "KRB5" // MechType OID for Kerberos 5 OIDMSLegacyKRB5 OIDName = "MSLegacyKRB5" // MechType OID for Kerberos 5 OIDSPNEGO OIDName = "SPNEGO" ) // GSS-API status values const ( StatusBadBindings = 1 << iota StatusBadMech StatusBadName StatusBadNameType StatusBadStatus StatusBadSig StatusBadMIC StatusContextExpired StatusCredentialsExpired StatusDefectiveCredential StatusDefectiveToken StatusFailure StatusNoContext StatusNoCred StatusBadQOP StatusUnauthorized StatusUnavailable StatusDuplicateElement StatusNameNotMN StatusComplete StatusContinueNeeded StatusDuplicateToken StatusOldToken StatusUnseqToken StatusGapToken ) // ContextToken is an interface for a GSS-API context token. type ContextToken interface { Marshal() ([]byte, error) Unmarshal(b []byte) error Verify() (bool, Status) Context() context.Context } /* CREDENTIAL MANAGEMENT GSS_Acquire_cred acquire credentials for use GSS_Release_cred release credentials after use GSS_Inquire_cred display information about credentials GSS_Add_cred construct credentials incrementally GSS_Inquire_cred_by_mech display per-mechanism credential information CONTEXT-LEVEL CALLS GSS_Init_sec_context initiate outbound security context GSS_Accept_sec_context accept inbound security context GSS_Delete_sec_context flush context when no longer needed GSS_Process_context_token process received control token on context GSS_Context_time indicate validity time remaining on context GSS_Inquire_context display information about context GSS_Wrap_size_limit determine GSS_Wrap token size limit GSS_Export_sec_context transfer context to other process GSS_Import_sec_context import transferred context PER-MESSAGE CALLS GSS_GetMIC apply integrity check, receive as token separate from message GSS_VerifyMIC validate integrity check token along with message GSS_Wrap sign, optionally encrypt, encapsulate GSS_Unwrap decapsulate, decrypt if needed, validate integrity check SUPPORT CALLS GSS_Display_status translate status codes to printable form GSS_Indicate_mechs indicate mech_types supported on local system GSS_Compare_name compare two names for equality GSS_Display_name translate name to printable form GSS_Import_name convert printable name to normalized form GSS_Release_name free storage of normalized-form name GSS_Release_buffer free storage of general GSS-allocated object GSS_Release_OID_set free storage of OID set object GSS_Create_empty_OID_set create empty OID set GSS_Add_OID_set_member add member to OID set GSS_Test_OID_set_member test if OID is member of OID set GSS_Inquire_names_for_mech indicate name types supported by mechanism GSS_Inquire_mechs_for_name indicates mechanisms supporting name type GSS_Canonicalize_name translate name to per-mechanism form GSS_Export_name externalize per-mechanism name GSS_Duplicate_name duplicate name object */ // Mechanism is the GSS-API interface for authentication mechanisms. type Mechanism interface { OID() asn1.ObjectIdentifier AcquireCred() error // acquire credentials for use (eg. AS exchange for KRB5) InitSecContext() (ContextToken, error) // initiate outbound security context (eg TGS exchange builds AP_REQ to go into ContextToken to send to service) AcceptSecContext(ct ContextToken) (bool, context.Context, Status) // service verifies the token server side to establish a context MIC() MICToken // apply integrity check, receive as token separate from message VerifyMIC(mt MICToken) (bool, error) // validate integrity check token along with message Wrap(msg []byte) WrapToken // sign, optionally encrypt, encapsulate Unwrap(wt WrapToken) []byte // decapsulate, decrypt if needed, validate integrity check } // OIDName is the type for defined GSS-API OIDs. type OIDName string // OID returns the OID for the provided OID name. func OID(o OIDName) asn1.ObjectIdentifier { switch o { case OIDSPNEGO: return asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2} case OIDKRB5: return asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2} case OIDMSLegacyKRB5: return asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2} } return asn1.ObjectIdentifier{} } // Status is the GSS-API status and implements the error interface. type Status struct { Code int Message string } // Error returns the Status description. func (s Status) Error() string { var str string switch s.Code { case StatusBadBindings: str = "channel binding mismatch" case StatusBadMech: str = "unsupported mechanism requested" case StatusBadName: str = "invalid name provided" case StatusBadNameType: str = "name of unsupported type provided" case StatusBadStatus: str = "invalid input status selector" case StatusBadSig: str = "token had invalid integrity check" case StatusBadMIC: str = "preferred alias for GSS_S_BAD_SIG" case StatusContextExpired: str = "specified security context expired" case StatusCredentialsExpired: str = "expired credentials detected" case StatusDefectiveCredential: str = "defective credential detected" case StatusDefectiveToken: str = "defective token detected" case StatusFailure: str = "failure, unspecified at GSS-API level" case StatusNoContext: str = "no valid security context specified" case StatusNoCred: str = "no valid credentials provided" case StatusBadQOP: str = "unsupported QOP valu" case StatusUnauthorized: str = "operation unauthorized" case StatusUnavailable: str = "operation unavailable" case StatusDuplicateElement: str = "duplicate credential element requested" case StatusNameNotMN: str = "name contains multi-mechanism elements" case StatusComplete: str = "normal completion" case StatusContinueNeeded: str = "continuation call to routine required" case StatusDuplicateToken: str = "duplicate per-message token detected" case StatusOldToken: str = "timed-out per-message token detected" case StatusUnseqToken: str = "reordered (early) per-message token detected" case StatusGapToken: str = "skipped predecessor token(s) detected" default: str = "unknown GSS-API error status" } if s.Message != "" { return fmt.Sprintf("%s: %s", str, s.Message) } return str } gokrb5-8.4.3/gssapi/gssapi_test.go000066400000000000000000000010111427031340300170470ustar00rootroot00000000000000package gssapi import ( "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" ) func TestOID(t *testing.T) { var tests = []struct { name OIDName oid []int }{ {OIDMSLegacyKRB5, []int{1, 2, 840, 48018, 1, 2, 2}}, {OIDKRB5, []int{1, 2, 840, 113554, 1, 2, 2}}, {OIDSPNEGO, []int{1, 3, 6, 1, 5, 5, 2}}, } for _, tst := range tests { oid := asn1.ObjectIdentifier(tst.oid) assert.True(t, oid.Equal(OID(tst.name)), "OID value not as expected for %s", tst.name) } } gokrb5-8.4.3/gssapi/wrapToken.go000066400000000000000000000154431427031340300165120ustar00rootroot00000000000000package gssapi import ( "bytes" "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/types" ) // RFC 4121, section 4.2.6.2 const ( // HdrLen is the length of the Wrap Token's header HdrLen = 16 // FillerByte is a filler in the WrapToken structure FillerByte byte = 0xFF ) // WrapToken represents a GSS API Wrap token, as defined in RFC 4121. // It contains the header fields, the payload and the checksum, and provides // the logic for converting to/from bytes plus computing and verifying checksums type WrapToken struct { // const GSS Token ID: 0x0504 Flags byte // contains three flags: acceptor, sealed, acceptor subkey // const Filler: 0xFF EC uint16 // checksum length. big-endian RRC uint16 // right rotation count. big-endian SndSeqNum uint64 // sender's sequence number. big-endian Payload []byte // your data! :) CheckSum []byte // authenticated checksum of { payload | header } } // Return the 2 bytes identifying a GSS API Wrap token func getGssWrapTokenId() *[2]byte { return &[2]byte{0x05, 0x04} } // Marshal the WrapToken into a byte slice. // The payload should have been set and the checksum computed, otherwise an error is returned. func (wt *WrapToken) Marshal() ([]byte, error) { if wt.CheckSum == nil { return nil, errors.New("checksum has not been set") } if wt.Payload == nil { return nil, errors.New("payload has not been set") } pldOffset := HdrLen // Offset of the payload in the token chkSOffset := HdrLen + len(wt.Payload) // Offset of the checksum in the token bytes := make([]byte, chkSOffset+int(wt.EC)) copy(bytes[0:], getGssWrapTokenId()[:]) bytes[2] = wt.Flags bytes[3] = FillerByte binary.BigEndian.PutUint16(bytes[4:6], wt.EC) binary.BigEndian.PutUint16(bytes[6:8], wt.RRC) binary.BigEndian.PutUint64(bytes[8:16], wt.SndSeqNum) copy(bytes[pldOffset:], wt.Payload) copy(bytes[chkSOffset:], wt.CheckSum) return bytes, nil } // SetCheckSum uses the passed encryption key and key usage to compute the checksum over the payload and // the header, and sets the CheckSum field of this WrapToken. // If the payload has not been set or the checksum has already been set, an error is returned. func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error { if wt.Payload == nil { return errors.New("payload has not been set") } if wt.CheckSum != nil { return errors.New("checksum has already been computed") } chkSum, cErr := wt.computeCheckSum(key, keyUsage) if cErr != nil { return cErr } wt.CheckSum = chkSum return nil } // ComputeCheckSum computes and returns the checksum of this token, computed using the passed key and key usage. // Note: This will NOT update the struct's Checksum field. func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) { if wt.Payload == nil { return nil, errors.New("cannot compute checksum with uninitialized payload") } // Build a slice containing { payload | header } checksumMe := make([]byte, HdrLen+len(wt.Payload)) copy(checksumMe[0:], wt.Payload) copy(checksumMe[len(wt.Payload):], getChecksumHeader(wt.Flags, wt.SndSeqNum)) encType, err := crypto.GetEtype(key.KeyType) if err != nil { return nil, err } return encType.GetChecksumHash(key.KeyValue, checksumMe, keyUsage) } // Build a header suitable for a checksum computation func getChecksumHeader(flags byte, senderSeqNum uint64) []byte { header := make([]byte, 16) copy(header[0:], []byte{0x05, 0x04, flags, 0xFF, 0x00, 0x00, 0x00, 0x00}) binary.BigEndian.PutUint64(header[8:], senderSeqNum) return header } // Verify computes the token's checksum with the provided key and usage, // and compares it to the checksum present in the token. // In case of any failure, (false, Err) is returned, with Err an explanatory error. func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) { computed, cErr := wt.computeCheckSum(key, keyUsage) if cErr != nil { return false, cErr } if !hmac.Equal(computed, wt.CheckSum) { return false, fmt.Errorf( "checksum mismatch. Computed: %s, Contained in token: %s", hex.EncodeToString(computed), hex.EncodeToString(wt.CheckSum)) } return true, nil } // Unmarshal bytes into the corresponding WrapToken. // If expectFromAcceptor is true, we expect the token to have been emitted by the gss acceptor, // and will check the according flag, returning an error if the token does not match the expectation. func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error { // Check if we can read a whole header if len(b) < 16 { return errors.New("bytes shorter than header length") } // Is the Token ID correct? if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) { return fmt.Errorf("wrong Token ID. Expected %s, was %s", hex.EncodeToString(getGssWrapTokenId()[:]), hex.EncodeToString(b[0:2])) } // Check the acceptor flag flags := b[2] isFromAcceptor := flags&0x01 == 1 if isFromAcceptor && !expectFromAcceptor { return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor") } if !isFromAcceptor && expectFromAcceptor { return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator") } // Check the filler byte if b[3] != FillerByte { return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4])) } checksumL := binary.BigEndian.Uint16(b[4:6]) // Sanity check on the checksum length if int(checksumL) > len(b)-HdrLen { return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL) } wt.Flags = flags wt.EC = checksumL wt.RRC = binary.BigEndian.Uint16(b[6:8]) wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16]) wt.Payload = b[16 : len(b)-int(checksumL)] wt.CheckSum = b[len(b)-int(checksumL):] return nil } // NewInitiatorWrapToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum. // Other flags are set to 0, and the RRC and sequence number are initialized to 0. // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier. // This is currently not supported. func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) { encType, err := crypto.GetEtype(key.KeyType) if err != nil { return nil, err } token := WrapToken{ Flags: 0x00, // all zeroed out (this is a token sent by the initiator) // Checksum size: length of output of the HMAC function, in bytes. EC: uint16(encType.GetHMACBitLength() / 8), RRC: 0, SndSeqNum: 0, Payload: payload, } if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil { return nil, err } return &token, nil } gokrb5-8.4.3/gssapi/wrapToken_test.go000066400000000000000000000153201427031340300175430ustar00rootroot00000000000000package gssapi import ( "encoding/binary" "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( // What a kerberized server might send testChallengeFromAcceptor = "050401ff000c000000000000575e85d601010000853b728d5268525a1386c19f" // What an initiator client could reply testChallengeReplyFromInitiator = "050400ff000c000000000000000000000101000079a033510b6f127212242b97" // session key used to sign the tokens above sessionKey = "14f9bde6b50ec508201a97f74c4e5bd3" sessionKeyType = 17 acceptorSeal = keyusage.GSSAPI_ACCEPTOR_SEAL initiatorSeal = keyusage.GSSAPI_INITIATOR_SEAL ) func getSessionKey() types.EncryptionKey { key, _ := hex.DecodeString(sessionKey) return types.EncryptionKey{ KeyType: sessionKeyType, KeyValue: key, } } func getChallengeReference() *WrapToken { challenge, _ := hex.DecodeString(testChallengeFromAcceptor) return &WrapToken{ Flags: 0x01, EC: 12, RRC: 0, SndSeqNum: binary.BigEndian.Uint64(challenge[8:16]), Payload: []byte{0x01, 0x01, 0x00, 0x00}, CheckSum: challenge[20:32], } } func getChallengeReferenceNoChksum() *WrapToken { c := getChallengeReference() c.CheckSum = nil return c } func getResponseReference() *WrapToken { response, _ := hex.DecodeString(testChallengeReplyFromInitiator) return &WrapToken{ Flags: 0x00, EC: 12, RRC: 0, SndSeqNum: 0, Payload: []byte{0x01, 0x01, 0x00, 0x00}, CheckSum: response[20:32], } } func getResponseReferenceNoChkSum() *WrapToken { r := getResponseReference() r.CheckSum = nil return r } func TestUnmarshal_Challenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken err := wt.Unmarshal(challenge, true) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getChallengeReference(), &wt, "Token not decoded as expected.") } func TestUnmarshalFailure_Challenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken err := wt.Unmarshal(challenge, false) assert.NotNil(t, err, "Expected error did not occur: a message from the acceptor cannot be expected to be sent from the initiator.") assert.Nil(t, wt.Payload, "Token fields should not have been initialised") assert.Nil(t, wt.CheckSum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), wt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.EC, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.RRC, "Token fields should not have been initialised") assert.Equal(t, uint64(0), wt.SndSeqNum, "Token fields should not have been initialised") } func TestUnmarshal_ChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testChallengeReplyFromInitiator) var wt WrapToken err := wt.Unmarshal(response, false) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getResponseReference(), &wt, "Token not decoded as expected.") } func TestUnmarshalFailure_ChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testChallengeReplyFromInitiator) var wt WrapToken err := wt.Unmarshal(response, true) assert.NotNil(t, err, "Expected error did not occur: a message from the initiator cannot be expected to be sent from the acceptor.") assert.Nil(t, wt.Payload, "Token fields should not have been initialised") assert.Nil(t, wt.CheckSum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), wt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.EC, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.RRC, "Token fields should not have been initialised") assert.Equal(t, uint64(0), wt.SndSeqNum, "Token fields should not have been initialised") } func TestChallengeChecksumVerification(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken wt.Unmarshal(challenge, true) challengeOk, cErr := wt.Verify(getSessionKey(), acceptorSeal) assert.Nil(t, cErr, "Error occurred during checksum verification.") assert.True(t, challengeOk, "Checksum verification failed.") } func TestResponseChecksumVerification(t *testing.T) { t.Parallel() reply, _ := hex.DecodeString(testChallengeReplyFromInitiator) var wt WrapToken wt.Unmarshal(reply, false) replyOk, rErr := wt.Verify(getSessionKey(), initiatorSeal) assert.Nil(t, rErr, "Error occurred during checksum verification.") assert.True(t, replyOk, "Checksum verification failed.") } func TestChecksumVerificationFailure(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken wt.Unmarshal(challenge, true) // Test a failure with the correct key but wrong keyusage: challengeOk, cErr := wt.Verify(getSessionKey(), initiatorSeal) assert.NotNil(t, cErr, "Expected error did not occur.") assert.False(t, challengeOk, "Checksum verification succeeded when it should have failed.") wrongKeyVal, _ := hex.DecodeString("14f9bde6b50ec508201a97f74c4effff") badKey := types.EncryptionKey{ KeyType: sessionKeyType, KeyValue: wrongKeyVal, } // Test a failure with the wrong key but correct keyusage: wrongKeyOk, wkErr := wt.Verify(badKey, acceptorSeal) assert.NotNil(t, wkErr, "Expected error did not occur.") assert.False(t, wrongKeyOk, "Checksum verification succeeded when it should have failed.") } func TestMarshal_Challenge(t *testing.T) { t.Parallel() bytes, _ := getChallengeReference().Marshal() assert.Equal(t, testChallengeFromAcceptor, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_ChallengeReply(t *testing.T) { t.Parallel() bytes, _ := getResponseReference().Marshal() assert.Equal(t, testChallengeReplyFromInitiator, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_Failures(t *testing.T) { t.Parallel() noChkSum := getResponseReferenceNoChkSum() chkBytes, chkErr := noChkSum.Marshal() assert.Nil(t, chkBytes, "No bytes should be returned.") assert.NotNil(t, chkErr, "Expected an error as no checksum was set") noPayload := getResponseReference() noPayload.Payload = nil pldBytes, pldErr := noPayload.Marshal() assert.Nil(t, pldBytes, "No bytes should be returned.") assert.NotNil(t, pldErr, "Expected an error as no checksum was set") } func TestNewInitiatorTokenSignatureAndMarshalling(t *testing.T) { t.Parallel() token, tErr := NewInitiatorWrapToken([]byte{0x01, 0x01, 0x00, 0x00}, getSessionKey()) assert.Nil(t, tErr, "Unexpected error.") assert.Equal(t, getResponseReference(), token, "Token failed to be marshalled to the expected bytes.") } gokrb5-8.4.3/iana/000077500000000000000000000000001427031340300136245ustar00rootroot00000000000000gokrb5-8.4.3/iana/addrtype/000077500000000000000000000000001427031340300154405ustar00rootroot00000000000000gokrb5-8.4.3/iana/addrtype/constants.go000066400000000000000000000005221427031340300200020ustar00rootroot00000000000000// Package addrtype provides Address type assigned numbers. package addrtype // Address type IDs. const ( IPv4 int32 = 2 Directional int32 = 3 ChaosNet int32 = 5 XNS int32 = 6 ISO int32 = 7 DECNETPhaseIV int32 = 12 AppleTalkDDP int32 = 16 NetBios int32 = 20 IPv6 int32 = 24 ) gokrb5-8.4.3/iana/adtype/000077500000000000000000000000001427031340300151125ustar00rootroot00000000000000gokrb5-8.4.3/iana/adtype/constants.go000066400000000000000000000014711427031340300174600ustar00rootroot00000000000000// Package adtype provides Authenticator type assigned numbers. package adtype // Authenticator type IDs. const ( ADIfRelevant int32 = 1 ADIntendedForServer int32 = 2 ADIntendedForApplicationClass int32 = 3 ADKDCIssued int32 = 4 ADAndOr int32 = 5 ADMandatoryTicketExtensions int32 = 6 ADInTicketExtensions int32 = 7 ADMandatoryForKDC int32 = 8 OSFDCE int32 = 64 SESAME int32 = 65 ADOSFDCEPKICertID int32 = 66 ADAuthenticationStrength int32 = 70 ADFXFastArmor int32 = 71 ADFXFastUsed int32 = 72 ADWin2KPAC int32 = 128 ADEtypeNegotiation int32 = 129 //Reserved values 9-63 ) gokrb5-8.4.3/iana/asnAppTag/000077500000000000000000000000001427031340300155025ustar00rootroot00000000000000gokrb5-8.4.3/iana/asnAppTag/constants.go000066400000000000000000000007611427031340300200510ustar00rootroot00000000000000// Package asnAppTag provides ASN1 application tag numbers. package asnAppTag // ASN1 application tag numbers. const ( Ticket = 1 Authenticator = 2 EncTicketPart = 3 ASREQ = 10 TGSREQ = 12 ASREP = 11 TGSREP = 13 APREQ = 14 APREP = 15 KRBSafe = 20 KRBPriv = 21 KRBCred = 22 EncASRepPart = 25 EncTGSRepPart = 26 EncAPRepPart = 27 EncKrbPrivPart = 28 EncKrbCredPart = 29 KRBError = 30 ) gokrb5-8.4.3/iana/chksumtype/000077500000000000000000000000001427031340300160205ustar00rootroot00000000000000gokrb5-8.4.3/iana/chksumtype/constants.go000066400000000000000000000017431427031340300203700ustar00rootroot00000000000000// Package chksumtype provides Kerberos 5 checksum type assigned numbers. package chksumtype // Checksum type IDs. const ( //RESERVED : 0 CRC32 int32 = 1 RSA_MD4 int32 = 2 RSA_MD4_DES int32 = 3 DES_MAC int32 = 4 DES_MAC_K int32 = 5 RSA_MD4_DES_K int32 = 6 RSA_MD5 int32 = 7 RSA_MD5_DES int32 = 8 RSA_MD5_DES3 int32 = 9 SHA1_ID10 int32 = 10 //UNASSIGNED : 11 HMAC_SHA1_DES3_KD int32 = 12 HMAC_SHA1_DES3 int32 = 13 SHA1_ID14 int32 = 14 HMAC_SHA1_96_AES128 int32 = 15 HMAC_SHA1_96_AES256 int32 = 16 CMAC_CAMELLIA128 int32 = 17 CMAC_CAMELLIA256 int32 = 18 HMAC_SHA256_128_AES128 int32 = 19 HMAC_SHA384_192_AES256 int32 = 20 //UNASSIGNED : 21-32770 GSSAPI int32 = 32771 //UNASSIGNED : 32772-2147483647 KERB_CHECKSUM_HMAC_MD5_UNSIGNED uint32 = 4294967158 // 0xFFFFFF76 documentation says this is -138 but in an unsigned int this is 4294967158 KERB_CHECKSUM_HMAC_MD5 int32 = -138 ) gokrb5-8.4.3/iana/constants.go000066400000000000000000000001731427031340300161700ustar00rootroot00000000000000// Package iana provides Kerberos 5 assigned numbers. package iana // PVNO is the Protocol Version Number. const PVNO = 5 gokrb5-8.4.3/iana/errorcode/000077500000000000000000000000001427031340300156105ustar00rootroot00000000000000gokrb5-8.4.3/iana/errorcode/constants.go000066400000000000000000000305641427031340300201630ustar00rootroot00000000000000// Package errorcode provides Kerberos 5 assigned error codes. package errorcode import "fmt" // Kerberos error codes. const ( KDC_ERR_NONE int32 = 0 //No error KDC_ERR_NAME_EXP int32 = 1 //Client's entry in database has expired KDC_ERR_SERVICE_EXP int32 = 2 //Server's entry in database has expired KDC_ERR_BAD_PVNO int32 = 3 //Requested protocol version number not supported KDC_ERR_C_OLD_MAST_KVNO int32 = 4 //Client's key encrypted in old master key KDC_ERR_S_OLD_MAST_KVNO int32 = 5 //Server's key encrypted in old master key KDC_ERR_C_PRINCIPAL_UNKNOWN int32 = 6 //Client not found in Kerberos database KDC_ERR_S_PRINCIPAL_UNKNOWN int32 = 7 //Server not found in Kerberos database KDC_ERR_PRINCIPAL_NOT_UNIQUE int32 = 8 //Multiple principal entries in database KDC_ERR_NULL_KEY int32 = 9 //The client or server has a null key KDC_ERR_CANNOT_POSTDATE int32 = 10 //Ticket not eligible for postdating KDC_ERR_NEVER_VALID int32 = 11 //Requested starttime is later than end time KDC_ERR_POLICY int32 = 12 //KDC policy rejects request KDC_ERR_BADOPTION int32 = 13 //KDC cannot accommodate requested option KDC_ERR_ETYPE_NOSUPP int32 = 14 //KDC has no support for encryption type KDC_ERR_SUMTYPE_NOSUPP int32 = 15 //KDC has no support for checksum type KDC_ERR_PADATA_TYPE_NOSUPP int32 = 16 //KDC has no support for padata type KDC_ERR_TRTYPE_NOSUPP int32 = 17 //KDC has no support for transited type KDC_ERR_CLIENT_REVOKED int32 = 18 //Clients credentials have been revoked KDC_ERR_SERVICE_REVOKED int32 = 19 //Credentials for server have been revoked KDC_ERR_TGT_REVOKED int32 = 20 //TGT has been revoked KDC_ERR_CLIENT_NOTYET int32 = 21 //Client not yet valid; try again later KDC_ERR_SERVICE_NOTYET int32 = 22 //Server not yet valid; try again later KDC_ERR_KEY_EXPIRED int32 = 23 //Password has expired; change password to reset KDC_ERR_PREAUTH_FAILED int32 = 24 //Pre-authentication information was invalid KDC_ERR_PREAUTH_REQUIRED int32 = 25 //Additional pre-authentication required KDC_ERR_SERVER_NOMATCH int32 = 26 //Requested server and ticket don't match KDC_ERR_MUST_USE_USER2USER int32 = 27 //Server principal valid for user2user only KDC_ERR_PATH_NOT_ACCEPTED int32 = 28 //KDC Policy rejects transited path KDC_ERR_SVC_UNAVAILABLE int32 = 29 //A service is not available KRB_AP_ERR_BAD_INTEGRITY int32 = 31 //Integrity check on decrypted field failed KRB_AP_ERR_TKT_EXPIRED int32 = 32 //Ticket expired KRB_AP_ERR_TKT_NYV int32 = 33 //Ticket not yet valid KRB_AP_ERR_REPEAT int32 = 34 //Request is a replay KRB_AP_ERR_NOT_US int32 = 35 //The ticket isn't for us KRB_AP_ERR_BADMATCH int32 = 36 //Ticket and authenticator don't match KRB_AP_ERR_SKEW int32 = 37 //Clock skew too great KRB_AP_ERR_BADADDR int32 = 38 //Incorrect net address KRB_AP_ERR_BADVERSION int32 = 39 //Protocol version mismatch KRB_AP_ERR_MSG_TYPE int32 = 40 //Invalid msg type KRB_AP_ERR_MODIFIED int32 = 41 //Message stream modified KRB_AP_ERR_BADORDER int32 = 42 //Message out of order KRB_AP_ERR_BADKEYVER int32 = 44 //Specified version of key is not available KRB_AP_ERR_NOKEY int32 = 45 //Service key not available KRB_AP_ERR_MUT_FAIL int32 = 46 //Mutual authentication failed KRB_AP_ERR_BADDIRECTION int32 = 47 //Incorrect message direction KRB_AP_ERR_METHOD int32 = 48 //Alternative authentication method required KRB_AP_ERR_BADSEQ int32 = 49 //Incorrect sequence number in message KRB_AP_ERR_INAPP_CKSUM int32 = 50 //Inappropriate type of checksum in message KRB_AP_PATH_NOT_ACCEPTED int32 = 51 //Policy rejects transited path KRB_ERR_RESPONSE_TOO_BIG int32 = 52 //Response too big for UDP; retry with TCP KRB_ERR_GENERIC int32 = 60 //Generic error (description in e-text) KRB_ERR_FIELD_TOOLONG int32 = 61 //Field is too long for this implementation KDC_ERROR_CLIENT_NOT_TRUSTED int32 = 62 //Reserved for PKINIT KDC_ERROR_KDC_NOT_TRUSTED int32 = 63 //Reserved for PKINIT KDC_ERROR_INVALID_SIG int32 = 64 //Reserved for PKINIT KDC_ERR_KEY_TOO_WEAK int32 = 65 //Reserved for PKINIT KDC_ERR_CERTIFICATE_MISMATCH int32 = 66 //Reserved for PKINIT KRB_AP_ERR_NO_TGT int32 = 67 //No TGT available to validate USER-TO-USER KDC_ERR_WRONG_REALM int32 = 68 //Reserved for future use KRB_AP_ERR_USER_TO_USER_REQUIRED int32 = 69 //Ticket must be for USER-TO-USER KDC_ERR_CANT_VERIFY_CERTIFICATE int32 = 70 //Reserved for PKINIT KDC_ERR_INVALID_CERTIFICATE int32 = 71 //Reserved for PKINIT KDC_ERR_REVOKED_CERTIFICATE int32 = 72 //Reserved for PKINIT KDC_ERR_REVOCATION_STATUS_UNKNOWN int32 = 73 //Reserved for PKINIT KDC_ERR_REVOCATION_STATUS_UNAVAILABLE int32 = 74 //Reserved for PKINIT KDC_ERR_CLIENT_NAME_MISMATCH int32 = 75 //Reserved for PKINIT KDC_ERR_KDC_NAME_MISMATCH int32 = 76 //Reserved for PKINIT ) // Lookup an error code description. func Lookup(i int32) string { if s, ok := errorcodeLookup[i]; ok { return fmt.Sprintf("(%d) %s", i, s) } return fmt.Sprintf("Unknown ErrorCode %d", i) } var errorcodeLookup = map[int32]string{ KDC_ERR_NONE: "KDC_ERR_NONE No error", KDC_ERR_NAME_EXP: "KDC_ERR_NAME_EXP Client's entry in database has expired", KDC_ERR_SERVICE_EXP: "KDC_ERR_SERVICE_EXP Server's entry in database has expired", KDC_ERR_BAD_PVNO: "KDC_ERR_BAD_PVNO Requested protocol version number not supported", KDC_ERR_C_OLD_MAST_KVNO: "KDC_ERR_C_OLD_MAST_KVNO Client's key encrypted in old master key", KDC_ERR_S_OLD_MAST_KVNO: "KDC_ERR_S_OLD_MAST_KVNO Server's key encrypted in old master key", KDC_ERR_C_PRINCIPAL_UNKNOWN: "KDC_ERR_C_PRINCIPAL_UNKNOWN Client not found in Kerberos database", KDC_ERR_S_PRINCIPAL_UNKNOWN: "KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database", KDC_ERR_PRINCIPAL_NOT_UNIQUE: "KDC_ERR_PRINCIPAL_NOT_UNIQUE Multiple principal entries in database", KDC_ERR_NULL_KEY: "KDC_ERR_NULL_KEY The client or server has a null key", KDC_ERR_CANNOT_POSTDATE: "KDC_ERR_CANNOT_POSTDATE Ticket not eligible for postdating", KDC_ERR_NEVER_VALID: "KDC_ERR_NEVER_VALID Requested starttime is later than end time", KDC_ERR_POLICY: "KDC_ERR_POLICY KDC policy rejects request", KDC_ERR_BADOPTION: "KDC_ERR_BADOPTION KDC cannot accommodate requested option", KDC_ERR_ETYPE_NOSUPP: "KDC_ERR_ETYPE_NOSUPP KDC has no support for encryption type", KDC_ERR_SUMTYPE_NOSUPP: "KDC_ERR_SUMTYPE_NOSUPP KDC has no support for checksum type", KDC_ERR_PADATA_TYPE_NOSUPP: "KDC_ERR_PADATA_TYPE_NOSUPP KDC has no support for padata type", KDC_ERR_TRTYPE_NOSUPP: "KDC_ERR_TRTYPE_NOSUPP KDC has no support for transited type", KDC_ERR_CLIENT_REVOKED: "KDC_ERR_CLIENT_REVOKED Clients credentials have been revoked", KDC_ERR_SERVICE_REVOKED: "KDC_ERR_SERVICE_REVOKED Credentials for server have been revoked", KDC_ERR_TGT_REVOKED: "KDC_ERR_TGT_REVOKED TGT has been revoked", KDC_ERR_CLIENT_NOTYET: "KDC_ERR_CLIENT_NOTYET Client not yet valid; try again later", KDC_ERR_SERVICE_NOTYET: "KDC_ERR_SERVICE_NOTYET Server not yet valid; try again later", KDC_ERR_KEY_EXPIRED: "KDC_ERR_KEY_EXPIRED Password has expired; change password to reset", KDC_ERR_PREAUTH_FAILED: "KDC_ERR_PREAUTH_FAILED Pre-authentication information was invalid", KDC_ERR_PREAUTH_REQUIRED: "KDC_ERR_PREAUTH_REQUIRED Additional pre-authentication required", KDC_ERR_SERVER_NOMATCH: "KDC_ERR_SERVER_NOMATCH Requested server and ticket don't match", KDC_ERR_MUST_USE_USER2USER: "KDC_ERR_MUST_USE_USER2USER Server principal valid for user2user only", KDC_ERR_PATH_NOT_ACCEPTED: "KDC_ERR_PATH_NOT_ACCEPTED KDC Policy rejects transited path", KDC_ERR_SVC_UNAVAILABLE: "KDC_ERR_SVC_UNAVAILABLE A service is not available", KRB_AP_ERR_BAD_INTEGRITY: "KRB_AP_ERR_BAD_INTEGRITY Integrity check on decrypted field failed", KRB_AP_ERR_TKT_EXPIRED: "KRB_AP_ERR_TKT_EXPIRED Ticket expired", KRB_AP_ERR_TKT_NYV: "KRB_AP_ERR_TKT_NYV Ticket not yet valid", KRB_AP_ERR_REPEAT: "KRB_AP_ERR_REPEAT Request is a replay", KRB_AP_ERR_NOT_US: "KRB_AP_ERR_NOT_US The ticket isn't for us", KRB_AP_ERR_BADMATCH: "KRB_AP_ERR_BADMATCH Ticket and authenticator don't match", KRB_AP_ERR_SKEW: "KRB_AP_ERR_SKEW Clock skew too great", KRB_AP_ERR_BADADDR: "KRB_AP_ERR_BADADDR Incorrect net address", KRB_AP_ERR_BADVERSION: "KRB_AP_ERR_BADVERSION Protocol version mismatch", KRB_AP_ERR_MSG_TYPE: "KRB_AP_ERR_MSG_TYPE Invalid msg type", KRB_AP_ERR_MODIFIED: "KRB_AP_ERR_MODIFIED Message stream modified", KRB_AP_ERR_BADORDER: "KRB_AP_ERR_BADORDER Message out of order", KRB_AP_ERR_BADKEYVER: "KRB_AP_ERR_BADKEYVER Specified version of key is not available", KRB_AP_ERR_NOKEY: "KRB_AP_ERR_NOKEY Service key not available", KRB_AP_ERR_MUT_FAIL: "KRB_AP_ERR_MUT_FAIL Mutual authentication failed", KRB_AP_ERR_BADDIRECTION: "KRB_AP_ERR_BADDIRECTION Incorrect message direction", KRB_AP_ERR_METHOD: "KRB_AP_ERR_METHOD Alternative authentication method required", KRB_AP_ERR_BADSEQ: "KRB_AP_ERR_BADSEQ Incorrect sequence number in message", KRB_AP_ERR_INAPP_CKSUM: "KRB_AP_ERR_INAPP_CKSUM Inappropriate type of checksum in message", KRB_AP_PATH_NOT_ACCEPTED: "KRB_AP_PATH_NOT_ACCEPTED Policy rejects transited path", KRB_ERR_RESPONSE_TOO_BIG: "KRB_ERR_RESPONSE_TOO_BIG Response too big for UDP; retry with TCP", KRB_ERR_GENERIC: "KRB_ERR_GENERIC Generic error (description in e-text)", KRB_ERR_FIELD_TOOLONG: "KRB_ERR_FIELD_TOOLONG Field is too long for this implementation", KDC_ERROR_CLIENT_NOT_TRUSTED: "KDC_ERROR_CLIENT_NOT_TRUSTED Reserved for PKINIT", KDC_ERROR_KDC_NOT_TRUSTED: "KDC_ERROR_KDC_NOT_TRUSTED Reserved for PKINIT", KDC_ERROR_INVALID_SIG: "KDC_ERROR_INVALID_SIG Reserved for PKINIT", KDC_ERR_KEY_TOO_WEAK: "KDC_ERR_KEY_TOO_WEAK Reserved for PKINIT", KDC_ERR_CERTIFICATE_MISMATCH: "KDC_ERR_CERTIFICATE_MISMATCH Reserved for PKINIT", KRB_AP_ERR_NO_TGT: "KRB_AP_ERR_NO_TGT No TGT available to validate USER-TO-USER", KDC_ERR_WRONG_REALM: "KDC_ERR_WRONG_REALM Reserved for future use", KRB_AP_ERR_USER_TO_USER_REQUIRED: "KRB_AP_ERR_USER_TO_USER_REQUIRED Ticket must be for USER-TO-USER", KDC_ERR_CANT_VERIFY_CERTIFICATE: "KDC_ERR_CANT_VERIFY_CERTIFICATE Reserved for PKINIT", KDC_ERR_INVALID_CERTIFICATE: "KDC_ERR_INVALID_CERTIFICATE Reserved for PKINIT", KDC_ERR_REVOKED_CERTIFICATE: "KDC_ERR_REVOKED_CERTIFICATE Reserved for PKINIT", KDC_ERR_REVOCATION_STATUS_UNKNOWN: "KDC_ERR_REVOCATION_STATUS_UNKNOWN Reserved for PKINIT", KDC_ERR_REVOCATION_STATUS_UNAVAILABLE: "KDC_ERR_REVOCATION_STATUS_UNAVAILABLE Reserved for PKINIT", KDC_ERR_CLIENT_NAME_MISMATCH: "KDC_ERR_CLIENT_NAME_MISMATCH Reserved for PKINIT", KDC_ERR_KDC_NAME_MISMATCH: "KDC_ERR_KDC_NAME_MISMATCH Reserved for PKINIT", } gokrb5-8.4.3/iana/etypeID/000077500000000000000000000000001427031340300151675ustar00rootroot00000000000000gokrb5-8.4.3/iana/etypeID/constants.go000066400000000000000000000073561427031340300175450ustar00rootroot00000000000000// Package etypeID provides Kerberos 5 encryption type assigned numbers. package etypeID // Kerberos encryption type assigned numbers. const ( //RESERVED : 0 DES_CBC_CRC int32 = 1 DES_CBC_MD4 int32 = 2 DES_CBC_MD5 int32 = 3 DES_CBC_RAW int32 = 4 DES3_CBC_MD5 int32 = 5 DES3_CBC_RAW int32 = 6 DES3_CBC_SHA1 int32 = 7 DES_HMAC_SHA1 int32 = 8 DSAWITHSHA1_CMSOID int32 = 9 MD5WITHRSAENCRYPTION_CMSOID int32 = 10 SHA1WITHRSAENCRYPTION_CMSOID int32 = 11 RC2CBC_ENVOID int32 = 12 RSAENCRYPTION_ENVOID int32 = 13 RSAES_OAEP_ENV_OID int32 = 14 DES_EDE3_CBC_ENV_OID int32 = 15 DES3_CBC_SHA1_KD int32 = 16 AES128_CTS_HMAC_SHA1_96 int32 = 17 AES256_CTS_HMAC_SHA1_96 int32 = 18 AES128_CTS_HMAC_SHA256_128 int32 = 19 AES256_CTS_HMAC_SHA384_192 int32 = 20 //UNASSIGNED : 21-22 RC4_HMAC int32 = 23 RC4_HMAC_EXP int32 = 24 CAMELLIA128_CTS_CMAC int32 = 25 CAMELLIA256_CTS_CMAC int32 = 26 //UNASSIGNED : 27-64 SUBKEY_KEYMATERIAL int32 = 65 //UNASSIGNED : 66-2147483647 ) // ETypesByName is a map of EncType names to their assigned EncType number. var ETypesByName = map[string]int32{ "des-cbc-crc": DES_CBC_CRC, "des-cbc-md4": DES_CBC_MD4, "des-cbc-md5": DES_CBC_MD5, "des-cbc-raw": DES_CBC_RAW, "des3-cbc-md5": DES3_CBC_MD5, "des3-cbc-raw": DES3_CBC_RAW, "des3-cbc-sha1": DES3_CBC_SHA1, "des3-hmac-sha1": DES_HMAC_SHA1, "des3-cbc-sha1-kd": DES3_CBC_SHA1_KD, "des-hmac-sha1": DES_HMAC_SHA1, "dsaWithSHA1-CmsOID": DSAWITHSHA1_CMSOID, "md5WithRSAEncryption-CmsOID": MD5WITHRSAENCRYPTION_CMSOID, "sha1WithRSAEncryption-CmsOID": SHA1WITHRSAENCRYPTION_CMSOID, "rc2CBC-EnvOID": RC2CBC_ENVOID, "rsaEncryption-EnvOID": RSAENCRYPTION_ENVOID, "rsaES-OAEP-ENV-OID": RSAES_OAEP_ENV_OID, "des-ede3-cbc-Env-OID": DES_EDE3_CBC_ENV_OID, "aes128-cts-hmac-sha1-96": AES128_CTS_HMAC_SHA1_96, "aes128-cts": AES128_CTS_HMAC_SHA1_96, "aes128-sha1": AES128_CTS_HMAC_SHA1_96, "aes256-cts-hmac-sha1-96": AES256_CTS_HMAC_SHA1_96, "aes256-cts": AES256_CTS_HMAC_SHA1_96, "aes256-sha1": AES256_CTS_HMAC_SHA1_96, "aes128-cts-hmac-sha256-128": AES128_CTS_HMAC_SHA256_128, "aes128-sha2": AES128_CTS_HMAC_SHA256_128, "aes256-cts-hmac-sha384-192": AES256_CTS_HMAC_SHA384_192, "aes256-sha2": AES256_CTS_HMAC_SHA384_192, "arcfour-hmac": RC4_HMAC, "rc4-hmac": RC4_HMAC, "arcfour-hmac-md5": RC4_HMAC, "arcfour-hmac-exp": RC4_HMAC_EXP, "rc4-hmac-exp": RC4_HMAC_EXP, "arcfour-hmac-md5-exp": RC4_HMAC_EXP, "camellia128-cts-cmac": CAMELLIA128_CTS_CMAC, "camellia128-cts": CAMELLIA128_CTS_CMAC, "camellia256-cts-cmac": CAMELLIA256_CTS_CMAC, "camellia256-cts": CAMELLIA256_CTS_CMAC, "subkey-keymaterial": SUBKEY_KEYMATERIAL, } // EtypeSupported resolves the etype name string to the etype ID. // If zero is returned the etype is not supported by gokrb5. func EtypeSupported(etype string) int32 { // Slice of supported enctype IDs s := []int32{ AES128_CTS_HMAC_SHA1_96, AES256_CTS_HMAC_SHA1_96, AES128_CTS_HMAC_SHA256_128, AES256_CTS_HMAC_SHA384_192, DES3_CBC_SHA1_KD, RC4_HMAC, } id := ETypesByName[etype] if id == 0 { return id } for _, sid := range s { if id == sid { return id } } return 0 } gokrb5-8.4.3/iana/flags/000077500000000000000000000000001427031340300147205ustar00rootroot00000000000000gokrb5-8.4.3/iana/flags/constants.go000066400000000000000000000016761427031340300172750ustar00rootroot00000000000000// Package flags provides Kerberos 5 flag assigned numbers. package flags // Flag values for KRB5 messages and tickets. const ( Reserved = 0 Forwardable = 1 Forwarded = 2 Proxiable = 3 Proxy = 4 AllowPostDate = 5 MayPostDate = 5 PostDated = 6 Invalid = 7 Renewable = 8 Initial = 9 PreAuthent = 10 HWAuthent = 11 OptHardwareAuth = 11 RequestAnonymous = 12 TransitedPolicyChecked = 12 OKAsDelegate = 13 EncPARep = 15 Canonicalize = 15 DisableTransitedCheck = 26 RenewableOK = 27 EncTktInSkey = 28 Renew = 30 Validate = 31 // AP Option Flags // 0 Reserved for future use. APOptionUseSessionKey = 1 APOptionMutualRequired = 2 // 3-31 Reserved for future use. ) gokrb5-8.4.3/iana/keyusage/000077500000000000000000000000001427031340300154415ustar00rootroot00000000000000gokrb5-8.4.3/iana/keyusage/constants.go000066400000000000000000000036351427031340300200130ustar00rootroot00000000000000// Package keyusage provides Kerberos 5 key usage assigned numbers. package keyusage // Key usage numbers. const ( AS_REQ_PA_ENC_TIMESTAMP = 1 KDC_REP_TICKET = 2 AS_REP_ENCPART = 3 TGS_REQ_KDC_REQ_BODY_AUTHDATA_SESSION_KEY = 4 TGS_REQ_KDC_REQ_BODY_AUTHDATA_SUB_KEY = 5 TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM = 6 TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR = 7 TGS_REP_ENCPART_SESSION_KEY = 8 TGS_REP_ENCPART_AUTHENTICATOR_SUB_KEY = 9 AP_REQ_AUTHENTICATOR_CHKSUM = 10 AP_REQ_AUTHENTICATOR = 11 AP_REP_ENCPART = 12 KRB_PRIV_ENCPART = 13 KRB_CRED_ENCPART = 14 KRB_SAFE_CHKSUM = 15 KERB_NON_KERB_SALT = 16 KERB_NON_KERB_CKSUM_SALT = 17 //18. Reserved for future use in Kerberos and related protocols. AD_KDC_ISSUED_CHKSUM = 19 //20-21. Reserved for future use in Kerberos and related protocols. GSSAPI_ACCEPTOR_SEAL = 22 GSSAPI_ACCEPTOR_SIGN = 23 GSSAPI_INITIATOR_SEAL = 24 GSSAPI_INITIATOR_SIGN = 25 KEY_USAGE_FAST_REQ_CHKSUM = 50 KEY_USAGE_FAST_ENC = 51 KEY_USAGE_FAST_REP = 52 KEY_USAGE_FAST_FINISHED = 53 KEY_USAGE_ENC_CHALLENGE_CLIENT = 54 KEY_USAGE_ENC_CHALLENGE_KDC = 55 KEY_USAGE_AS_REQ = 56 //26-511. Reserved for future use in Kerberos and related protocols. //512-1023. Reserved for uses internal to a Kerberos implementation. //1024. Encryption for application use in protocols that do not specify key usage values //1025. Checksums for application use in protocols that do not specify key usage values //1026-2047. Reserved for application use. ) gokrb5-8.4.3/iana/msgtype/000077500000000000000000000000001427031340300153145ustar00rootroot00000000000000gokrb5-8.4.3/iana/msgtype/constants.go000066400000000000000000000014711427031340300176620ustar00rootroot00000000000000// Package msgtype provides Kerberos 5 message type assigned numbers. package msgtype // KRB message type IDs. const ( KRB_AS_REQ = 10 //Request for initial authentication KRB_AS_REP = 11 //Response to KRB_AS_REQ request KRB_TGS_REQ = 12 //Request for authentication based on TGT KRB_TGS_REP = 13 //Response to KRB_TGS_REQ request KRB_AP_REQ = 14 //Application request to server KRB_AP_REP = 15 //Response to KRB_AP_REQ_MUTUAL KRB_RESERVED16 = 16 //Reserved for user-to-user krb_tgt_request KRB_RESERVED17 = 17 //Reserved for user-to-user krb_tgt_reply KRB_SAFE = 20 // Safe (checksummed) application message KRB_PRIV = 21 // Private (encrypted) application message KRB_CRED = 22 //Private (encrypted) message to forward credentials KRB_ERROR = 30 //Error response ) gokrb5-8.4.3/iana/nametype/000077500000000000000000000000001427031340300154465ustar00rootroot00000000000000gokrb5-8.4.3/iana/nametype/constants.go000066400000000000000000000014671427031340300200210ustar00rootroot00000000000000// Package nametype provides Kerberos 5 principal name type numbers. package nametype // Kerberos name type IDs. const ( KRB_NT_UNKNOWN int32 = 0 //Name type not known KRB_NT_PRINCIPAL int32 = 1 //Just the name of the principal as in DCE, or for users KRB_NT_SRV_INST int32 = 2 //Service and other unique instance (krbtgt) KRB_NT_SRV_HST int32 = 3 //Service with host name as instance (telnet, rcommands) KRB_NT_SRV_XHST int32 = 4 //Service with host as remaining components KRB_NT_UID int32 = 5 //Unique ID KRB_NT_X500_PRINCIPAL int32 = 6 //Encoded X.509 Distinguished name [RFC2253] KRB_NT_SMTP_NAME int32 = 7 //Name in form of SMTP email name (e.g., user@example.com) KRB_NT_ENTERPRISE int32 = 10 //Enterprise name; may be mapped to principal name ) gokrb5-8.4.3/iana/patype/000077500000000000000000000000001427031340300151265ustar00rootroot00000000000000gokrb5-8.4.3/iana/patype/constants.go000066400000000000000000000047221427031340300174760ustar00rootroot00000000000000// Package patype provides Kerberos 5 pre-authentication type assigned numbers. package patype // Kerberos pre-authentication type assigned numbers. const ( PA_TGS_REQ int32 = 1 PA_ENC_TIMESTAMP int32 = 2 PA_PW_SALT int32 = 3 //RESERVED : 4 PA_ENC_UNIX_TIME int32 = 5 PA_SANDIA_SECUREID int32 = 6 PA_SESAME int32 = 7 PA_OSF_DCE int32 = 8 PA_CYBERSAFE_SECUREID int32 = 9 PA_AFS3_SALT int32 = 10 PA_ETYPE_INFO int32 = 11 PA_SAM_CHALLENGE int32 = 12 PA_SAM_RESPONSE int32 = 13 PA_PK_AS_REQ_OLD int32 = 14 PA_PK_AS_REP_OLD int32 = 15 PA_PK_AS_REQ int32 = 16 PA_PK_AS_REP int32 = 17 PA_PK_OCSP_RESPONSE int32 = 18 PA_ETYPE_INFO2 int32 = 19 PA_USE_SPECIFIED_KVNO int32 = 20 PA_SVR_REFERRAL_INFO int32 = 20 PA_SAM_REDIRECT int32 = 21 PA_GET_FROM_TYPED_DATA int32 = 22 TD_PADATA int32 = 22 PA_SAM_ETYPE_INFO int32 = 23 PA_ALT_PRINC int32 = 24 PA_SERVER_REFERRAL int32 = 25 //UNASSIGNED : 26-29 PA_SAM_CHALLENGE2 int32 = 30 PA_SAM_RESPONSE2 int32 = 31 //UNASSIGNED : 32-40 PA_EXTRA_TGT int32 = 41 //UNASSIGNED : 42-100 TD_PKINIT_CMS_CERTIFICATES int32 = 101 TD_KRB_PRINCIPAL int32 = 102 TD_KRB_REALM int32 = 103 TD_TRUSTED_CERTIFIERS int32 = 104 TD_CERTIFICATE_INDEX int32 = 105 TD_APP_DEFINED_ERROR int32 = 106 TD_REQ_NONCE int32 = 107 TD_REQ_SEQ int32 = 108 TD_DH_PARAMETERS int32 = 109 //UNASSIGNED : 110 TD_CMS_DIGEST_ALGORITHMS int32 = 111 TD_CERT_DIGEST_ALGORITHMS int32 = 112 //UNASSIGNED : 113-127 PA_PAC_REQUEST int32 = 128 PA_FOR_USER int32 = 129 PA_FOR_X509_USER int32 = 130 PA_FOR_CHECK_DUPS int32 = 131 PA_AS_CHECKSUM int32 = 132 PA_FX_COOKIE int32 = 133 PA_AUTHENTICATION_SET int32 = 134 PA_AUTH_SET_SELECTED int32 = 135 PA_FX_FAST int32 = 136 PA_FX_ERROR int32 = 137 PA_ENCRYPTED_CHALLENGE int32 = 138 //UNASSIGNED : 139-140 PA_OTP_CHALLENGE int32 = 141 PA_OTP_REQUEST int32 = 142 PA_OTP_CONFIRM int32 = 143 PA_OTP_PIN_CHANGE int32 = 144 PA_EPAK_AS_REQ int32 = 145 PA_EPAK_AS_REP int32 = 146 PA_PKINIT_KX int32 = 147 PA_PKU2U_NAME int32 = 148 PA_REQ_ENC_PA_REP int32 = 149 PA_AS_FRESHNESS int32 = 150 //UNASSIGNED : 151-164 PA_SUPPORTED_ETYPES int32 = 165 PA_EXTENDED_ERROR int32 = 166 ) gokrb5-8.4.3/iana/trtype/000077500000000000000000000000001427031340300151535ustar00rootroot00000000000000gokrb5-8.4.3/iana/trtype/constants.go000066400000000000000000000003151427031340300175150ustar00rootroot00000000000000// Package trtype provides Transited Encoding Type assigned numbers. package trtype // Transited Encoding Type IDs. const ( DOMAIN_X500_COMPRESS int32 = 1 //Reserved values All others ) gokrb5-8.4.3/kadmin/000077500000000000000000000000001427031340300141575ustar00rootroot00000000000000gokrb5-8.4.3/kadmin/changepasswddata.go000066400000000000000000000012021427031340300200020ustar00rootroot00000000000000package kadmin import ( "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/types" ) // ChangePasswdData is the payload to a password change message. type ChangePasswdData struct { NewPasswd []byte `asn1:"explicit,tag:0"` TargName types.PrincipalName `asn1:"explicit,optional,tag:1"` TargRealm string `asn1:"generalstring,optional,explicit,tag:2"` } // Marshal ChangePasswdData into a byte slice. func (c *ChangePasswdData) Marshal() ([]byte, error) { b, err := asn1.Marshal(*c) if err != nil { return []byte{}, err } //b = asn1tools.AddASNAppTag(b, asnAppTag.) return b, nil } gokrb5-8.4.3/kadmin/changepasswddata_test.go000066400000000000000000000014361427031340300210520ustar00rootroot00000000000000package kadmin import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) func TestChangePasswdData_Marshal(t *testing.T) { t.Parallel() chgpasswd := ChangePasswdData{ NewPasswd: []byte("newpassword"), TargName: types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, "testuser1"), TargRealm: "TEST.GOKRB5", } chpwdb, err := chgpasswd.Marshal() if err != nil { t.Fatalf("error marshaling change passwd data: %v\n", err) } b, err := hex.DecodeString(testdata.MarshaledChangePasswdData) if err != nil { t.Fatalf("Test vector read error: %v", err) } assert.Equal(t, b, chpwdb, "marshaled bytes of change passwd data not as expected") } gokrb5-8.4.3/kadmin/message.go000066400000000000000000000053031427031340300161330ustar00rootroot00000000000000package kadmin import ( "bytes" "encoding/binary" "errors" "fmt" "math" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( verisonHex = "ff80" ) // Request message for changing password. type Request struct { APREQ messages.APReq KRBPriv messages.KRBPriv } // Reply message for a password change. type Reply struct { MessageLength int Version int APREPLength int APREP messages.APRep KRBPriv messages.KRBPriv KRBError messages.KRBError IsKRBError bool ResultCode uint16 Result string } // Marshal a Request into a byte slice. func (m *Request) Marshal() (b []byte, err error) { b = []byte{255, 128} // protocol version number: contains the hex constant 0xff80 (big-endian integer). ab, e := m.APREQ.Marshal() if e != nil { err = fmt.Errorf("error marshaling AP_REQ: %v", e) return } if len(ab) > math.MaxUint16 { err = errors.New("length of AP_REQ greater then max Uint16 size") return } al := make([]byte, 2) binary.BigEndian.PutUint16(al, uint16(len(ab))) b = append(b, al...) b = append(b, ab...) pb, e := m.KRBPriv.Marshal() if e != nil { err = fmt.Errorf("error marshaling KRB_Priv: %v", e) return } b = append(b, pb...) if len(b)+2 > math.MaxUint16 { err = errors.New("length of message greater then max Uint16 size") return } ml := make([]byte, 2) binary.BigEndian.PutUint16(ml, uint16(len(b)+2)) b = append(ml, b...) return } // Unmarshal a byte slice into a Reply. func (m *Reply) Unmarshal(b []byte) error { m.MessageLength = int(binary.BigEndian.Uint16(b[0:2])) m.Version = int(binary.BigEndian.Uint16(b[2:4])) if m.Version != 1 { return fmt.Errorf("kadmin reply has incorrect protocol version number: %d", m.Version) } m.APREPLength = int(binary.BigEndian.Uint16(b[4:6])) if m.APREPLength != 0 { err := m.APREP.Unmarshal(b[6 : 6+m.APREPLength]) if err != nil { return err } err = m.KRBPriv.Unmarshal(b[6+m.APREPLength : m.MessageLength]) if err != nil { return err } } else { m.IsKRBError = true m.KRBError.Unmarshal(b[6:m.MessageLength]) m.ResultCode, m.Result = parseResponse(m.KRBError.EData) } return nil } func parseResponse(b []byte) (c uint16, s string) { c = binary.BigEndian.Uint16(b[0:2]) buf := bytes.NewBuffer(b[2:]) m := make([]byte, len(b)-2) binary.Read(buf, binary.BigEndian, &m) s = string(m) return } // Decrypt the encrypted part of the KRBError within the change password Reply. func (m *Reply) Decrypt(key types.EncryptionKey) error { if m.IsKRBError { return m.KRBError } err := m.KRBPriv.DecryptEncPart(key) if err != nil { return err } m.ResultCode, m.Result = parseResponse(m.KRBPriv.DecryptedEncPart.UserData) return nil } gokrb5-8.4.3/kadmin/message_test.go000066400000000000000000000025161427031340300171750ustar00rootroot00000000000000package kadmin import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalReply(t *testing.T) { t.Parallel() var a Reply b, err := hex.DecodeString(testdata.MarshaledKpasswd_Rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, 236, a.MessageLength, "message length not as expected") assert.Equal(t, 1, a.Version, "message version not as expected") assert.Equal(t, 140, a.APREPLength, "AP_REP length not as expected") assert.Equal(t, iana.PVNO, a.APREP.PVNO, "AP_REP within reply not as expected") assert.Equal(t, msgtype.KRB_AP_REP, a.APREP.MsgType, "AP_REP message type within reply not as expected") assert.Equal(t, int32(18), a.APREP.EncPart.EType, "AP_REQ etype not as expected") assert.Equal(t, iana.PVNO, a.KRBPriv.PVNO, "KRBPriv within reply not as expected") assert.Equal(t, msgtype.KRB_PRIV, a.KRBPriv.MsgType, "KRBPriv type within reply not as expected") assert.Equal(t, int32(18), a.KRBPriv.EncPart.EType, "KRBPriv etype not as expected") } // Request marshal is tested via integration test in the client package due to the dynamic keys and encryption. gokrb5-8.4.3/kadmin/passwd.go000066400000000000000000000036611427031340300160150ustar00rootroot00000000000000// Package kadmin provides Kerberos administration capabilities. package kadmin import ( "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/types" ) // ChangePasswdMsg generate a change password request and also return the key needed to decrypt the reply. func ChangePasswdMsg(cname types.PrincipalName, realm, password string, tkt messages.Ticket, sessionKey types.EncryptionKey) (r Request, k types.EncryptionKey, err error) { // Create change password data struct and marshal to bytes chgpasswd := ChangePasswdData{ NewPasswd: []byte(password), TargName: cname, TargRealm: realm, } chpwdb, err := chgpasswd.Marshal() if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error marshaling change passwd data") return } // Generate authenticator auth, err := types.NewAuthenticator(realm, cname) if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") return } etype, err := crypto.GetEtype(sessionKey.KeyType) if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey etype") return } err = auth.GenerateSeqNumberAndSubKey(etype.GetETypeID(), etype.GetKeyByteSize()) if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey") return } k = auth.SubKey // Generate AP_REQ APreq, err := messages.NewAPReq(tkt, sessionKey, auth) if err != nil { return } // Form the KRBPriv encpart data kp := messages.EncKrbPrivPart{ UserData: chpwdb, Timestamp: auth.CTime, Usec: auth.Cusec, SequenceNumber: auth.SeqNumber, } kpriv := messages.NewKRBPriv(kp) err = kpriv.EncryptEncPart(k) if err != nil { err = krberror.Errorf(err, krberror.EncryptingError, "error encrypting change passwd data") return } r = Request{ APREQ: APreq, KRBPriv: kpriv, } return } gokrb5-8.4.3/keytab/000077500000000000000000000000001427031340300141735ustar00rootroot00000000000000gokrb5-8.4.3/keytab/keytab.go000066400000000000000000000252121427031340300160030ustar00rootroot00000000000000// Package keytab implements Kerberos keytabs: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html. package keytab import ( "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "time" "unsafe" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( keytabFirstByte byte = 05 ) // Keytab struct. type Keytab struct { version uint8 Entries []entry } // Keytab entry struct. type entry struct { Principal principal Timestamp time.Time KVNO8 uint8 Key types.EncryptionKey KVNO uint32 } // Keytab entry principal struct. type principal struct { NumComponents int16 Realm string Components []string NameType int32 } // New creates new, empty Keytab type. func New() *Keytab { var e []entry return &Keytab{ version: 0, Entries: e, } } // GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal. func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, error) { //TODO (theme: KVNO from keytab) this function should return the kvno too var key types.EncryptionKey var t time.Time for _, k := range kt.Entries { if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) && k.Key.KeyType == etype && (k.KVNO == uint32(kvno) || kvno == 0) && k.Timestamp.After(t) { p := true for i, n := range k.Principal.Components { if princName.NameString[i] != n { p = false break } } if p { key = k.Key t = k.Timestamp } } } if len(key.KeyValue) < 1 { return key, fmt.Errorf("matching key not found in keytab. Looking for %v realm: %v kvno: %v etype: %v", princName.NameString, realm, kvno, etype) } return key, nil } // Create a new Keytab entry. func newKeytabEntry() entry { var b []byte return entry{ Principal: newPrincipal(), Timestamp: time.Time{}, KVNO8: 0, Key: types.EncryptionKey{ KeyType: 0, KeyValue: b, }, KVNO: 0, } } // Create a new principal. func newPrincipal() principal { var c []string return principal{ NumComponents: 0, Realm: "", Components: c, NameType: 0, } } // Load a Keytab file into a Keytab type. func Load(ktPath string) (*Keytab, error) { kt := new(Keytab) b, err := ioutil.ReadFile(ktPath) if err != nil { return kt, err } err = kt.Unmarshal(b) return kt, err } // Marshal keytab into byte slice func (kt *Keytab) Marshal() ([]byte, error) { b := []byte{keytabFirstByte, kt.version} for _, e := range kt.Entries { eb, err := e.marshal(int(kt.version)) if err != nil { return b, err } b = append(b, eb...) } return b, nil } // Write the keytab bytes to io.Writer. // Returns the number of bytes written func (kt *Keytab) Write(w io.Writer) (int, error) { b, err := kt.Marshal() if err != nil { return 0, fmt.Errorf("error marshaling keytab: %v", err) } return w.Write(b) } // Unmarshal byte slice of Keytab data into Keytab type. func (kt *Keytab) Unmarshal(b []byte) error { if len(b) < 2 { return fmt.Errorf("byte array is less than 2 bytes: %d", len(b)) } //The first byte of the file always has the value 5 if b[0] != keytabFirstByte { return errors.New("invalid keytab data. First byte does not equal 5") } //Get keytab version //The 2nd byte contains the version number (1 or 2) kt.version = b[1] if kt.version != 1 && kt.version != 2 { return errors.New("invalid keytab data. Keytab version is neither 1 nor 2") } //Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order var endian binary.ByteOrder endian = binary.BigEndian if kt.version == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } // n tracks position in the byte array n := 2 l, err := readInt32(b, &n, &endian) if err != nil { return err } for l != 0 { if l < 0 { //Zero padded so skip over l = l * -1 n = n + int(l) } else { if n < 0 { return fmt.Errorf("%d can't be less than zero", n) } if n+int(l) > len(b) { return fmt.Errorf("%s's length is less than %d", b, n+int(l)) } eb := b[n : n+int(l)] n = n + int(l) ke := newKeytabEntry() // p keeps track as to where we are in the byte stream var p int var err error parsePrincipal(eb, &p, kt, &ke, &endian) ke.Timestamp, err = readTimestamp(eb, &p, &endian) if err != nil { return err } rei8, err := readInt8(eb, &p, &endian) if err != nil { return err } ke.KVNO8 = uint8(rei8) rei16, err := readInt16(eb, &p, &endian) if err != nil { return err } ke.Key.KeyType = int32(rei16) rei16, err = readInt16(eb, &p, &endian) if err != nil { return err } kl := int(rei16) ke.Key.KeyValue, err = readBytes(eb, &p, kl, &endian) if err != nil { return err } // The 32-bit key version overrides the 8-bit key version. // If at least 4 bytes are left after the other fields are read and they are non-zero // this indicates the 32-bit version is present. if len(eb)-p >= 4 { // The 32-bit key may be present ri32, err := readInt32(eb, &p, &endian) if err != nil { return err } ke.KVNO = uint32(ri32) } if ke.KVNO == 0 { // Handles if the value from the last 4 bytes was zero and also if there are not the 4 bytes present. Makes sense to put the same value here as KVNO8 ke.KVNO = uint32(ke.KVNO8) } // Add the entry to the keytab kt.Entries = append(kt.Entries, ke) } // Check if there are still 4 bytes left to read // Also check that n is greater than zero if n < 0 || n > len(b) || len(b[n:]) < 4 { break } // Read the size of the next entry l, err = readInt32(b, &n, &endian) if err != nil { return err } } return nil } func (e entry) marshal(v int) ([]byte, error) { var b []byte pb, err := e.Principal.marshal(v) if err != nil { return b, err } b = append(b, pb...) var endian binary.ByteOrder endian = binary.BigEndian if v == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } t := make([]byte, 9) endian.PutUint32(t[0:4], uint32(e.Timestamp.Unix())) t[4] = e.KVNO8 endian.PutUint16(t[5:7], uint16(e.Key.KeyType)) endian.PutUint16(t[7:9], uint16(len(e.Key.KeyValue))) b = append(b, t...) buf := new(bytes.Buffer) err = binary.Write(buf, endian, e.Key.KeyValue) if err != nil { return b, err } b = append(b, buf.Bytes()...) t = make([]byte, 4) endian.PutUint32(t, e.KVNO) b = append(b, t...) // Add the length header t = make([]byte, 4) endian.PutUint32(t, uint32(len(b))) b = append(t, b...) return b, nil } // Parse the Keytab bytes of a principal into a Keytab entry's principal. func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) error { var err error ke.Principal.NumComponents, err = readInt16(b, p, e) if err != nil { return err } if kt.version == 1 { //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2 ke.Principal.NumComponents-- } lenRealm, err := readInt16(b, p, e) if err != nil { return err } realmB, err := readBytes(b, p, int(lenRealm), e) if err != nil { return err } ke.Principal.Realm = string(realmB) for i := 0; i < int(ke.Principal.NumComponents); i++ { l, err := readInt16(b, p, e) if err != nil { return err } compB, err := readBytes(b, p, int(l), e) if err != nil { return err } ke.Principal.Components = append(ke.Principal.Components, string(compB)) } if kt.version != 1 { //Name Type is omitted in version 1 ke.Principal.NameType, err = readInt32(b, p, e) if err != nil { return err } } return nil } func (p principal) marshal(v int) ([]byte, error) { //var b []byte b := make([]byte, 2) var endian binary.ByteOrder endian = binary.BigEndian if v == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } endian.PutUint16(b[0:], uint16(p.NumComponents)) realm, err := marshalString(p.Realm, v) if err != nil { return b, err } b = append(b, realm...) for _, c := range p.Components { cb, err := marshalString(c, v) if err != nil { return b, err } b = append(b, cb...) } if v != 1 { t := make([]byte, 4) endian.PutUint32(t, uint32(p.NameType)) b = append(b, t...) } return b, nil } func marshalString(s string, v int) ([]byte, error) { sb := []byte(s) b := make([]byte, 2) var endian binary.ByteOrder endian = binary.BigEndian if v == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } endian.PutUint16(b[0:], uint16(len(sb))) buf := new(bytes.Buffer) err := binary.Write(buf, endian, sb) if err != nil { return b, err } b = append(b, buf.Bytes()...) return b, err } // Read bytes representing a timestamp. func readTimestamp(b []byte, p *int, e *binary.ByteOrder) (time.Time, error) { i32, err := readInt32(b, p, e) if err != nil { return time.Time{}, err } return time.Unix(int64(i32), 0), nil } // Read bytes representing an eight bit integer. func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8, err error) { if *p < 0 { return 0, fmt.Errorf("%d cannot be less than zero", *p) } if (*p + 1) > len(b) { return 0, fmt.Errorf("%s's length is less than %d", b, *p+1) } buf := bytes.NewBuffer(b[*p : *p+1]) binary.Read(buf, *e, &i) *p++ return } // Read bytes representing a sixteen bit integer. func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16, err error) { if *p < 0 { return 0, fmt.Errorf("%d cannot be less than zero", *p) } if (*p + 2) > len(b) { return 0, fmt.Errorf("%s's length is less than %d", b, *p+2) } buf := bytes.NewBuffer(b[*p : *p+2]) binary.Read(buf, *e, &i) *p += 2 return } // Read bytes representing a thirty two bit integer. func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32, err error) { if *p < 0 { return 0, fmt.Errorf("%d cannot be less than zero", *p) } if (*p + 4) > len(b) { return 0, fmt.Errorf("%s's length is less than %d", b, *p+4) } buf := bytes.NewBuffer(b[*p : *p+4]) binary.Read(buf, *e, &i) *p += 4 return } func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) ([]byte, error) { if s < 0 { return nil, fmt.Errorf("%d cannot be less than zero", s) } i := *p + s if i > len(b) { return nil, fmt.Errorf("%s's length is greater than %d", b, i) } buf := bytes.NewBuffer(b[*p:i]) r := make([]byte, s) if err := binary.Read(buf, *e, &r); err != nil { return nil, err } *p += s return r, nil } func isNativeEndianLittle() bool { var x = 0x012345678 var p = unsafe.Pointer(&x) var bp = (*[4]byte)(p) var endian bool if 0x01 == bp[0] { endian = false } else if (0x78 & 0xff) == (bp[0] & 0xff) { endian = true } else { // Default to big endian endian = false } return endian } gokrb5-8.4.3/keytab/keytab_test.go000066400000000000000000000110131427031340300170340ustar00rootroot00000000000000package keytab import ( "encoding/base64" "encoding/binary" "encoding/hex" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshal(t *testing.T) { t.Parallel() b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := New() err := kt.Unmarshal(b) if err != nil { t.Fatalf("Error parsing keytab data: %v\n", err) } assert.Equal(t, uint8(2), kt.version, "Keytab version not as expected") assert.Equal(t, uint32(1), kt.Entries[0].KVNO, "KVNO not as expected") assert.Equal(t, uint8(1), kt.Entries[0].KVNO8, "KVNO8 not as expected") assert.Equal(t, time.Unix(1505669592, 0), kt.Entries[0].Timestamp, "Timestamp not as expected") assert.Equal(t, int32(17), kt.Entries[0].Key.KeyType, "Key's EType not as expected") assert.Equal(t, "698c4df8e9f60e7eea5a21bf4526ad25", hex.EncodeToString(kt.Entries[0].Key.KeyValue), "Key material not as expected") assert.Equal(t, int16(1), kt.Entries[0].Principal.NumComponents, "Number of components in principal not as expected") assert.Equal(t, int32(1), kt.Entries[0].Principal.NameType, "Name type of principal not as expected") assert.Equal(t, "TEST.GOKRB5", kt.Entries[0].Principal.Realm, "Realm of principal not as expected") assert.Equal(t, "testuser1", kt.Entries[0].Principal.Components[0], "Component in principal not as expected") } func TestMarshal(t *testing.T) { t.Parallel() b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := New() err := kt.Unmarshal(b) if err != nil { t.Fatalf("Error parsing keytab data: %v\n", err) } mb, err := kt.Marshal() if err != nil { t.Fatalf("Error marshaling: %v", err) } assert.Equal(t, b, mb, "Marshaled bytes not the same as input bytes") err = kt.Unmarshal(mb) if err != nil { t.Fatalf("Error parsing marshaled bytes: %v", err) } } func TestLoad(t *testing.T) { t.Parallel() f := "test/testdata/testuser1.testtab" cwd, _ := os.Getwd() dir := os.Getenv("TRAVIS_BUILD_DIR") if dir != "" { f = dir + "/" + f } else if filepath.Base(cwd) == "keytab" { f = "../" + f } kt, err := Load(f) if err != nil { t.Fatalf("could not load keytab: %v", err) } assert.Equal(t, uint8(2), kt.version, "keytab version not as expected") assert.Equal(t, 12, len(kt.Entries), "keytab entry count not as expected: %+v", *kt) for _, e := range kt.Entries { if e.Principal.Realm != "TEST.GOKRB5" { t.Error("principal realm not as expected") } if e.Principal.NameType != int32(1) { t.Error("name type not as expected") } if e.Principal.NumComponents != int16(1) { t.Error("number of component not as expected") } if len(e.Principal.Components) != 1 { t.Error("number of component not as expected") } if e.Principal.Components[0] != "testuser1" { t.Error("principal components not as expected") } if e.Timestamp.IsZero() { t.Error("entry timestamp incorrect") } if e.KVNO == uint32(0) { t.Error("entry kvno not as expected") } if e.KVNO8 == uint8(0) { t.Error("entry kvno8 not as expected") } } } // This test provides inputs to readBytes that previously // caused a panic. func TestReadBytes(t *testing.T) { var endian binary.ByteOrder endian = binary.BigEndian p := 0 if _, err := readBytes(nil, &p, 1, &endian); err == nil { t.Fatal("err should be populated because s was given that exceeds array length") } if _, err := readBytes(nil, &p, -1, &endian); err == nil { t.Fatal("err should be given because negative s was given") } } func TestUnmarshalPotentialPanics(t *testing.T) { kt := New() // Test a good keytab with bad bytes to unmarshal. These should // return errors, but not panic. if err := kt.Unmarshal(nil); err == nil { t.Fatal("should have errored, input is absent") } if err := kt.Unmarshal([]byte{}); err == nil { t.Fatal("should have errored, input is empty") } // Incorrect first byte. if err := kt.Unmarshal([]byte{4}); err == nil { t.Fatal("should have errored, input isn't long enough") } // First byte, but no further content. if err := kt.Unmarshal([]byte{5}); err == nil { t.Fatal("should have errored, input isn't long enough") } } // cxf testing stuff func TestBadKeytabs(t *testing.T) { badPayloads := make([]string, 3) badPayloads = append(badPayloads, "BQIwMDAwMDA=") badPayloads = append(badPayloads, "BQIAAAAwAAEACjAwMDAwMDAwMDAAIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw") badPayloads = append(badPayloads, "BQKAAAAA") for _, v := range badPayloads { decodedKt, _ := base64.StdEncoding.DecodeString(v) parsedKt := new(Keytab) parsedKt.Unmarshal(decodedKt) } } gokrb5-8.4.3/krberror/000077500000000000000000000000001427031340300145445ustar00rootroot00000000000000gokrb5-8.4.3/krberror/error.go000066400000000000000000000032301427031340300162220ustar00rootroot00000000000000// Package krberror provides error type and functions for gokrb5. package krberror import ( "fmt" "strings" ) // Error type descriptions. const ( separator = " < " EncodingError = "Encoding_Error" NetworkingError = "Networking_Error" DecryptingError = "Decrypting_Error" EncryptingError = "Encrypting_Error" ChksumError = "Checksum_Error" KRBMsgError = "KRBMessage_Handling_Error" ConfigError = "Configuration_Error" KDCError = "KDC_Error" ) // Krberror is an error type for gokrb5 type Krberror struct { RootCause string EText []string } // Error function to implement the error interface. func (e Krberror) Error() string { return fmt.Sprintf("[Root cause: %s] ", e.RootCause) + strings.Join(e.EText, separator) } // Add another error statement to the error. func (e *Krberror) Add(et string, s string) { e.EText = append([]string{fmt.Sprintf("%s: %s", et, s)}, e.EText...) } // NewKrberror creates a new instance of Krberror. func NewKrberror(et, s string) Krberror { return Krberror{ RootCause: et, EText: []string{s}, } } // Errorf appends to or creates a new Krberror. func Errorf(err error, et, format string, a ...interface{}) Krberror { if e, ok := err.(Krberror); ok { e.Add(et, fmt.Sprintf(format, a...)) return e } return NewErrorf(et, format+": %s", append(a, err)...) } // NewErrorf creates a new Krberror from a formatted string. func NewErrorf(et, format string, a ...interface{}) Krberror { var s string if len(a) > 0 { s = fmt.Sprintf("%s: %s", et, fmt.Sprintf(format, a...)) } else { s = fmt.Sprintf("%s: %s", et, format) } return Krberror{ RootCause: et, EText: []string{s}, } } gokrb5-8.4.3/krberror/error_test.go000066400000000000000000000014221427031340300172620ustar00rootroot00000000000000package krberror import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestErrorf(t *testing.T) { err := fmt.Errorf("an error") var a Krberror a = Errorf(err, "cause", "some text") assert.Equal(t, "[Root cause: cause] cause: some text: an error", a.Error()) a = Errorf(err, "cause", "arg1=%d arg2=%s", 123, "arg") assert.Equal(t, "[Root cause: cause] cause: arg1=123 arg2=arg: an error", a.Error()) err = NewErrorf("another error", "some text") a = Errorf(err, "cause", "some text") assert.Equal(t, "[Root cause: another error] cause: some text < another error: some text", a.Error()) a = Errorf(err, "cause", "arg1=%d arg2=%s", 123, "arg") assert.Equal(t, "[Root cause: another error] cause: arg1=123 arg2=arg < another error: some text", a.Error()) } gokrb5-8.4.3/messages/000077500000000000000000000000001427031340300145235ustar00rootroot00000000000000gokrb5-8.4.3/messages/APRep.go000066400000000000000000000032671427031340300160310ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) // APRep implements RFC 4120 KRB_AP_REP: https://tools.ietf.org/html/rfc4120#section-5.5.2. type APRep struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` EncPart types.EncryptedData `asn1:"explicit,tag:2"` } // EncAPRepPart is the encrypted part of KRB_AP_REP. type EncAPRepPart struct { CTime time.Time `asn1:"generalized,explicit,tag:0"` Cusec int `asn1:"explicit,tag:1"` Subkey types.EncryptionKey `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` } // Unmarshal bytes b into the APRep struct. func (a *APRep) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.APREP)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_AP_REP if a.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_AP_REP. Expected: %v; Actual: %v", expectedMsgType, a.MsgType) } return nil } // Unmarshal bytes b into the APRep encrypted part struct. func (a *EncAPRepPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncAPRepPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "AP_REP unmarshal error") } return nil } gokrb5-8.4.3/messages/APRep_test.go000066400000000000000000000043701427031340300170640ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalAPRep(t *testing.T) { t.Parallel() var a APRep b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AP_REP, a.MsgType, "MsgType is not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Ticket encPart etype not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Ticket encPart KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "Ticket encPart cipher not as expected") } func TestUnmarshalEncAPRepPart(t *testing.T) { t.Parallel() var a EncAPRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep_enc_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, tt, a.CTime, "CTime not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, int32(1), a.Subkey.KeyType, "Subkey type not as expected") assert.Equal(t, []byte("12345678"), a.Subkey.KeyValue, "Subkey value not as expected") assert.Equal(t, int64(17), a.SequenceNumber, "Sequence number not as expected") } func TestUnmarshalEncAPRepPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncAPRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep_enc_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, tt, a.CTime, "CTime not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") } gokrb5-8.4.3/messages/APReq.go000066400000000000000000000164561427031340300160360ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) type marshalAPReq struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` APOptions asn1.BitString `asn1:"explicit,tag:2"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag Ticket asn1.RawValue `asn1:"explicit,tag:3"` EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"` } // APReq implements RFC 4120 KRB_AP_REQ: https://tools.ietf.org/html/rfc4120#section-5.5.1. type APReq struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` APOptions asn1.BitString `asn1:"explicit,tag:2"` Ticket Ticket `asn1:"explicit,tag:3"` EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"` Authenticator types.Authenticator `asn1:"optional"` } // NewAPReq generates a new KRB_AP_REQ struct. func NewAPReq(tkt Ticket, sessionKey types.EncryptionKey, auth types.Authenticator) (APReq, error) { var a APReq ed, err := encryptAuthenticator(auth, sessionKey, tkt) if err != nil { return a, krberror.Errorf(err, krberror.KRBMsgError, "error creating Authenticator for AP_REQ") } a = APReq{ PVNO: iana.PVNO, MsgType: msgtype.KRB_AP_REQ, APOptions: types.NewKrbFlags(), Ticket: tkt, EncryptedAuthenticator: ed, } return a, nil } // Encrypt Authenticator func encryptAuthenticator(a types.Authenticator, sessionKey types.EncryptionKey, tkt Ticket) (types.EncryptedData, error) { var ed types.EncryptedData m, err := a.Marshal() if err != nil { return ed, krberror.Errorf(err, krberror.EncodingError, "marshaling error of EncryptedData form of Authenticator") } usage := authenticatorKeyUsage(tkt.SName) ed, err = crypto.GetEncryptedData(m, sessionKey, uint32(usage), tkt.EncPart.KVNO) if err != nil { return ed, krberror.Errorf(err, krberror.EncryptingError, "error encrypting Authenticator") } return ed, nil } // DecryptAuthenticator decrypts the Authenticator within the AP_REQ. func (a *APReq) DecryptAuthenticator(sessionKey types.EncryptionKey) error { usage := authenticatorKeyUsage(a.Ticket.SName) ab, e := crypto.DecryptEncPart(a.EncryptedAuthenticator, sessionKey, uint32(usage)) if e != nil { return fmt.Errorf("error decrypting authenticator: %v", e) } err := a.Authenticator.Unmarshal(ab) if err != nil { return fmt.Errorf("error unmarshaling authenticator: %v", err) } return nil } func authenticatorKeyUsage(pn types.PrincipalName) int { if pn.NameString[0] == "krbtgt" { return keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR } return keyusage.AP_REQ_AUTHENTICATOR } // Unmarshal bytes b into the APReq struct. func (a *APReq) Unmarshal(b []byte) error { var m marshalAPReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.APREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "unmarshal error of AP_REQ") } if m.MsgType != msgtype.KRB_AP_REQ { return NewKRBError(types.PrincipalName{}, "", errorcode.KRB_AP_ERR_MSG_TYPE, errorcode.Lookup(errorcode.KRB_AP_ERR_MSG_TYPE)) } a.PVNO = m.PVNO a.MsgType = m.MsgType a.APOptions = m.APOptions a.EncryptedAuthenticator = m.EncryptedAuthenticator a.Ticket, err = unmarshalTicket(m.Ticket.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "unmarshaling error of Ticket within AP_REQ") } return nil } // Marshal APReq struct. func (a *APReq) Marshal() ([]byte, error) { m := marshalAPReq{ PVNO: a.PVNO, MsgType: a.MsgType, APOptions: a.APOptions, EncryptedAuthenticator: a.EncryptedAuthenticator, } var b []byte b, err := a.Ticket.Marshal() if err != nil { return b, err } m.Ticket = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 3, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AP_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.APREQ) return mk, nil } // Verify an AP_REQ using service's keytab, spn and max acceptable clock skew duration. // The service ticket encrypted part and authenticator will be decrypted as part of this operation. func (a *APReq) Verify(kt *keytab.Keytab, d time.Duration, cAddr types.HostAddress) (bool, error) { // Decrypt ticket's encrypted part with service key //TODO decrypt with service's session key from its TGT is use-to-user. Need to figure out how to get TGT. //if types.IsFlagSet(&a.APOptions, flags.APOptionUseSessionKey) { // err := a.Ticket.Decrypt(tgt.DecryptedEncPart.Key) // if err != nil { // return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of ticket provided using session key") // } //} else { // err := a.Ticket.DecryptEncPart(*kt, &a.Ticket.SName) // if err != nil { // return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided") // } //} err := a.Ticket.DecryptEncPart(kt, &a.Ticket.SName) if err != nil { return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided") } // Check time validity of ticket ok, err := a.Ticket.Valid(d) if err != nil || !ok { return ok, err } // Check client's address is listed in the client addresses in the ticket if len(a.Ticket.DecryptedEncPart.CAddr) > 0 { //If client addresses are present check if any of them match the source IP that sent the APReq //If there is no match return KRB_AP_ERR_BADADDR error. if !types.HostAddressesContains(a.Ticket.DecryptedEncPart.CAddr, cAddr) { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "client address not within the list contained in the service ticket") } } // Decrypt authenticator with session key from ticket's encrypted part err = a.DecryptAuthenticator(a.Ticket.DecryptedEncPart.Key) if err != nil { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BAD_INTEGRITY, "could not decrypt authenticator") } // Check CName in authenticator is the same as that in the ticket if !a.Authenticator.CName.Equal(a.Ticket.DecryptedEncPart.CName) { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket") } // Check the clock skew between the client and the service server ct := a.Authenticator.CTime.Add(time.Duration(a.Authenticator.Cusec) * time.Microsecond) t := time.Now().UTC() if t.Sub(ct) > d || ct.Sub(t) > d { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("clock skew with client too large. greater than %v seconds", d)) } return true, nil } gokrb5-8.4.3/messages/APReq_test.go000066400000000000000000000040571427031340300170670ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalAPReq(t *testing.T) { t.Parallel() var a APReq b, err := hex.DecodeString(testdata.MarshaledKRB5ap_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AP_REQ, a.MsgType, "MsgType is not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.APOptions.Bytes), "AP Options not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "Ticket VNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket SName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "Ticket SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName name string entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Ticket encPart etype not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encPart KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Ticket.EncPart.Cipher, "Ticket encPart cipher not as expected") } func TestMarshalAPReq(t *testing.T) { t.Parallel() var a APReq b, err := hex.DecodeString(testdata.MarshaledKRB5ap_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of Authenticator not as expected") } gokrb5-8.4.3/messages/KDCRep.go000066400000000000000000000337571427031340300161410ustar00rootroot00000000000000package messages // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.4.2 import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/flags" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) type marshalKDCRep struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` PAData types.PADataSequence `asn1:"explicit,optional,tag:2"` CRealm string `asn1:"generalstring,explicit,tag:3"` CName types.PrincipalName `asn1:"explicit,tag:4"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag Ticket asn1.RawValue `asn1:"explicit,tag:5"` EncPart types.EncryptedData `asn1:"explicit,tag:6"` } // KDCRepFields represents the KRB_KDC_REP fields. type KDCRepFields struct { PVNO int MsgType int PAData []types.PAData CRealm string CName types.PrincipalName Ticket Ticket EncPart types.EncryptedData DecryptedEncPart EncKDCRepPart } // ASRep implements RFC 4120 KRB_AS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2. type ASRep struct { KDCRepFields } // TGSRep implements RFC 4120 KRB_TGS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2. type TGSRep struct { KDCRepFields } // EncKDCRepPart is the encrypted part of KRB_KDC_REP. type EncKDCRepPart struct { Key types.EncryptionKey `asn1:"explicit,tag:0"` LastReqs []LastReq `asn1:"explicit,tag:1"` Nonce int `asn1:"explicit,tag:2"` KeyExpiration time.Time `asn1:"generalized,explicit,optional,tag:3"` Flags asn1.BitString `asn1:"explicit,tag:4"` AuthTime time.Time `asn1:"generalized,explicit,tag:5"` StartTime time.Time `asn1:"generalized,explicit,optional,tag:6"` EndTime time.Time `asn1:"generalized,explicit,tag:7"` RenewTill time.Time `asn1:"generalized,explicit,optional,tag:8"` SRealm string `asn1:"generalstring,explicit,tag:9"` SName types.PrincipalName `asn1:"explicit,tag:10"` CAddr []types.HostAddress `asn1:"explicit,optional,tag:11"` EncPAData types.PADataSequence `asn1:"explicit,optional,tag:12"` } // LastReq part of KRB_KDC_REP. type LastReq struct { LRType int32 `asn1:"explicit,tag:0"` LRValue time.Time `asn1:"generalized,explicit,tag:1"` } // Unmarshal bytes b into the ASRep struct. func (k *ASRep) Unmarshal(b []byte) error { var m marshalKDCRep _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREP)) if err != nil { return processUnmarshalReplyError(b, err) } if m.MsgType != msgtype.KRB_AS_REP { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an AS_REP. Expected: %v; Actual: %v", msgtype.KRB_AS_REP, m.MsgType) } //Process the raw ticket within tkt, err := unmarshalTicket(m.Ticket.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within AS_REP") } k.KDCRepFields = KDCRepFields{ PVNO: m.PVNO, MsgType: m.MsgType, PAData: m.PAData, CRealm: m.CRealm, CName: m.CName, Ticket: tkt, EncPart: m.EncPart, } return nil } // Unmarshal bytes b into the TGSRep struct. func (k *TGSRep) Unmarshal(b []byte) error { var m marshalKDCRep _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREP)) if err != nil { return processUnmarshalReplyError(b, err) } if m.MsgType != msgtype.KRB_TGS_REP { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an TGS_REP. Expected: %v; Actual: %v", msgtype.KRB_TGS_REP, m.MsgType) } //Process the raw ticket within tkt, err := unmarshalTicket(m.Ticket.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within TGS_REP") } k.KDCRepFields = KDCRepFields{ PVNO: m.PVNO, MsgType: m.MsgType, PAData: m.PAData, CRealm: m.CRealm, CName: m.CName, Ticket: tkt, EncPart: m.EncPart, } return nil } // Unmarshal bytes b into encrypted part of KRB_KDC_REP. func (e *EncKDCRepPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncASRepPart)) if err != nil { // Try using tag 26 // Ref: RFC 4120 - mentions that some implementations use application tag number 26 wether or not the reply is // a AS-REP or a TGS-REP. _, err = asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncTGSRepPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part within KDC_REP") } } return nil } // DecryptEncPart decrypts the encrypted part of an AS_REP. func (k *ASRep) DecryptEncPart(c *credentials.Credentials) (types.EncryptionKey, error) { var key types.EncryptionKey var err error if c.HasKeytab() { key, err = c.Keytab().GetEncryptionKey(k.CName, k.CRealm, k.EncPart.KVNO, k.EncPart.EType) if err != nil { return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part") } } if c.HasPassword() { key, _, err = crypto.GetKeyFromPassword(c.Password(), k.CName, k.CRealm, k.EncPart.EType, k.PAData) if err != nil { return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part") } } if !c.HasKeytab() && !c.HasPassword() { return key, krberror.NewErrorf(krberror.DecryptingError, "no secret available in credentials to perform decryption of AS_REP encrypted part") } b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.AS_REP_ENCPART) if err != nil { return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part") } var denc EncKDCRepPart err = denc.Unmarshal(b) if err != nil { return key, krberror.Errorf(err, krberror.EncodingError, "error unmarshaling decrypted encpart of AS_REP") } k.DecryptedEncPart = denc return key, nil } // Verify checks the validity of AS_REP message. func (k *ASRep) Verify(cfg *config.Config, creds *credentials.Credentials, asReq ASReq) (bool, error) { //Ref RFC 4120 Section 3.1.5 if k.CName.NameType != asReq.ReqBody.CName.NameType || k.CName.NameString == nil { return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName) } for i := range k.CName.NameString { if k.CName.NameString[i] != asReq.ReqBody.CName.NameString[i] { return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName) } } if k.CRealm != asReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "CRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.CRealm) } key, err := k.DecryptEncPart(creds) if err != nil { return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting EncPart of AS_REP") } if k.DecryptedEncPart.Nonce != asReq.ReqBody.Nonce { return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request") } if k.DecryptedEncPart.SName.NameType != asReq.ReqBody.SName.NameType || k.DecryptedEncPart.SName.NameString == nil { return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", asReq.ReqBody.SName, k.DecryptedEncPart.SName) } for i := range k.CName.NameString { if k.DecryptedEncPart.SName.NameString[i] != asReq.ReqBody.SName.NameString[i] { return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.SName, k.DecryptedEncPart.SName) } } if k.DecryptedEncPart.SRealm != asReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.DecryptedEncPart.SRealm) } if len(asReq.ReqBody.Addresses) > 0 { if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, asReq.ReqBody.Addresses) { return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the AS_REP does not match those listed in the AS_REQ") } } t := time.Now().UTC() if t.Sub(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(t) > cfg.LibDefaults.Clockskew { return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds()) } // RFC 6806 https://tools.ietf.org/html/rfc6806.html#section-11 if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) && types.IsFlagSet(&k.DecryptedEncPart.Flags, flags.EncPARep) { if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) { return false, krberror.NewErrorf(krberror.KRBMsgError, "KDC did not respond appropriately to FAST negotiation") } for _, pa := range k.DecryptedEncPart.EncPAData { if pa.PADataType == patype.PA_REQ_ENC_PA_REP { var pafast types.PAReqEncPARep err := pafast.Unmarshal(pa.PADataValue) if err != nil { return false, krberror.Errorf(err, krberror.EncodingError, "KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP") } etype, err := crypto.GetChksumEtype(pafast.ChksumType) if err != nil { return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response error") } ab, _ := asReq.Marshal() if !etype.VerifyChecksum(key.KeyValue, ab, pafast.Chksum, keyusage.KEY_USAGE_AS_REQ) { return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response checksum invalid") } } } } return true, nil } // DecryptEncPart decrypts the encrypted part of an TGS_REP. func (k *TGSRep) DecryptEncPart(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.TGS_REP_ENCPART_SESSION_KEY) if err != nil { return krberror.Errorf(err, krberror.DecryptingError, "error decrypting TGS_REP EncPart") } var denc EncKDCRepPart err = denc.Unmarshal(b) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part") } k.DecryptedEncPart = denc return nil } // Verify checks the validity of the TGS_REP message. func (k *TGSRep) Verify(cfg *config.Config, tgsReq TGSReq) (bool, error) { if k.CName.NameType != tgsReq.ReqBody.CName.NameType || k.CName.NameString == nil { return false, krberror.NewErrorf(krberror.KRBMsgError, "CName type in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.CName, k.CName) } for i := range k.CName.NameString { if k.CName.NameString[i] != tgsReq.ReqBody.CName.NameString[i] { return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.CName, k.CName) } } if k.Ticket.Realm != tgsReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "realm in response ticket does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.Ticket.Realm) } if k.DecryptedEncPart.Nonce != tgsReq.ReqBody.Nonce { return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request") } //if k.Ticket.SName.NameType != tgsReq.ReqBody.SName.NameType || k.Ticket.SName.NameString == nil { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.Ticket.SName) //} //for i := range k.Ticket.SName.NameString { // if k.Ticket.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.Ticket.SName) // } //} //if k.DecryptedEncPart.SName.NameType != tgsReq.ReqBody.SName.NameType || k.DecryptedEncPart.SName.NameString == nil { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName) //} //for i := range k.DecryptedEncPart.SName.NameString { // if k.DecryptedEncPart.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName) // } //} if k.DecryptedEncPart.SRealm != tgsReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.DecryptedEncPart.SRealm) } if len(k.DecryptedEncPart.CAddr) > 0 { if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, tgsReq.ReqBody.Addresses) { return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the TGS_REP does not match those listed in the TGS_REQ") } } if time.Since(k.DecryptedEncPart.StartTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.StartTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew { if time.Since(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew { return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds.", cfg.LibDefaults.Clockskew.Seconds()) } } return true, nil } gokrb5-8.4.3/messages/KDCRep_test.go000066400000000000000000000510611427031340300171640ustar00rootroot00000000000000package messages import ( "encoding/hex" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) const ( testuser1EType18Keytab = "05020000004b0001000b544553542e474f4b5242350009746573747573657231000000015898e0770100120020bbdc430aab7e2d4622a0b6951481453b0962e9db8e2f168942ad175cda6d9de900000001" testuser1EType18ASREP = "6b8202f3308202efa003020105a10302010ba22e302c302aa103020113a2230421301f301da003020112a1161b14544553542e474f4b524235746573747573657231a30d1b0b544553542e474f4b524235a4163014a003020101a10d300b1b09746573747573657231a582015a6182015630820152a003020105a10d1b0b544553542e474f4b524235a220301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a382011830820114a003020112a103020101a28201060482010237e486e32cd18ab1ac9f8d42e93f8babd7b3497084cc5599f18ec61961c6d5242d350354d99d67a7604c451116188d16cb719e84377212eac2743440e8c504ef69c755e489cc6b65f935dd032bfc076f9b2c56d816197845b8fe857d738bc59712787631a50e86833d1b0e4732c8712c856417a6a257758e7d01d3182adb3233f0dde65d228c240ed26aa1af69f8d765dc0bc69096fdb037a75af220fea176839528d44b70f7dabfaa2ea506de1296f847176a60c501fd8cef8e0a51399bb6d5f753962d96292e93ffe344c6630db912931d46d88c0279f00719e22d0efcfd4ee33a702d0b660c1f13970a9beec12c0c8af3dda68bd81ac1fe3f126d2a24ebb445c5a682012c30820128a003020112a282011f0482011bb149cc16018072c4c18788d95a33aba540e52c11b54a93e67e788d05de75d8f3d4aa1afafbbfa6fde3eb40e5aa1890644cea2607efd5213a3fd00345b02eeb9ae1b589f36c74c689cd4ec1239dfe61e42ba6afa33f6240e3cfab291e4abb465d273302dbf7dbd148a299a9369044dd03377c1687e7dd36aa66501284a4ca50c0a7b08f4f87aecfa23b0dd0b11490e3ad330906dab715de81fc52f120d09c39990b8b5330d4601cc396b2ed258834329c4cc02c563a12de3ef9bf11e946258bc2ab5257f4caa4d443a7daf0fc25f6f531c2fcba88af8ca55c85300997cd05abbea52811fe2d038ba8f62fc8e3bc71ce04362d356ea2e1df8ac55c784c53cfb07817d48e39fe99fc8788040d98209c79dcf044d97e80de9f47824646" testRealm = "TEST.GOKRB5" testUser = "testuser1" testUserPassword = "passwordvalue" ) func TestUnmarshalASRep(t *testing.T) { t.Parallel() var a ASRep b, err := hex.DecodeString(testdata.MarshaledKRB5as_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestUnmarshalASRep_optionalsNULL(t *testing.T) { t.Parallel() var a ASRep b, err := hex.DecodeString(testdata.MarshaledKRB5as_repOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestUnmarshalTGSRep(t *testing.T) { t.Parallel() var a TGSRep b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestUnmarshalTGSRep_optionalsNULL(t *testing.T) { t.Parallel() var a TGSRep b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_repOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestUnmarshalEncKDCRepPart(t *testing.T) { t.Parallel() var a EncKDCRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_kdc_rep_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, 2, len(a.LastReqs), "Number of last request entries not as expected") for i, r := range a.LastReqs { assert.Equal(t, int32(-5), r.LRType, fmt.Sprintf("Last request typ not as expected for last request entry %d", i+1)) assert.Equal(t, tt, r.LRValue, fmt.Sprintf("Last request time value not as expected for last request entry %d", i+1)) } assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Nonce not as expected") assert.Equal(t, tt, a.KeyExpiration, "key expiration time not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.StartTime, "Start time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") assert.Equal(t, tt, a.RenewTill, "Renew Till time not as expected") assert.Equal(t, testdata.TEST_REALM, a.SRealm, "SRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "SName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "SName string entries not as expected") assert.Equal(t, 2, len(a.CAddr), "Number of client addresses not as expected") for i, addr := range a.CAddr { assert.Equal(t, int32(2), addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } } func TestUnmarshalEncKDCRepPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncKDCRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_kdc_rep_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, 2, len(a.LastReqs), "Number of last request entries not as expected") for i, r := range a.LastReqs { assert.Equal(t, int32(-5), r.LRType, fmt.Sprintf("Last request typ not as expected for last request entry %d", i+1)) assert.Equal(t, tt, r.LRValue, fmt.Sprintf("Last request time value not as expected for last request entry %d", i+1)) } assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Nonce not as expected") assert.Equal(t, "fe5cba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") assert.Equal(t, testdata.TEST_REALM, a.SRealm, "SRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "SName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "SName string entries not as expected") } func TestUnmarshalASRepDecodeAndDecrypt(t *testing.T) { t.Parallel() var asRep ASRep b, _ := hex.DecodeString(testuser1EType18ASREP) err := asRep.Unmarshal(b) if err != nil { t.Fatalf("AS REP Unmarshal error: %v\n", err) } assert.Equal(t, 5, asRep.PVNO, "PVNO not as expected") assert.Equal(t, 11, asRep.MsgType, "MsgType not as expected") assert.Equal(t, testRealm, asRep.CRealm, "Client Realm not as expected") assert.Equal(t, int32(1), asRep.CName.NameType, "CName NameType not as expected") assert.Equal(t, testUser, asRep.CName.NameString[0], "CName NameType not as expected") assert.Equal(t, int32(19), asRep.PAData[0].PADataType, "PADataType not as expected") assert.Equal(t, 5, asRep.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testRealm, asRep.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, int32(2), asRep.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, "krbtgt", asRep.Ticket.SName.NameString[0], "Ticket service name string not as expected") assert.Equal(t, testRealm, asRep.Ticket.SName.NameString[1], "Ticket service name string not as expected") assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], asRep.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, 1, asRep.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], asRep.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, 0, asRep.EncPart.KVNO, "Encrypted part KVNO not as expected") //t.Log("Finished testing unecrypted parts of AS REP") ktb, _ := hex.DecodeString(testuser1EType18Keytab) kt := keytab.New() err = kt.Unmarshal(ktb) if err != nil { t.Fatalf("keytab parse error: %v\n", err) } cred := credentials.New(testUser, testRealm) _, err = asRep.DecryptEncPart(cred.WithKeytab(kt)) if err != nil { t.Fatalf("Decryption of AS_REP EncPart failed: %v", err) } assert.Equal(t, int32(18), asRep.DecryptedEncPart.Key.KeyType, "KeyType in decrypted EncPart not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.LastReqs[0].LRValue, "LastReqs did not have a time value") assert.Equal(t, 2069991465, asRep.DecryptedEncPart.Nonce, "Nonce value not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.KeyExpiration, "Key expiration not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.AuthTime, "AuthTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.StartTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.EndTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.RenewTill, "RenewTill not a time type") assert.Equal(t, testRealm, asRep.DecryptedEncPart.SRealm, "Service realm not as expected") assert.Equal(t, int32(2), asRep.DecryptedEncPart.SName.NameType, "Name type for AS_REP not as expected") assert.Equal(t, []string{"krbtgt", testRealm}, asRep.DecryptedEncPart.SName.NameString, "Service name string not as expected") } func TestUnmarshalASRepDecodeAndDecrypt_withPassword(t *testing.T) { t.Parallel() var asRep ASRep b, _ := hex.DecodeString(testuser1EType18ASREP) err := asRep.Unmarshal(b) if err != nil { t.Fatalf("AS REP Unmarshal error: %v\n", err) } assert.Equal(t, 5, asRep.PVNO, "PVNO not as expected") assert.Equal(t, 11, asRep.MsgType, "MsgType not as expected") assert.Equal(t, testRealm, asRep.CRealm, "Client Realm not as expected") assert.Equal(t, int32(1), asRep.CName.NameType, "CName NameType not as expected") assert.Equal(t, testUser, asRep.CName.NameString[0], "CName NameType not as expected") assert.Equal(t, int32(19), asRep.PAData[0].PADataType, "PADataType not as expected") assert.Equal(t, 5, asRep.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testRealm, asRep.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, int32(2), asRep.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, "krbtgt", asRep.Ticket.SName.NameString[0], "Ticket service name string not as expected") assert.Equal(t, testRealm, asRep.Ticket.SName.NameString[1], "Ticket service name string not as expected") assert.Equal(t, etypeID.AES256_CTS_HMAC_SHA1_96, asRep.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, 1, asRep.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, etypeID.AES256_CTS_HMAC_SHA1_96, asRep.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, 0, asRep.EncPart.KVNO, "Encrypted part KVNO not as expected") cred := credentials.New(testUser, testRealm) _, err = asRep.DecryptEncPart(cred.WithPassword(testUserPassword)) if err != nil { t.Fatalf("Decryption of AS_REP EncPart failed: %v", err) } assert.Equal(t, int32(18), asRep.DecryptedEncPart.Key.KeyType, "KeyType in decrypted EncPart not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.LastReqs[0].LRValue, "LastReqs did not have a time value") assert.Equal(t, 2069991465, asRep.DecryptedEncPart.Nonce, "Nonce value not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.KeyExpiration, "Key expiration not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.AuthTime, "AuthTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.StartTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.EndTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.RenewTill, "RenewTill not a time type") assert.Equal(t, testRealm, asRep.DecryptedEncPart.SRealm, "Service realm not as expected") assert.Equal(t, nametype.KRB_NT_SRV_INST, asRep.DecryptedEncPart.SName.NameType, "Name type for AS_REP not as expected") assert.Equal(t, []string{"krbtgt", testRealm}, asRep.DecryptedEncPart.SName.NameString, "Service name string not as expected") } gokrb5-8.4.3/messages/KDCReq.go000066400000000000000000000336161427031340300161340ustar00rootroot00000000000000package messages // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.4.1 import ( "crypto/rand" "fmt" "math" "math/big" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/flags" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) type marshalKDCReq struct { PVNO int `asn1:"explicit,tag:1"` MsgType int `asn1:"explicit,tag:2"` PAData types.PADataSequence `asn1:"explicit,optional,tag:3"` ReqBody asn1.RawValue `asn1:"explicit,tag:4"` } // KDCReqFields represents the KRB_KDC_REQ fields. type KDCReqFields struct { PVNO int MsgType int PAData types.PADataSequence ReqBody KDCReqBody Renewal bool } // ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1. type ASReq struct { KDCReqFields } // TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1. type TGSReq struct { KDCReqFields } type marshalKDCReqBody struct { KDCOptions asn1.BitString `asn1:"explicit,tag:0"` CName types.PrincipalName `asn1:"explicit,optional,tag:1"` Realm string `asn1:"generalstring,explicit,tag:2"` SName types.PrincipalName `asn1:"explicit,optional,tag:3"` From time.Time `asn1:"generalized,explicit,optional,tag:4"` Till time.Time `asn1:"generalized,explicit,tag:5"` RTime time.Time `asn1:"generalized,explicit,optional,tag:6"` Nonce int `asn1:"explicit,tag:7"` EType []int32 `asn1:"explicit,tag:8"` Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"` EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"` } // KDCReqBody implements the KRB_KDC_REQ request body. type KDCReqBody struct { KDCOptions asn1.BitString `asn1:"explicit,tag:0"` CName types.PrincipalName `asn1:"explicit,optional,tag:1"` Realm string `asn1:"generalstring,explicit,tag:2"` SName types.PrincipalName `asn1:"explicit,optional,tag:3"` From time.Time `asn1:"generalized,explicit,optional,tag:4"` Till time.Time `asn1:"generalized,explicit,tag:5"` RTime time.Time `asn1:"generalized,explicit,optional,tag:6"` Nonce int `asn1:"explicit,tag:7"` EType []int32 `asn1:"explicit,tag:8"` Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"` EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"` AdditionalTickets []Ticket `asn1:"explicit,optional,tag:11"` } // NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request. func NewASReqForTGT(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) { sname := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } return NewASReq(realm, c, cname, sname) } // NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request. func NewASReqForChgPasswd(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) { sname := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"kadmin", "changepw"}, } return NewASReq(realm, c, cname, sname) } // NewASReq generates a new KRB_AS_REQ struct for a given SNAME. func NewASReq(realm string, c *config.Config, cname, sname types.PrincipalName) (ASReq, error) { nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) if err != nil { return ASReq{}, err } t := time.Now().UTC() // Copy the default options to make this thread safe kopts := types.NewKrbFlags() copy(kopts.Bytes, c.LibDefaults.KDCDefaultOptions.Bytes) kopts.BitLength = c.LibDefaults.KDCDefaultOptions.BitLength a := ASReq{ KDCReqFields{ PVNO: iana.PVNO, MsgType: msgtype.KRB_AS_REQ, PAData: types.PADataSequence{}, ReqBody: KDCReqBody{ KDCOptions: kopts, Realm: realm, CName: cname, SName: sname, Till: t.Add(c.LibDefaults.TicketLifetime), Nonce: int(nonce.Int64()), EType: c.LibDefaults.DefaultTktEnctypeIDs, }, }, } if c.LibDefaults.Forwardable { types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable) } if c.LibDefaults.Canonicalize { types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize) } if c.LibDefaults.Proxiable { types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable) } if c.LibDefaults.RenewLifetime != 0 { types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable) a.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime) a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour) } if !c.LibDefaults.NoAddresses { ha, err := types.LocalHostAddresses() if err != nil { return a, fmt.Errorf("could not get local addresses: %v", err) } ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...) a.ReqBody.Addresses = ha } return a, nil } // NewTGSReq generates a new KRB_TGS_REQ struct. func NewTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, tgt Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool) (TGSReq, error) { a, err := tgsReq(cname, sname, kdcRealm, renewal, c) if err != nil { return a, err } err = a.setPAData(tgt, sessionKey) return a, err } // NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7) func NewUser2UserTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, clientTGT Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool, verifyingTGT Ticket) (TGSReq, error) { a, err := tgsReq(cname, sname, kdcRealm, renewal, c) if err != nil { return a, err } a.ReqBody.AdditionalTickets = []Ticket{verifyingTGT} types.SetFlag(&a.ReqBody.KDCOptions, flags.EncTktInSkey) err = a.setPAData(clientTGT, sessionKey) return a, err } // tgsReq populates the fields for a TGS_REQ func tgsReq(cname, sname types.PrincipalName, kdcRealm string, renewal bool, c *config.Config) (TGSReq, error) { nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) if err != nil { return TGSReq{}, err } t := time.Now().UTC() k := KDCReqFields{ PVNO: iana.PVNO, MsgType: msgtype.KRB_TGS_REQ, ReqBody: KDCReqBody{ KDCOptions: types.NewKrbFlags(), Realm: kdcRealm, CName: cname, // Add the CName to make validation of the reply easier SName: sname, Till: t.Add(c.LibDefaults.TicketLifetime), Nonce: int(nonce.Int64()), EType: c.LibDefaults.DefaultTGSEnctypeIDs, }, Renewal: renewal, } if c.LibDefaults.Forwardable { types.SetFlag(&k.ReqBody.KDCOptions, flags.Forwardable) } if c.LibDefaults.Canonicalize { types.SetFlag(&k.ReqBody.KDCOptions, flags.Canonicalize) } if c.LibDefaults.Proxiable { types.SetFlag(&k.ReqBody.KDCOptions, flags.Proxiable) } if c.LibDefaults.RenewLifetime > time.Duration(0) { types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable) k.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime) } if !c.LibDefaults.NoAddresses { ha, err := types.LocalHostAddresses() if err != nil { return TGSReq{}, fmt.Errorf("could not get local addresses: %v", err) } ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...) k.ReqBody.Addresses = ha } if renewal { types.SetFlag(&k.ReqBody.KDCOptions, flags.Renew) types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable) } return TGSReq{ k, }, nil } func (k *TGSReq) setPAData(tgt Ticket, sessionKey types.EncryptionKey) error { // Marshal the request and calculate checksum b, err := k.ReqBody.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REQ body") } etype, err := crypto.GetEtype(sessionKey.KeyType) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype to encrypt authenticator") } cb, err := etype.GetChecksumHash(sessionKey.KeyValue, b, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM) if err != nil { return krberror.Errorf(err, krberror.ChksumError, "error getting etype checksum hash") } // Form PAData for TGS_REQ // Create authenticator auth, err := types.NewAuthenticator(tgt.Realm, k.ReqBody.CName) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") } auth.Cksum = types.Checksum{ CksumType: etype.GetHashID(), Checksum: cb, } // Create AP_REQ apReq, err := NewAPReq(tgt, sessionKey, auth) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AP_REQ") } apb, err := apReq.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data") } k.PAData = types.PADataSequence{ types.PAData{ PADataType: patype.PA_TGS_REQ, PADataValue: apb, }, } return nil } // Unmarshal bytes b into the ASReq struct. func (k *ASReq) Unmarshal(b []byte) error { var m marshalKDCReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling AS_REQ") } expectedMsgType := msgtype.KRB_AS_REQ if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } var reqb KDCReqBody err = reqb.Unmarshal(m.ReqBody.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error processing AS_REQ body") } k.MsgType = m.MsgType k.PAData = m.PAData k.PVNO = m.PVNO k.ReqBody = reqb return nil } // Unmarshal bytes b into the TGSReq struct. func (k *TGSReq) Unmarshal(b []byte) error { var m marshalKDCReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling TGS_REQ") } expectedMsgType := msgtype.KRB_TGS_REQ if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } var reqb KDCReqBody err = reqb.Unmarshal(m.ReqBody.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error processing TGS_REQ body") } k.MsgType = m.MsgType k.PAData = m.PAData k.PVNO = m.PVNO k.ReqBody = reqb return nil } // Unmarshal bytes b into the KRB_KDC_REQ body struct. func (k *KDCReqBody) Unmarshal(b []byte) error { var m marshalKDCReqBody _, err := asn1.Unmarshal(b, &m) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling KDC_REQ body") } k.KDCOptions = m.KDCOptions if len(k.KDCOptions.Bytes) < 4 { tb := make([]byte, 4-len(k.KDCOptions.Bytes)) k.KDCOptions.Bytes = append(tb, k.KDCOptions.Bytes...) k.KDCOptions.BitLength = len(k.KDCOptions.Bytes) * 8 } k.CName = m.CName k.Realm = m.Realm k.SName = m.SName k.From = m.From k.Till = m.Till k.RTime = m.RTime k.Nonce = m.Nonce k.EType = m.EType k.Addresses = m.Addresses k.EncAuthData = m.EncAuthData if len(m.AdditionalTickets.Bytes) > 0 { k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets") } } return nil } // Marshal ASReq struct. func (k *ASReq) Marshal() ([]byte, error) { m := marshalKDCReq{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, } b, err := k.ReqBody.Marshal() if err != nil { var mk []byte return mk, err } m.ReqBody = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 4, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREQ) return mk, nil } // Marshal TGSReq struct. func (k *TGSReq) Marshal() ([]byte, error) { m := marshalKDCReq{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, } b, err := k.ReqBody.Marshal() if err != nil { var mk []byte return mk, err } m.ReqBody = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 4, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREQ) return mk, nil } // Marshal KRB_KDC_REQ body struct. func (k *KDCReqBody) Marshal() ([]byte, error) { var b []byte m := marshalKDCReqBody{ KDCOptions: k.KDCOptions, CName: k.CName, Realm: k.Realm, SName: k.SName, From: k.From, Till: k.Till, RTime: k.RTime, Nonce: k.Nonce, EType: k.EType, Addresses: k.Addresses, EncAuthData: k.EncAuthData, } rawtkts, err := MarshalTicketSequence(k.AdditionalTickets) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body additional tickets") } //The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody rawtkts.Tag = 11 if len(rawtkts.Bytes) > 0 { m.AdditionalTickets = rawtkts } b, err = asn1.Marshal(m) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body") } return b, nil } gokrb5-8.4.3/messages/KDCReq_test.go000066400000000000000000000653531427031340300171760ustar00rootroot00000000000000package messages import ( "encoding/hex" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalKDCReqBody(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_body) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba90", hex.EncodeToString(a.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "Request body CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "Request body CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "Request body CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.From, "Request body From time not as expected") assert.Equal(t, tt, a.Till, "Request body Till time not as expected") assert.Equal(t, tt, a.RTime, "Request body RTime time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.EType, "Etype list not as expected") assert.Equal(t, 2, len(a.Addresses), "Number of client addresses not as expected") for i, addr := range a.Addresses { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.EncAuthData.EType, "Etype of request body encrypted authorization data not as expected") assert.Equal(t, iana.PVNO, a.EncAuthData.KVNO, "KVNO of request body encrypted authorization data not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncAuthData.Cipher, "Ciphertext of request body encrypted authorization data not as expected") assert.Equal(t, 2, len(a.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalKDCReqBody_optionalsNULLexceptsecond_ticket(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_bodyOptionalsNULLexceptsecond_ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba98", hex.EncodeToString(a.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Request body Realm not as expected") assert.Equal(t, tt, a.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 2, len(a.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalKDCReqBody_optionalsNULLexceptserver(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_bodyOptionalsNULLexceptserver) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba90", hex.EncodeToString(a.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 0, len(a.AdditionalTickets), "Number of additional tickets not empty") } func TestUnmarshalASReq(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.CName.NameType, "Request body CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.CName.NameString), "Request body CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.CName.NameString, "Request body CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.From, "Request body From time not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, tt, a.ReqBody.RTime, "Request body RTime time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 2, len(a.ReqBody.Addresses), "Number of client addresses not as expected") for i, addr := range a.ReqBody.Addresses { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.ReqBody.EncAuthData.EType, "Etype of request body encrypted authorization data not as expected") assert.Equal(t, iana.PVNO, a.ReqBody.EncAuthData.KVNO, "KVNO of request body encrypted authorization data not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.ReqBody.EncAuthData.Cipher, "Ciphertext of request body encrypted authorization data not as expected") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalASReq_optionalsNULLexceptsecond_ticket(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_reqOptionalsNULLexceptsecond_ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalASReq_optionalsNULLexceptserver(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_reqOptionalsNULLexceptserver) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 0, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not empty") } func TestUnmarshalTGSReq(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.CName.NameType, "Request body CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.CName.NameString), "Request body CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.CName.NameString, "Request body CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.From, "Request body From time not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, tt, a.ReqBody.RTime, "Request body RTime time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 2, len(a.ReqBody.Addresses), "Number of client addresses not as expected") for i, addr := range a.ReqBody.Addresses { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.ReqBody.EncAuthData.EType, "Etype of request body encrypted authorization data not as expected") assert.Equal(t, iana.PVNO, a.ReqBody.EncAuthData.KVNO, "KVNO of request body encrypted authorization data not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.ReqBody.EncAuthData.Cipher, "Ciphertext of request body encrypted authorization data not as expected") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalTGSReq_optionalsNULLexceptsecond_ticket(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_reqOptionalsNULLexceptsecond_ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalTGSReq_optionalsNULLexceptserver(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_reqOptionalsNULLexceptserver) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 0, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not empty") } //// Marshal Tests //// func TestMarshalKDCReqBody(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_body) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } // Marshal and re-unmarshal the result nd then compare mb, err := a.Marshal() if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of KDCReqBody not as expected") } func TestMarshalASReq(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of ASReq not as expected") } func TestMarshalTGSReq(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of TGSReq not as expected") } gokrb5-8.4.3/messages/KRBCred.go000066400000000000000000000073741427031340300163010ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) type marshalKRBCred struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` Tickets asn1.RawValue `asn1:"explicit,tag:2"` EncPart types.EncryptedData `asn1:"explicit,tag:3"` } // KRBCred implements RFC 4120 KRB_CRED: https://tools.ietf.org/html/rfc4120#section-5.8.1. type KRBCred struct { PVNO int MsgType int Tickets []Ticket EncPart types.EncryptedData DecryptedEncPart EncKrbCredPart } // EncKrbCredPart is the encrypted part of KRB_CRED. type EncKrbCredPart struct { TicketInfo []KrbCredInfo `asn1:"explicit,tag:0"` Nouce int `asn1:"optional,explicit,tag:1"` Timestamp time.Time `asn1:"generalized,optional,explicit,tag:2"` Usec int `asn1:"optional,explicit,tag:3"` SAddress types.HostAddress `asn1:"optional,explicit,tag:4"` RAddress types.HostAddress `asn1:"optional,explicit,tag:5"` } // KrbCredInfo is the KRB_CRED_INFO part of KRB_CRED. type KrbCredInfo struct { Key types.EncryptionKey `asn1:"explicit,tag:0"` PRealm string `asn1:"generalstring,optional,explicit,tag:1"` PName types.PrincipalName `asn1:"optional,explicit,tag:2"` Flags asn1.BitString `asn1:"optional,explicit,tag:3"` AuthTime time.Time `asn1:"generalized,optional,explicit,tag:4"` StartTime time.Time `asn1:"generalized,optional,explicit,tag:5"` EndTime time.Time `asn1:"generalized,optional,explicit,tag:6"` RenewTill time.Time `asn1:"generalized,optional,explicit,tag:7"` SRealm string `asn1:"optional,explicit,ia5,tag:8"` SName types.PrincipalName `asn1:"optional,explicit,tag:9"` CAddr types.HostAddresses `asn1:"optional,explicit,tag:10"` } // Unmarshal bytes b into the KRBCred struct. func (k *KRBCred) Unmarshal(b []byte) error { var m marshalKRBCred _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBCred)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_CRED if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_CRED. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } k.PVNO = m.PVNO k.MsgType = m.MsgType k.EncPart = m.EncPart if len(m.Tickets.Bytes) > 0 { k.Tickets, err = unmarshalTicketsSequence(m.Tickets) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling tickets within KRB_CRED") } } return nil } // DecryptEncPart decrypts the encrypted part of a KRB_CRED. func (k *KRBCred) DecryptEncPart(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.KRB_CRED_ENCPART) if err != nil { return krberror.Errorf(err, krberror.DecryptingError, "error decrypting KRB_CRED EncPart") } var denc EncKrbCredPart err = denc.Unmarshal(b) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part of KRB_CRED") } k.DecryptedEncPart = denc return nil } // Unmarshal bytes b into the encrypted part of KRB_CRED. func (k *EncKrbCredPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncKrbCredPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling EncKrbCredPart") } return nil } gokrb5-8.4.3/messages/KRBCred_test.go000066400000000000000000000213341427031340300173300ustar00rootroot00000000000000package messages import ( "encoding/hex" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalKRBCred(t *testing.T) { t.Parallel() var a KRBCred b, err := hex.DecodeString(testdata.MarshaledKRB5cred) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_CRED, a.MsgType, "Message type not as expected") assert.Equal(t, 2, len(a.Tickets), "Number of tickets not as expected") for i, tkt := range a.Tickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Ticket (%v) encPart cipher not as expected", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "encPart etype not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "encPart KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "encPart cipher not as expected") } func TestUnmarshalEncCredPart(t *testing.T) { t.Parallel() var a EncKrbCredPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_cred_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, 2, len(a.TicketInfo), "Number of ticket info items not as expected") for i, tkt := range a.TicketInfo { assert.Equal(t, int32(1), tkt.Key.KeyType, fmt.Sprintf("Key type not as expected in ticket info item %d", i+1)) assert.Equal(t, []byte("12345678"), tkt.Key.KeyValue, fmt.Sprintf("Key value not as expected in ticket info item %d", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.PRealm, fmt.Sprintf("PRealm not as expected on ticket info item %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.PName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.PName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.PName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, "fedcba98", hex.EncodeToString(tkt.Flags.Bytes), fmt.Sprintf("Flags not as expected on ticket info %d", i+1)) assert.Equal(t, tt, tkt.AuthTime, fmt.Sprintf("Auth time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, tkt.StartTime, fmt.Sprintf("Start time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, tkt.EndTime, fmt.Sprintf("End time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, tkt.RenewTill, fmt.Sprintf("Renew Till time value not as expected for ticket info %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, 2, len(tkt.CAddr), "Number of client addresses not as expected") for j, addr := range tkt.CAddr { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d within ticket info %d", j+1, i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d within ticket info %d", j+1, i+1)) } } assert.Equal(t, testdata.TEST_NONCE, a.Nouce, "Nouce not as expected") assert.Equal(t, tt, a.Timestamp, "Timestamp not as expected") assert.Equal(t, 123456, a.Usec, "Microseconds not as expected") assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SAddress.Address), "Address not as expected for SAddress") assert.Equal(t, addrtype.IPv4, a.RAddress.AddrType, "RAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.RAddress.Address), "Address not as expected for RAddress") } func TestUnmarshalEncCredPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncKrbCredPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_cred_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, 2, len(a.TicketInfo), "Number of ticket info items not as expected") //1st Ticket i := 0 assert.Equal(t, int32(1), a.TicketInfo[i].Key.KeyType, fmt.Sprintf("Key type not as expected in ticket info item %d", i+1)) assert.Equal(t, []byte("12345678"), a.TicketInfo[i].Key.KeyValue, fmt.Sprintf("Key value not as expected in ticket info item %d", i+1)) //2nd Ticket i = 1 assert.Equal(t, int32(1), a.TicketInfo[i].Key.KeyType, fmt.Sprintf("Key type not as expected in ticket info item %d", i+1)) assert.Equal(t, []byte("12345678"), a.TicketInfo[i].Key.KeyValue, fmt.Sprintf("Key value not as expected in ticket info item %d", i+1)) assert.Equal(t, testdata.TEST_REALM, a.TicketInfo[i].PRealm, fmt.Sprintf("PRealm not as expected on ticket info item %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.TicketInfo[i].PName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.TicketInfo[i].PName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.TicketInfo[i].PName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, "fedcba98", hex.EncodeToString(a.TicketInfo[i].Flags.Bytes), fmt.Sprintf("Flags not as expected on ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].AuthTime, fmt.Sprintf("Auth time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].StartTime, fmt.Sprintf("Start time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].EndTime, fmt.Sprintf("End time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].RenewTill, fmt.Sprintf("Renew Till time value not as expected for ticket info %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.TicketInfo[i].SName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.TicketInfo[i].SName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.TicketInfo[i].SName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, 2, len(a.TicketInfo[i].CAddr), "Number of client addresses not as expected") for j, addr := range a.TicketInfo[i].CAddr { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d within ticket info %d", j+1, i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d within ticket info %d", j+1, i+1)) } } gokrb5-8.4.3/messages/KRBError.go000066400000000000000000000056441427031340300165130ustar00rootroot00000000000000// Package messages implements Kerberos 5 message types and methods. package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) // KRBError implements RFC 4120 KRB_ERROR: https://tools.ietf.org/html/rfc4120#section-5.9.1. type KRBError struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` CTime time.Time `asn1:"generalized,optional,explicit,tag:2"` Cusec int `asn1:"optional,explicit,tag:3"` STime time.Time `asn1:"generalized,explicit,tag:4"` Susec int `asn1:"explicit,tag:5"` ErrorCode int32 `asn1:"explicit,tag:6"` CRealm string `asn1:"generalstring,optional,explicit,tag:7"` CName types.PrincipalName `asn1:"optional,explicit,tag:8"` Realm string `asn1:"generalstring,explicit,tag:9"` SName types.PrincipalName `asn1:"explicit,tag:10"` EText string `asn1:"generalstring,optional,explicit,tag:11"` EData []byte `asn1:"optional,explicit,tag:12"` } // NewKRBError creates a new KRBError. func NewKRBError(sname types.PrincipalName, realm string, code int32, etext string) KRBError { t := time.Now().UTC() return KRBError{ PVNO: iana.PVNO, MsgType: msgtype.KRB_ERROR, STime: t, Susec: int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)), ErrorCode: code, SName: sname, Realm: realm, EText: etext, } } // Unmarshal bytes b into the KRBError struct. func (k *KRBError) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBError)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "KRB_ERROR unmarshal error") } expectedMsgType := msgtype.KRB_ERROR if k.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_ERROR. Expected: %v; Actual: %v", expectedMsgType, k.MsgType) } return nil } // Error method implementing error interface on KRBError struct. func (k KRBError) Error() string { etxt := fmt.Sprintf("KRB Error: %s", errorcode.Lookup(k.ErrorCode)) if k.EText != "" { etxt = fmt.Sprintf("%s - %s", etxt, k.EText) } return etxt } func processUnmarshalReplyError(b []byte, err error) error { switch err.(type) { case asn1.StructuralError: var krberr KRBError tmperr := krberr.Unmarshal(b) if tmperr != nil { return krberror.Errorf(err, krberror.EncodingError, "failed to unmarshal KDC's reply") } return krberr default: return krberror.Errorf(err, krberror.EncodingError, "failed to unmarshal KDC's reply") } } gokrb5-8.4.3/messages/KRBError_test.go000066400000000000000000000067311427031340300175500ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalKRBError(t *testing.T) { t.Parallel() var a KRBError b, err := hex.DecodeString(testdata.MarshaledKRB5error) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO is not as expected") assert.Equal(t, msgtype.KRB_ERROR, a.MsgType, "Message type is not as expected") assert.Equal(t, tt, a.CTime, "CTime not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.STime, "STime not as expected") assert.Equal(t, 123456, a.Susec, "Service microseconds not as expected") assert.Equal(t, errorcode.KRB_ERR_GENERIC, a.ErrorCode, "Error code not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Ticket SName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Ticket SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Ticket SName name string entries not as expected") assert.Equal(t, "krb5data", a.EText, "EText not as expected") assert.Equal(t, []byte("krb5data"), a.EData, "EData not as expected") } func TestUnmarshalKRBError_optionalsNULL(t *testing.T) { t.Parallel() var a KRBError b, err := hex.DecodeString(testdata.MarshaledKRB5errorOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO is not as expected") assert.Equal(t, msgtype.KRB_ERROR, a.MsgType, "Message type is not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.STime, "STime not as expected") assert.Equal(t, 123456, a.Susec, "Service microseconds not as expected") assert.Equal(t, errorcode.KRB_ERR_GENERIC, a.ErrorCode, "Error code not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Ticket SName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Ticket SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Ticket SName name string entries not as expected") } gokrb5-8.4.3/messages/KRBPriv.go000066400000000000000000000067241427031340300163420ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) // KRBPriv implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.7.1. type KRBPriv struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` EncPart types.EncryptedData `asn1:"explicit,tag:3"` DecryptedEncPart EncKrbPrivPart `asn1:"optional,omitempty"` // Not part of ASN1 bytes so marked as optional so unmarshalling works } // EncKrbPrivPart is the encrypted part of KRB_PRIV. type EncKrbPrivPart struct { UserData []byte `asn1:"explicit,tag:0"` Timestamp time.Time `asn1:"generalized,optional,explicit,tag:1"` Usec int `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` SAddress types.HostAddress `asn1:"explicit,tag:4"` RAddress types.HostAddress `asn1:"optional,explicit,tag:5"` } // NewKRBPriv returns a new KRBPriv type. func NewKRBPriv(part EncKrbPrivPart) KRBPriv { return KRBPriv{ PVNO: iana.PVNO, MsgType: msgtype.KRB_PRIV, DecryptedEncPart: part, } } // Unmarshal bytes b into the KRBPriv struct. func (k *KRBPriv) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBPriv)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_PRIV if k.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_PRIV. Expected: %v; Actual: %v", expectedMsgType, k.MsgType) } return nil } // Unmarshal bytes b into the EncKrbPrivPart struct. func (k *EncKrbPrivPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncKrbPrivPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "KRB_PRIV unmarshal error") } return nil } // Marshal the KRBPriv. func (k *KRBPriv) Marshal() ([]byte, error) { tk := KRBPriv{ PVNO: k.PVNO, MsgType: k.MsgType, EncPart: k.EncPart, } b, err := asn1.Marshal(tk) if err != nil { return []byte{}, err } b = asn1tools.AddASNAppTag(b, asnAppTag.KRBPriv) return b, nil } // EncryptEncPart encrypts the DecryptedEncPart within the KRBPriv. // Use to prepare for marshaling. func (k *KRBPriv) EncryptEncPart(key types.EncryptionKey) error { b, err := asn1.Marshal(k.DecryptedEncPart) if err != nil { return err } b = asn1tools.AddASNAppTag(b, asnAppTag.EncKrbPrivPart) k.EncPart, err = crypto.GetEncryptedData(b, key, keyusage.KRB_PRIV_ENCPART, 1) if err != nil { return err } return nil } // DecryptEncPart decrypts the encrypted part of the KRBPriv message. func (k *KRBPriv) DecryptEncPart(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.KRB_PRIV_ENCPART) if err != nil { return fmt.Errorf("error decrypting KRBPriv EncPart: %v", err) } err = k.DecryptedEncPart.Unmarshal(b) if err != nil { return fmt.Errorf("error unmarshaling encrypted part: %v", err) } return nil } gokrb5-8.4.3/messages/KRBPriv_test.go000066400000000000000000000102351427031340300173710ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) func TestUnmarshalKRBPriv(t *testing.T) { t.Parallel() var a KRBPriv b, err := hex.DecodeString(testdata.MarshaledKRB5priv) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_PRIV, a.MsgType, "Message type not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "EncPart KVNO not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "EncPart etype not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "Cipher text of EncPart not as expected") } func TestUnmarshalEncPrivPart(t *testing.T) { t.Parallel() var a EncKrbPrivPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "krb5data", string(a.UserData), "User data not as expected") assert.Equal(t, tt, a.Timestamp, "Timestamp not as expected") assert.Equal(t, 123456, a.Usec, "Microseconds not as expected") assert.Equal(t, int64(17), a.SequenceNumber, "Sequence number not as expected") assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SAddress.Address), "Address not as expected for SAddress") assert.Equal(t, addrtype.IPv4, a.RAddress.AddrType, "RAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.RAddress.Address), "Address not as expected for RAddress") } func TestUnmarshalEncPrivPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncKrbPrivPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, "krb5data", string(a.UserData), "User data not as expected") assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SAddress.Address), "Address not as expected for SAddress") } func TestMarshalKRBPriv(t *testing.T) { t.Parallel() var a KRBPriv b, err := hex.DecodeString(testdata.MarshaledKRB5priv) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("error marshaling KRBPriv: %v", err) } assert.Equal(t, b, mb, "marshaled bytes not as expected") be, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.DecryptedEncPart.Unmarshal(be) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err = a.Marshal() if err != nil { t.Fatalf("error marshaling KRBPriv: %v", err) } assert.Equal(t, b, mb, "marshaled bytes not as expected when it has decrypted encpart") } func TestKRBPriv_EncryptEncPart(t *testing.T) { t.Parallel() var a KRBPriv b, err := hex.DecodeString(testdata.MarshaledKRB5priv) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } b, err = hex.DecodeString(testdata.MarshaledKRB5enc_priv_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.DecryptedEncPart.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } key := types.EncryptionKey{ KeyType: int32(18), KeyValue: []byte("12345678901234567890123456789012"), } err = a.EncryptEncPart(key) if err != nil { t.Fatalf("error encrypting encpart: %v", err) } } gokrb5-8.4.3/messages/KRBSafe.go000066400000000000000000000030221427031340300162640ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/types" ) // KRBSafe implements RFC 4120 KRB_SAFE: https://tools.ietf.org/html/rfc4120#section-5.6.1. type KRBSafe struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` SafeBody KRBSafeBody `asn1:"explicit,tag:2"` Cksum types.Checksum `asn1:"explicit,tag:3"` } // KRBSafeBody implements the KRB_SAFE_BODY of KRB_SAFE. type KRBSafeBody struct { UserData []byte `asn1:"explicit,tag:0"` Timestamp time.Time `asn1:"generalized,optional,explicit,tag:1"` Usec int `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` SAddress types.HostAddress `asn1:"explicit,tag:4"` RAddress types.HostAddress `asn1:"optional,explicit,tag:5"` } // Unmarshal bytes b into the KRBSafe struct. func (s *KRBSafe) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, s, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBSafe)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_SAFE if s.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_SAFE. Expected: %v; Actual: %v", expectedMsgType, s.MsgType) } return nil } gokrb5-8.4.3/messages/KRBSafe_test.go000066400000000000000000000051041427031340300173260ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalKRBSafe(t *testing.T) { t.Parallel() var a KRBSafe b, err := hex.DecodeString(testdata.MarshaledKRB5safe) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_SAFE, a.MsgType, "MsgType is not as expected") assert.Equal(t, []byte("krb5data"), a.SafeBody.UserData, "Safe body userdata not as expected") assert.Equal(t, tt, a.SafeBody.Timestamp, "Safe body timestamp not as expected") assert.Equal(t, 123456, a.SafeBody.Usec, "Safe body microseconds not as expected") assert.Equal(t, int64(17), a.SafeBody.SequenceNumber, "Safe body sequence number not as expected") assert.Equal(t, addrtype.IPv4, a.SafeBody.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SafeBody.SAddress.Address), "SAddress not as expected") assert.Equal(t, addrtype.IPv4, a.SafeBody.RAddress.AddrType, "RAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SafeBody.RAddress.Address), "RAddress not as expected") assert.Equal(t, int32(1), a.Cksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.Cksum.Checksum, "Checksum not as expected") } func TestUnmarshalKRBSafe_optionalsNULL(t *testing.T) { t.Parallel() var a KRBSafe b, err := hex.DecodeString(testdata.MarshaledKRB5safeOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_SAFE, a.MsgType, "MsgType is not as expected") assert.Equal(t, []byte("krb5data"), a.SafeBody.UserData, "Safe body userdata not as expected") assert.Equal(t, addrtype.IPv4, a.SafeBody.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SafeBody.SAddress.Address), "SAddress not as expected") assert.Equal(t, int32(1), a.Cksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.Cksum.Checksum, "Checksum not as expected") } gokrb5-8.4.3/messages/Ticket.go000066400000000000000000000243371427031340300163060ustar00rootroot00000000000000package messages import ( "crypto/rand" "fmt" "log" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/adtype" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode" "gopkg.in/jcmturner/gokrb5.v7/iana/flags" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/pac" "gopkg.in/jcmturner/gokrb5.v7/types" ) // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.3 // Ticket implements the Kerberos ticket. type Ticket struct { TktVNO int `asn1:"explicit,tag:0"` Realm string `asn1:"generalstring,explicit,tag:1"` SName types.PrincipalName `asn1:"explicit,tag:2"` EncPart types.EncryptedData `asn1:"explicit,tag:3"` DecryptedEncPart EncTicketPart `asn1:"optional"` // Not part of ASN1 bytes so marked as optional so unmarshalling works } // EncTicketPart is the encrypted part of the Ticket. type EncTicketPart struct { Flags asn1.BitString `asn1:"explicit,tag:0"` Key types.EncryptionKey `asn1:"explicit,tag:1"` CRealm string `asn1:"generalstring,explicit,tag:2"` CName types.PrincipalName `asn1:"explicit,tag:3"` Transited TransitedEncoding `asn1:"explicit,tag:4"` AuthTime time.Time `asn1:"generalized,explicit,tag:5"` StartTime time.Time `asn1:"generalized,explicit,optional,tag:6"` EndTime time.Time `asn1:"generalized,explicit,tag:7"` RenewTill time.Time `asn1:"generalized,explicit,optional,tag:8"` CAddr types.HostAddresses `asn1:"explicit,optional,tag:9"` AuthorizationData types.AuthorizationData `asn1:"explicit,optional,tag:10"` } // TransitedEncoding part of the ticket's encrypted part. type TransitedEncoding struct { TRType int32 `asn1:"explicit,tag:0"` Contents []byte `asn1:"explicit,tag:1"` } // NewTicket creates a new Ticket instance. func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalName, srealm string, flags asn1.BitString, sktab *keytab.Keytab, eTypeID int32, kvno int, authTime, startTime, endTime, renewTill time.Time) (Ticket, types.EncryptionKey, error) { etype, err := crypto.GetEtype(eTypeID) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting etype for new ticket") } ks := etype.GetKeyByteSize() kv := make([]byte, ks, ks) rand.Read(kv) sessionKey := types.EncryptionKey{ KeyType: eTypeID, KeyValue: kv, } etp := EncTicketPart{ Flags: flags, Key: sessionKey, CRealm: crealm, CName: cname, Transited: TransitedEncoding{}, AuthTime: authTime, StartTime: startTime, EndTime: endTime, RenewTill: renewTill, } b, err := asn1.Marshal(etp) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncodingError, "error marshalling ticket encpart") } b = asn1tools.AddASNAppTag(b, asnAppTag.EncTicketPart) skey, err := sktab.GetEncryptionKey(sname, srealm, kvno, eTypeID) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting encryption key for new ticket") } ed, err := crypto.GetEncryptedData(b, skey, keyusage.KDC_REP_TICKET, kvno) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error encrypting ticket encpart") } tkt := Ticket{ TktVNO: iana.PVNO, Realm: srealm, SName: sname, EncPart: ed, } return tkt, sessionKey, nil } // Unmarshal bytes b into a Ticket struct. func (t *Ticket) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.Ticket)) return err } // Marshal the Ticket. func (t *Ticket) Marshal() ([]byte, error) { b, err := asn1.Marshal(*t) if err != nil { return nil, err } b = asn1tools.AddASNAppTag(b, asnAppTag.Ticket) return b, nil } // Unmarshal bytes b into the EncTicketPart struct. func (t *EncTicketPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.EncTicketPart)) return err } // unmarshalTicket returns a ticket from the bytes provided. func unmarshalTicket(b []byte) (t Ticket, err error) { err = t.Unmarshal(b) return } // UnmarshalTicketsSequence returns a slice of Tickets from a raw ASN1 value. func unmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) { //This is a workaround to a asn1 decoding issue in golang - https://github.com/golang/go/issues/17321. It's not pretty I'm afraid //We pull out raw values from the larger raw value (that is actually the data of the sequence of raw values) and track our position moving along the data. b := in.Bytes // Ignore the head of the asn1 stream (1 byte for tag and those for the length) as this is what tells us its a sequence but we're handling it ourselves p := 1 + asn1tools.GetNumberBytesInLengthHeader(in.Bytes) var tkts []Ticket var raw asn1.RawValue for p < (len(b)) { _, err := asn1.UnmarshalWithParams(b[p:], &raw, fmt.Sprintf("application,tag:%d", asnAppTag.Ticket)) if err != nil { return nil, fmt.Errorf("unmarshaling sequence of tickets failed getting length of ticket: %v", err) } t, err := unmarshalTicket(b[p:]) if err != nil { return nil, fmt.Errorf("unmarshaling sequence of tickets failed: %v", err) } p += len(raw.FullBytes) tkts = append(tkts, t) } MarshalTicketSequence(tkts) return tkts, nil } // MarshalTicketSequence marshals a slice of Tickets returning an ASN1 raw value containing the ticket sequence. func MarshalTicketSequence(tkts []Ticket) (asn1.RawValue, error) { raw := asn1.RawValue{ Class: 2, IsCompound: true, } if len(tkts) < 1 { // There are no tickets to marshal return raw, nil } var btkts []byte for i, t := range tkts { b, err := t.Marshal() if err != nil { return raw, fmt.Errorf("error marshaling ticket number %d in sequence of tickets", i+1) } btkts = append(btkts, b...) } // The ASN1 wrapping consists of 2 bytes: // 1st byte -> Identifier Octet - In this case an OCTET STRING (ASN TAG // 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here. // Application Tag: //| Byte: | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | //| Value: | 0 | 1 | 1 | From the RFC spec 4120 | //| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value | btkts = append(asn1tools.MarshalLengthBytes(len(btkts)), btkts...) btkts = append([]byte{byte(32 + asn1.TagSequence)}, btkts...) raw.Bytes = btkts // If we need to create the full bytes then identifier octet is "context-specific" = 128 + "constructed" + 32 + the wrapping explicit tag (11) //fmt.Fprintf(os.Stderr, "mRaw fb: %v\n", raw.FullBytes) return raw, nil } // DecryptEncPart decrypts the encrypted part of the ticket. // The sname argument can be used to specify which service principal's key should be used to decrypt the ticket. // If nil is passed as the sname then the service principal specified within the ticket it used. func (t *Ticket) DecryptEncPart(keytab *keytab.Keytab, sname *types.PrincipalName) error { if sname == nil { sname = &t.SName } key, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType) if err != nil { return NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err)) } return t.Decrypt(key) } // Decrypt decrypts the encrypted part of the ticket using the key provided. func (t *Ticket) Decrypt(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(t.EncPart, key, keyusage.KDC_REP_TICKET) if err != nil { return fmt.Errorf("error decrypting Ticket EncPart: %v", err) } var denc EncTicketPart err = denc.Unmarshal(b) if err != nil { return fmt.Errorf("error unmarshaling encrypted part: %v", err) } t.DecryptedEncPart = denc return nil } // GetPACType returns a Microsoft PAC that has been extracted from the ticket and processed. func (t *Ticket) GetPACType(keytab *keytab.Keytab, sname *types.PrincipalName, l *log.Logger) (bool, pac.PACType, error) { var isPAC bool for _, ad := range t.DecryptedEncPart.AuthorizationData { if ad.ADType == adtype.ADIfRelevant { var ad2 types.AuthorizationData err := ad2.Unmarshal(ad.ADData) if err != nil { l.Printf("PAC authorization data could not be unmarshaled: %v", err) continue } if ad2[0].ADType == adtype.ADWin2KPAC { isPAC = true var p pac.PACType err = p.Unmarshal(ad2[0].ADData) if err != nil { return isPAC, p, fmt.Errorf("error unmarshaling PAC: %v", err) } if sname == nil { sname = &t.SName } key, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType) if err != nil { return isPAC, p, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err)) } err = p.ProcessPACInfoBuffers(key, l) return isPAC, p, err } } } return isPAC, pac.PACType{}, nil } // Valid checks it the ticket is currently valid. Max duration passed endtime passed in as argument. func (t *Ticket) Valid(d time.Duration) (bool, error) { // Check for future tickets or invalid tickets time := time.Now().UTC() if t.DecryptedEncPart.StartTime.Sub(time) > d || types.IsFlagSet(&t.DecryptedEncPart.Flags, flags.Invalid) { return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "service ticket provided is not yet valid") } // Check for expired ticket if time.Sub(t.DecryptedEncPart.EndTime) > d { return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "service ticket provided has expired") } return true, nil } gokrb5-8.4.3/messages/Ticket_test.go000066400000000000000000000154141427031340300173410ustar00rootroot00000000000000package messages import ( "bytes" "encoding/hex" "fmt" "log" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" "gopkg.in/jcmturner/gokrb5.v7/iana/adtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/iana/trtype" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) func TestUnmarshalTicket(t *testing.T) { t.Parallel() var a Ticket b, err := hex.DecodeString(testdata.MarshaledKRB5ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.TktVNO, "Ticket version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "SName name strings not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of Ticket EncPart not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "KNVO of Ticket EncPart not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "Cipher of Ticket EncPart not as expected") } func TestUnmarshalEncTicketPart(t *testing.T) { t.Parallel() var a EncTicketPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_tkt_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName string entries not as expected") assert.Equal(t, trtype.DOMAIN_X500_COMPRESS, a.Transited.TRType, "Transisted type not as expected") assert.Equal(t, []byte("EDU,MIT.,ATHENA.,WASHINGTON.EDU,CS."), a.Transited.Contents, "Transisted content not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.StartTime, "Start time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") assert.Equal(t, tt, a.RenewTill, "Renew Till time not as expected") assert.Equal(t, 2, len(a.CAddr), "Number of client addresses not as expected") for i, addr := range a.CAddr { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } for i, ele := range a.AuthorizationData { assert.Equal(t, adtype.ADIfRelevant, ele.ADType, fmt.Sprintf("Authorization data type of element %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_AUTHORIZATION_DATA_VALUE), ele.ADData, fmt.Sprintf("Authorization data of element %d not as expected", i+1)) } } func TestUnmarshalEncTicketPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncTicketPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_tkt_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName string entries not as expected") assert.Equal(t, trtype.DOMAIN_X500_COMPRESS, a.Transited.TRType, "Transisted type not as expected") assert.Equal(t, []byte("EDU,MIT.,ATHENA.,WASHINGTON.EDU,CS."), a.Transited.Contents, "Transisted content not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") } func TestMarshalTicket(t *testing.T) { t.Parallel() var a Ticket b, err := hex.DecodeString(testdata.MarshaledKRB5ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshalled bytes not as expected") } func TestAuthorizationData_GetPACType_GOKRB5TestData(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_AuthorizationData_GOKRB5) if err != nil { t.Fatalf("Test vector read error: %v", err) } var a types.AuthorizationData err = a.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } tkt := Ticket{ Realm: "TEST.GOKRB5", EncPart: types.EncryptedData{ EType: 18, KVNO: 2, }, DecryptedEncPart: EncTicketPart{ AuthorizationData: a, }, } b, _ = hex.DecodeString(testdata.SYSHTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}} w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(kt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "PAC should be present") assert.Equal(t, 5, len(pac.Buffers), "Number of buffers not as expected") assert.Equal(t, uint32(5), pac.CBuffers, "Count of buffers not as expected") assert.Equal(t, uint32(0), pac.Version, "PAC version not as expected") assert.NotNil(t, pac.KerbValidationInfo, "PAC Kerb Validation info is nil") assert.NotNil(t, pac.ClientInfo, "PAC Client Info info is nil") assert.NotNil(t, pac.UPNDNSInfo, "PAC UPN DNS Info info is nil") assert.NotNil(t, pac.KDCChecksum, "PAC KDC Checksum info is nil") assert.NotNil(t, pac.ServerChecksum, "PAC Server checksum info is nil") } gokrb5-8.4.3/pac/000077500000000000000000000000001427031340300134575ustar00rootroot00000000000000gokrb5-8.4.3/pac/client_claims.go000066400000000000000000000016101427031340300166120ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) // Claims reference: https://msdn.microsoft.com/en-us/library/hh553895.aspx // ClientClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh536365.aspx type ClientClaimsInfo struct { ClaimsSetMetadata mstypes.ClaimsSetMetadata ClaimsSet mstypes.ClaimsSet } // Unmarshal bytes into the ClientClaimsInfo struct func (k *ClientClaimsInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) m := new(mstypes.ClaimsSetMetadata) err = dec.Decode(m) if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSetMetadata: %v", err) return } k.ClaimsSetMetadata = *m k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet() if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err) } return } gokrb5-8.4.3/pac/client_claims_test.go000066400000000000000000000200741427031340300176560ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/rpc.v1/mstypes" ) const ( ClaimsEntryIDStr = "ad://ext/sAMAccountName:88d5d9085ea5c0c0" ClaimsEntryValueStr = "testuser1" ClaimsEntryIDInt64 = "ad://ext/msDS-SupportedE:88d5dea8f1af5f19" ClaimsEntryValueInt64 int64 = 28 ClaimsEntryIDUInt64 = "ad://ext/objectClass:88d5de791e7b27e6" ) func TestPAC_ClientClaimsInfoStr_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoStr) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, uint16(3), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDStr, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []mstypes.LPWSTR{{Value: ClaimsEntryValueStr}}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMultiUint) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, mstypes.ClaimTypeIDUInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(4), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDUInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []uint64{655369, 65543, 65542, 65536}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsInt_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoInt) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, mstypes.ClaimTypeIDInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []int64{ClaimsEntryValueInt64}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMultiStr) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, mstypes.ClaimTypeIDString, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(4), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected") assert.Equal(t, "ad://ext/otherIpPhone:88d5de9f6b4af985", k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []mstypes.LPWSTR{{Value: "str1"}, {Value: "str2"}, {Value: "str3"}, {Value: "str4"}}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) { // Has an int and a str claim type t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMulti) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(2), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, uint16(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []int64{int64(28)}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected") assert.Equal(t, uint16(3), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].TypeString.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDStr, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].ID, "claims entry ID not as expected") assert.Equal(t, []mstypes.LPWSTR{{Value: ClaimsEntryValueStr}}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].TypeString.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } // Compressed claims not yet supported. //func TestPAC_ClientClaimsInfo_Unmarshal_UnsupportedCompression(t *testing.T) { // t.Parallel() // b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfo_XPRESS_HUFF) // if err != nil { // t.Fatal("Could not decode test data hex string") // } // var k ClientClaimsInfo // err = k.Unmarshal(b) // if err != nil { // t.Fatalf("Error unmarshaling test data: %v", err) // } // assert.Equal(t, mstypes.CompressionFormatXPressHuff, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") //} gokrb5-8.4.3/pac/client_info.go000066400000000000000000000017731427031340300163070ustar00rootroot00000000000000package pac import ( "bytes" "gopkg.in/jcmturner/rpc.v1/mstypes" ) // ClientInfo implements https://msdn.microsoft.com/en-us/library/cc237951.aspx type ClientInfo struct { ClientID mstypes.FileTime // A FILETIME structure in little-endian format that contains the Kerberos initial ticket-granting ticket TGT authentication time NameLength uint16 // An unsigned 16-bit integer in little-endian format that specifies the length, in bytes, of the Name field. Name string // An array of 16-bit Unicode characters in little-endian format that contains the client's account name. } // Unmarshal bytes into the ClientInfo struct func (k *ClientInfo) Unmarshal(b []byte) (err error) { //The PAC_CLIENT_INFO structure is a simple structure that is not NDR-encoded. r := mstypes.NewReader(bytes.NewReader(b)) k.ClientID, err = r.FileTime() if err != nil { return } k.NameLength, err = r.Uint16() if err != nil { return } k.Name, err = r.UTF16String(int(k.NameLength)) return } gokrb5-8.4.3/pac/client_info_test.go000066400000000000000000000013361427031340300173410ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestPAC_ClientInfo_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_Client_Info) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, time.Date(2017, 5, 6, 15, 53, 11, 000000000, time.UTC), k.ClientID.Time(), "Client ID time not as expected.") assert.Equal(t, uint16(18), k.NameLength, "Client name length not as expected") assert.Equal(t, "testuser1", k.Name, "Client name not as expected") } gokrb5-8.4.3/pac/credentials_info.go000066400000000000000000000047151427031340300173250ustar00rootroot00000000000000package pac import ( "bytes" "errors" "fmt" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/types" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) // https://msdn.microsoft.com/en-us/library/cc237931.aspx // CredentialsInfo implements https://msdn.microsoft.com/en-us/library/cc237953.aspx type CredentialsInfo struct { Version uint32 // A 32-bit unsigned integer in little-endian format that defines the version. MUST be 0x00000000. EType uint32 PACCredentialDataEncrypted []byte // Key usage number for encryption: KERB_NON_KERB_SALT (16) PACCredentialData CredentialData } // Unmarshal bytes into the CredentialsInfo struct func (c *CredentialsInfo) Unmarshal(b []byte, k types.EncryptionKey) (err error) { //The CredentialsInfo structure is a simple structure that is not NDR-encoded. r := mstypes.NewReader(bytes.NewReader(b)) c.Version, err = r.Uint32() if err != nil { return } if c.Version != 0 { err = errors.New("credentials info version is not zero") return } c.EType, err = r.Uint32() if err != nil { return } c.PACCredentialDataEncrypted, err = r.ReadBytes(len(b) - 8) if err != nil { err = fmt.Errorf("error reading credentials info: %v", err) return } err = c.DecryptEncPart(k) if err != nil { err = fmt.Errorf("error decrypting PAC Credentials Data: %v", err) return } return } // DecryptEncPart decrypts the encrypted part of the CredentialsInfo. func (c *CredentialsInfo) DecryptEncPart(k types.EncryptionKey) error { if k.KeyType != int32(c.EType) { return fmt.Errorf("key provided is not the correct type. Type needed: %d, type provided: %d", c.EType, k.KeyType) } pt, err := crypto.DecryptMessage(c.PACCredentialDataEncrypted, k, keyusage.KERB_NON_KERB_SALT) if err != nil { return err } err = c.PACCredentialData.Unmarshal(pt) if err != nil { return err } return nil } // CredentialData implements https://msdn.microsoft.com/en-us/library/cc237952.aspx type CredentialData struct { CredentialCount uint32 Credentials []SECPKGSupplementalCred // Size is the value of CredentialCount } // Unmarshal converts the bytes provided into a CredentialData type. func (c *CredentialData) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(c) if err != nil { err = fmt.Errorf("error unmarshaling KerbValidationInfo: %v", err) } return } gokrb5-8.4.3/pac/device_claims.go000066400000000000000000000016101427031340300165730ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) // Claims reference: https://msdn.microsoft.com/en-us/library/hh553895.aspx // DeviceClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh554226.aspx type DeviceClaimsInfo struct { ClaimsSetMetadata mstypes.ClaimsSetMetadata ClaimsSet mstypes.ClaimsSet } // Unmarshal bytes into the ClientClaimsInfo struct func (k *DeviceClaimsInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) m := new(mstypes.ClaimsSetMetadata) err = dec.Decode(m) if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSetMetadata: %v", err) return } k.ClaimsSetMetadata = *m k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet() if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err) } return } gokrb5-8.4.3/pac/device_info.go000066400000000000000000000046551427031340300162720ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) // DeviceInfo implements https://msdn.microsoft.com/en-us/library/hh536402.aspx type DeviceInfo struct { UserID uint32 // A 32-bit unsigned integer that contains the RID of the account. If the UserId member equals 0x00000000, the first group SID in this member is the SID for this account. PrimaryGroupID uint32 // A 32-bit unsigned integer that contains the RID for the primary group to which this account belongs. AccountDomainID mstypes.RPCSID `ndr:"pointer"` // A SID structure that contains the SID for the domain of the account.This member is used in conjunction with the UserId, and GroupIds members to create the user and group SIDs for the client. AccountGroupCount uint32 // A 32-bit unsigned integer that contains the number of groups within the account domain to which the account belongs AccountGroupIDs []mstypes.GroupMembership `ndr:"pointer,conformant"` // A pointer to a list of GROUP_MEMBERSHIP (section 2.2.2) structures that contains the groups to which the account belongs in the account domain. The number of groups in this list MUST be equal to GroupCount. SIDCount uint32 // A 32-bit unsigned integer that contains the total number of SIDs present in the ExtraSids member. ExtraSIDs []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"` // A pointer to a list of KERB_SID_AND_ATTRIBUTES structures that contain a list of SIDs corresponding to groups not in domains. If the UserId member equals 0x00000000, the first group SID in this member is the SID for this account. DomainGroupCount uint32 // A 32-bit unsigned integer that contains the number of domains with groups to which the account belongs. DomainGroup []mstypes.DomainGroupMembership `ndr:"pointer,conformant"` // A pointer to a list of DOMAIN_GROUP_MEMBERSHIP structures (section 2.2.3) that contains the domains to which the account belongs to a group. The number of sets in this list MUST be equal to DomainCount. } // Unmarshal bytes into the DeviceInfo struct func (k *DeviceInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(k) if err != nil { err = fmt.Errorf("error unmarshaling DeviceInfo: %v", err) } return } gokrb5-8.4.3/pac/kerb_validation_info.go000066400000000000000000000111401427031340300201530ustar00rootroot00000000000000// Package pac implements Microsoft Privilege Attribute Certificate (PAC) processing. package pac import ( "bytes" "fmt" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) // KERB_VALIDATION_INFO flags. const ( USERFLAG_GUEST = 31 // Authentication was done via the GUEST account; no password was used. USERFLAG_NO_ENCRYPTION_AVAILABLE = 30 // No encryption is available. USERFLAG_LAN_MANAGER_KEY = 28 // LAN Manager key was used for authentication. USERFLAG_SUB_AUTH = 25 // Sub-authentication used; session key came from the sub-authentication package. USERFLAG_EXTRA_SIDS = 26 // Indicates that the ExtraSids field is populated and contains additional SIDs. USERFLAG_MACHINE_ACCOUNT = 24 // Indicates that the account is a machine account. USERFLAG_DC_NTLM2 = 23 // Indicates that the domain controller understands NTLMv2. USERFLAG_RESOURCE_GROUPIDS = 22 // Indicates that the ResourceGroupIds field is populated. USERFLAG_PROFILEPATH = 21 // Indicates that ProfilePath is populated. USERFLAG_NTLM2_NTCHALLENGERESP = 20 // The NTLMv2 response from the NtChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and session key generation. USERFLAG_LM2_LMCHALLENGERESP = 19 // The LMv2 response from the LmChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and session key generation. USERFLAG_AUTH_LMCHALLENGERESP_KEY_NTCHALLENGERESP = 18 // The LMv2 response from the LmChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and the NTLMv2 response from the NtChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used session key generation. ) // KerbValidationInfo implement https://msdn.microsoft.com/en-us/library/cc237948.aspx type KerbValidationInfo struct { LogOnTime mstypes.FileTime LogOffTime mstypes.FileTime KickOffTime mstypes.FileTime PasswordLastSet mstypes.FileTime PasswordCanChange mstypes.FileTime PasswordMustChange mstypes.FileTime EffectiveName mstypes.RPCUnicodeString FullName mstypes.RPCUnicodeString LogonScript mstypes.RPCUnicodeString ProfilePath mstypes.RPCUnicodeString HomeDirectory mstypes.RPCUnicodeString HomeDirectoryDrive mstypes.RPCUnicodeString LogonCount uint16 BadPasswordCount uint16 UserID uint32 PrimaryGroupID uint32 GroupCount uint32 GroupIDs []mstypes.GroupMembership `ndr:"pointer,conformant"` UserFlags uint32 UserSessionKey mstypes.UserSessionKey LogonServer mstypes.RPCUnicodeString LogonDomainName mstypes.RPCUnicodeString LogonDomainID mstypes.RPCSID `ndr:"pointer"` Reserved1 [2]uint32 // Has 2 elements UserAccountControl uint32 SubAuthStatus uint32 LastSuccessfulILogon mstypes.FileTime LastFailedILogon mstypes.FileTime FailedILogonCount uint32 Reserved3 uint32 SIDCount uint32 ExtraSIDs []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"` ResourceGroupDomainSID mstypes.RPCSID `ndr:"pointer"` ResourceGroupCount uint32 ResourceGroupIDs []mstypes.GroupMembership `ndr:"pointer,conformant"` } // Unmarshal bytes into the DeviceInfo struct func (k *KerbValidationInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(k) if err != nil { err = fmt.Errorf("error unmarshaling KerbValidationInfo: %v", err) } return } // GetGroupMembershipSIDs returns a slice of strings containing the group membership SIDs found in the PAC. func (k *KerbValidationInfo) GetGroupMembershipSIDs() []string { var g []string lSID := k.LogonDomainID.String() for i := range k.GroupIDs { g = append(g, fmt.Sprintf("%s-%d", lSID, k.GroupIDs[i].RelativeID)) } for _, s := range k.ExtraSIDs { var exists = false for _, es := range g { if es == s.SID.String() { exists = true break } } if !exists { g = append(g, s.SID.String()) } } for _, r := range k.ResourceGroupIDs { var exists = false s := fmt.Sprintf("%s-%d", k.ResourceGroupDomainSID.String(), r.RelativeID) for _, es := range g { if es == s { exists = true break } } if !exists { g = append(g, s) } } return g } gokrb5-8.4.3/pac/kerb_validation_info_test.go000066400000000000000000000337641427031340300212320ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/rpc.v1/mstypes" ) func TestKerbValidationInfo_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info_MS) if err != nil { t.Fatal("Could not decode test data hex string") } var k KerbValidationInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling KerbValidationInfo: %v", err) } assert.Equal(t, time.Date(2006, 4, 28, 1, 42, 50, 925640100, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected") assert.Equal(t, time.Date(2006, 3, 18, 10, 44, 54, 837147900, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected") assert.Equal(t, time.Date(2006, 3, 19, 10, 44, 54, 837147900, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected") assert.Equal(t, "lzhu", k.EffectiveName.Value, "EffectiveName not as expected") assert.Equal(t, "Liqiang(Larry) Zhu", k.FullName.String(), "EffectiveName not as expected") assert.Equal(t, "ntds2.bat", k.LogonScript.String(), "EffectiveName not as expected") assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected") assert.Equal(t, uint16(4180), k.LogonCount, "LogonCount not as expected") assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected") assert.Equal(t, uint32(2914711), k.UserID, "UserID not as expected") assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected") assert.Equal(t, uint32(26), k.GroupCount, "GroupCount not as expected") gids := []mstypes.GroupMembership{ {RelativeID: 3392609, Attributes: 7}, {RelativeID: 2999049, Attributes: 7}, {RelativeID: 3322974, Attributes: 7}, {RelativeID: 513, Attributes: 7}, {RelativeID: 2931095, Attributes: 7}, {RelativeID: 3338539, Attributes: 7}, {RelativeID: 3354830, Attributes: 7}, {RelativeID: 3026599, Attributes: 7}, {RelativeID: 3338538, Attributes: 7}, {RelativeID: 2931096, Attributes: 7}, {RelativeID: 3392610, Attributes: 7}, {RelativeID: 3342740, Attributes: 7}, {RelativeID: 3392630, Attributes: 7}, {RelativeID: 3014318, Attributes: 7}, {RelativeID: 2937394, Attributes: 7}, {RelativeID: 3278870, Attributes: 7}, {RelativeID: 3038018, Attributes: 7}, {RelativeID: 3322975, Attributes: 7}, {RelativeID: 3513546, Attributes: 7}, {RelativeID: 2966661, Attributes: 7}, {RelativeID: 3338434, Attributes: 7}, {RelativeID: 3271401, Attributes: 7}, {RelativeID: 3051245, Attributes: 7}, {RelativeID: 3271606, Attributes: 7}, {RelativeID: 3026603, Attributes: 7}, {RelativeID: 3018354, Attributes: 7}, } assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected") assert.Equal(t, uint32(32), k.UserFlags, "UserFlags not as expected") assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected") assert.Equal(t, "NTDEV-DC-05", k.LogonServer.Value, "LogonServer not as expected") assert.Equal(t, "NTDEV", k.LogonDomainName.Value, "LogonDomainName not as expected") assert.Equal(t, "S-1-5-21-397955417-626881126-188441444", k.LogonDomainID.String(), "LogonDomainID not as expected") assert.Equal(t, uint32(16), k.UserAccountControl, "UserAccountControl not as expected") assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected") assert.Equal(t, uint32(13), k.SIDCount, "SIDCount not as expected") assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same") var es = []struct { sid string attr uint32 }{ {"S-1-5-21-773533881-1816936887-355810188-513", uint32(7)}, {"S-1-5-21-397955417-626881126-188441444-3101812", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3291368", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3291341", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3322973", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3479105", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3271400", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3283393", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3338537", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3038991", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3037999", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3248111", uint32(536870919)}, } for i, s := range es { assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected") assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected") } assert.Equal(t, uint8(0), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected") assert.Equal(t, 0, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected") b, err = hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info) if err != nil { t.Fatal("Could not decode test data hex string") } var k2 KerbValidationInfo err = k2.Unmarshal(b) if err != nil { t.Fatal("Could not unmarshal KerbValidationInfo") } assert.Equal(t, time.Date(2017, 5, 6, 15, 53, 11, 825766900, time.UTC), k2.LogOnTime.Time(), "LogOnTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.LogOffTime.Time(), "LogOffTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.KickOffTime.Time(), "KickOffTime not as expected") assert.Equal(t, time.Date(2017, 5, 6, 7, 23, 8, 968750000, time.UTC), k2.PasswordLastSet.Time(), "PasswordLastSet not as expected") assert.Equal(t, time.Date(2017, 5, 7, 7, 23, 8, 968750000, time.UTC), k2.PasswordCanChange.Time(), "PasswordCanChange not as expected") assert.Equal(t, "testuser1", k2.EffectiveName.String(), "EffectiveName not as expected") assert.Equal(t, "Test1 User1", k2.FullName.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.LogonScript.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.ProfilePath.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.HomeDirectory.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.HomeDirectoryDrive.String(), "EffectiveName not as expected") assert.Equal(t, uint16(216), k2.LogonCount, "LogonCount not as expected") assert.Equal(t, uint16(0), k2.BadPasswordCount, "BadPasswordCount not as expected") assert.Equal(t, uint32(1105), k2.UserID, "UserID not as expected") assert.Equal(t, uint32(513), k2.PrimaryGroupID, "PrimaryGroupID not as expected") assert.Equal(t, uint32(5), k2.GroupCount, "GroupCount not as expected") gids = []mstypes.GroupMembership{ {RelativeID: 513, Attributes: 7}, {RelativeID: 1108, Attributes: 7}, {RelativeID: 1109, Attributes: 7}, {RelativeID: 1115, Attributes: 7}, {RelativeID: 1116, Attributes: 7}, } assert.Equal(t, gids, k2.GroupIDs, "GroupIDs not as expected") assert.Equal(t, uint32(32), k2.UserFlags, "UserFlags not as expected") assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k2.UserSessionKey, "UserSessionKey not as expected") assert.Equal(t, "ADDC", k2.LogonServer.Value, "LogonServer not as expected") assert.Equal(t, "TEST", k2.LogonDomainName.Value, "LogonDomainName not as expected") assert.Equal(t, "S-1-5-21-3167651404-3865080224-2280184895", k2.LogonDomainID.String(), "LogonDomainID not as expected") assert.Equal(t, uint32(528), k2.UserAccountControl, "UserAccountControl not as expected") assert.Equal(t, uint32(0), k2.SubAuthStatus, "SubAuthStatus not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, uint32(0), k2.FailedILogonCount, "FailedILogonCount not as expected") assert.Equal(t, uint32(2), k2.SIDCount, "SIDCount not as expected") assert.Equal(t, int(k2.SIDCount), len(k2.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same") var es2 = []struct { sid string attr uint32 }{ {"S-1-5-21-3167651404-3865080224-2280184895-1114", uint32(536870919)}, {"S-1-5-21-3167651404-3865080224-2280184895-1111", uint32(536870919)}, } for i, s := range es2 { assert.Equal(t, s.sid, k2.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected") assert.Equal(t, s.attr, k2.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected") } assert.Equal(t, uint8(0), k2.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected") assert.Equal(t, 0, len(k2.ResourceGroupIDs), "ResourceGroupIDs not as expected") } func TestKerbValidationInfo_Unmarshal_DomainTrust(t *testing.T) { b, err := hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info_Trust) if err != nil { t.Fatal("Could not decode test data hex string") } var k KerbValidationInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling KerbValidationInfo: %v", err) } assert.Equal(t, time.Date(2017, 10, 14, 12, 03, 41, 52409900, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected") assert.Equal(t, time.Date(2017, 10, 10, 20, 42, 56, 220282300, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected") assert.Equal(t, time.Date(2017, 10, 11, 20, 42, 56, 220282300, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected") assert.Equal(t, "testuser1", k.EffectiveName.String(), "EffectiveName not as expected") assert.Equal(t, "Test1 User1", k.FullName.String(), "EffectiveName not as expected") assert.Equal(t, "", k.LogonScript.String(), "EffectiveName not as expected") assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected") assert.Equal(t, uint16(46), k.LogonCount, "LogonCount not as expected") assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected") assert.Equal(t, uint32(1106), k.UserID, "UserID not as expected") assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected") assert.Equal(t, uint32(3), k.GroupCount, "GroupCount not as expected") gids := []mstypes.GroupMembership{ {RelativeID: 1110, Attributes: 7}, {RelativeID: 513, Attributes: 7}, {RelativeID: 1109, Attributes: 7}, } assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected") assert.Equal(t, uint32(544), k.UserFlags, "UserFlags not as expected") assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected") assert.Equal(t, "UDC", k.LogonServer.Value, "LogonServer not as expected") assert.Equal(t, "USER", k.LogonDomainName.Value, "LogonDomainName not as expected") assert.Equal(t, "S-1-5-21-2284869408-3503417140-1141177250", k.LogonDomainID.String(), "LogonDomainID not as expected") assert.Equal(t, uint32(528), k.UserAccountControl, "UserAccountControl not as expected") assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected") assert.Equal(t, uint32(1), k.SIDCount, "SIDCount not as expected") assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same") var es = []struct { sid string attr uint32 }{ {"S-1-18-1", uint32(7)}, } for i, s := range es { assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected") assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected") } assert.Equal(t, uint8(4), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected") assert.Equal(t, "S-1-5-21-3062750306-1230139592-1973306805", k.ResourceGroupDomainSID.String(), "ResourceGroupDomainSID value not as expected") assert.Equal(t, 2, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected") rgids := []mstypes.GroupMembership{ {RelativeID: 1107, Attributes: 536870919}, {RelativeID: 1108, Attributes: 536870919}, } assert.Equal(t, rgids, k.ResourceGroupIDs, "ResourceGroupIDs not as expected") groupSids := []string{"S-1-5-21-2284869408-3503417140-1141177250-1110", "S-1-5-21-2284869408-3503417140-1141177250-513", "S-1-5-21-2284869408-3503417140-1141177250-1109", "S-1-18-1", "S-1-5-21-3062750306-1230139592-1973306805-1107", "S-1-5-21-3062750306-1230139592-1973306805-1108"} assert.Equal(t, groupSids, k.GetGroupMembershipSIDs(), "GroupMembershipSIDs not as expected") } gokrb5-8.4.3/pac/pac_type.go000066400000000000000000000166731427031340300156270ustar00rootroot00000000000000package pac import ( "bytes" "errors" "fmt" "log" "gopkg.in/jcmturner/gokrb5.v7/crypto" "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" "gopkg.in/jcmturner/gokrb5.v7/types" "gopkg.in/jcmturner/rpc.v1/mstypes" ) const ( infoTypeKerbValidationInfo uint32 = 1 infoTypeCredentials uint32 = 2 infoTypePACServerSignatureData uint32 = 6 infoTypePACKDCSignatureData uint32 = 7 infoTypePACClientInfo uint32 = 10 infoTypeS4UDelegationInfo uint32 = 11 infoTypeUPNDNSInfo uint32 = 12 infoTypePACClientClaimsInfo uint32 = 13 infoTypePACDeviceInfo uint32 = 14 infoTypePACDeviceClaimsInfo uint32 = 15 ) // PACType implements: https://msdn.microsoft.com/en-us/library/cc237950.aspx type PACType struct { CBuffers uint32 Version uint32 Buffers []InfoBuffer Data []byte KerbValidationInfo *KerbValidationInfo CredentialsInfo *CredentialsInfo ServerChecksum *SignatureData KDCChecksum *SignatureData ClientInfo *ClientInfo S4UDelegationInfo *S4UDelegationInfo UPNDNSInfo *UPNDNSInfo ClientClaimsInfo *ClientClaimsInfo DeviceInfo *DeviceInfo DeviceClaimsInfo *DeviceClaimsInfo ZeroSigData []byte } // InfoBuffer implements the PAC Info Buffer: https://msdn.microsoft.com/en-us/library/cc237954.aspx type InfoBuffer struct { ULType uint32 // A 32-bit unsigned integer in little-endian format that describes the type of data present in the buffer contained at Offset. CBBufferSize uint32 // A 32-bit unsigned integer in little-endian format that contains the size, in bytes, of the buffer in the PAC located at Offset. Offset uint64 // A 64-bit unsigned integer in little-endian format that contains the offset to the beginning of the buffer, in bytes, from the beginning of the PACTYPE structure. The data offset MUST be a multiple of eight. The following sections specify the format of each type of element. } // Unmarshal bytes into the PACType struct func (pac *PACType) Unmarshal(b []byte) (err error) { pac.Data = b zb := make([]byte, len(b), len(b)) copy(zb, b) pac.ZeroSigData = zb r := mstypes.NewReader(bytes.NewReader(b)) pac.CBuffers, err = r.Uint32() if err != nil { return } pac.Version, err = r.Uint32() if err != nil { return } buf := make([]InfoBuffer, pac.CBuffers, pac.CBuffers) for i := range buf { buf[i].ULType, err = r.Uint32() if err != nil { return } buf[i].CBBufferSize, err = r.Uint32() if err != nil { return } buf[i].Offset, err = r.Uint64() if err != nil { return } } pac.Buffers = buf return nil } // ProcessPACInfoBuffers processes the PAC Info Buffers. // https://msdn.microsoft.com/en-us/library/cc237954.aspx func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey, l *log.Logger) error { for _, buf := range pac.Buffers { p := make([]byte, buf.CBBufferSize, buf.CBBufferSize) copy(p, pac.Data[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)]) switch buf.ULType { case infoTypeKerbValidationInfo: if pac.KerbValidationInfo != nil { //Must ignore subsequent buffers of this type continue } var k KerbValidationInfo err := k.Unmarshal(p) if err != nil { return fmt.Errorf("error processing KerbValidationInfo: %v", err) } pac.KerbValidationInfo = &k case infoTypeCredentials: // Currently PAC parsing is only useful on the service side in gokrb5 // The CredentialsInfo are only useful when gokrb5 has implemented RFC4556 and only applied on the client side. // Skipping CredentialsInfo - will be revisited under RFC4556 implementation. continue //if pac.CredentialsInfo != nil { // //Must ignore subsequent buffers of this type // continue //} //var k CredentialsInfo //err := k.Unmarshal(p, key) // The encryption key used is the AS reply key only available to the client. //if err != nil { // return fmt.Errorf("error processing CredentialsInfo: %v", err) //} //pac.CredentialsInfo = &k case infoTypePACServerSignatureData: if pac.ServerChecksum != nil { //Must ignore subsequent buffers of this type continue } var k SignatureData zb, err := k.Unmarshal(p) copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb) if err != nil { return fmt.Errorf("error processing ServerChecksum: %v", err) } pac.ServerChecksum = &k case infoTypePACKDCSignatureData: if pac.KDCChecksum != nil { //Must ignore subsequent buffers of this type continue } var k SignatureData zb, err := k.Unmarshal(p) copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb) if err != nil { return fmt.Errorf("error processing KDCChecksum: %v", err) } pac.KDCChecksum = &k case infoTypePACClientInfo: if pac.ClientInfo != nil { //Must ignore subsequent buffers of this type continue } var k ClientInfo err := k.Unmarshal(p) if err != nil { return fmt.Errorf("error processing ClientInfo: %v", err) } pac.ClientInfo = &k case infoTypeS4UDelegationInfo: if pac.S4UDelegationInfo != nil { //Must ignore subsequent buffers of this type continue } var k S4UDelegationInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process S4U_DelegationInfo: %v", err) continue } pac.S4UDelegationInfo = &k case infoTypeUPNDNSInfo: if pac.UPNDNSInfo != nil { //Must ignore subsequent buffers of this type continue } var k UPNDNSInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process UPN_DNSInfo: %v", err) continue } pac.UPNDNSInfo = &k case infoTypePACClientClaimsInfo: if pac.ClientClaimsInfo != nil || len(p) < 1 { //Must ignore subsequent buffers of this type continue } var k ClientClaimsInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process ClientClaimsInfo: %v", err) continue } pac.ClientClaimsInfo = &k case infoTypePACDeviceInfo: if pac.DeviceInfo != nil { //Must ignore subsequent buffers of this type continue } var k DeviceInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process DeviceInfo: %v", err) continue } pac.DeviceInfo = &k case infoTypePACDeviceClaimsInfo: if pac.DeviceClaimsInfo != nil { //Must ignore subsequent buffers of this type continue } var k DeviceClaimsInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process DeviceClaimsInfo: %v", err) continue } pac.DeviceClaimsInfo = &k } } if ok, err := pac.verify(key); !ok { return err } return nil } func (pac *PACType) verify(key types.EncryptionKey) (bool, error) { if pac.KerbValidationInfo == nil { return false, errors.New("PAC Info Buffers does not contain a KerbValidationInfo") } if pac.ServerChecksum == nil { return false, errors.New("PAC Info Buffers does not contain a ServerChecksum") } if pac.KDCChecksum == nil { return false, errors.New("PAC Info Buffers does not contain a KDCChecksum") } if pac.ClientInfo == nil { return false, errors.New("PAC Info Buffers does not contain a ClientInfo") } etype, err := crypto.GetChksumEtype(int32(pac.ServerChecksum.SignatureType)) if err != nil { return false, err } if ok := etype.VerifyChecksum(key.KeyValue, pac.ZeroSigData, pac.ServerChecksum.Signature, keyusage.KERB_NON_KERB_CKSUM_SALT); !ok { return false, errors.New("PAC service checksum verification failed") } return true, nil } gokrb5-8.4.3/pac/pac_type_test.go000066400000000000000000000032661427031340300166600ustar00rootroot00000000000000package pac import ( "bytes" "encoding/hex" "fmt" "log" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) func TestPACTypeVerify(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_AD_WIN2K_PAC) if err != nil { t.Fatalf("Test vector read error: %v", err) } var pac PACType err = pac.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } b, _ = hex.DecodeString(testdata.SYSHTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) pn, _ := types.ParseSPNString("sysHTTP") key, err := kt.GetEncryptionKey(pn, "TEST.GOKRB5", 2, 18) if err != nil { t.Fatalf("Error getting key: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) err = pac.ProcessPACInfoBuffers(key, l) if err != nil { t.Fatalf("Processing reference pac error: %v", err) } pacInvalidServerSig := pac // Check the signature to force failure pacInvalidServerSig.ServerChecksum.Signature[0] ^= 0xFF pacInvalidNilKerbValidationInfo := pac pacInvalidNilKerbValidationInfo.KerbValidationInfo = nil pacInvalidNilServerSig := pac pacInvalidNilServerSig.ServerChecksum = nil pacInvalidNilKdcSig := pac pacInvalidNilKdcSig.KDCChecksum = nil pacInvalidClientInfo := pac pacInvalidClientInfo.ClientInfo = nil var pacs = []struct { pac PACType }{ {pacInvalidServerSig}, {pacInvalidNilKerbValidationInfo}, {pacInvalidNilServerSig}, {pacInvalidNilKdcSig}, {pacInvalidClientInfo}, } for i, s := range pacs { v, _ := s.pac.verify(key) assert.False(t, v, fmt.Sprintf("Validation should have failed for test %v", i)) } } gokrb5-8.4.3/pac/s4u_delegation_info.go000066400000000000000000000015641427031340300177350ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) // S4UDelegationInfo implements https://msdn.microsoft.com/en-us/library/cc237944.aspx type S4UDelegationInfo struct { S4U2proxyTarget mstypes.RPCUnicodeString // The name of the principal to whom the application can forward the ticket. TransitedListSize uint32 S4UTransitedServices []mstypes.RPCUnicodeString `ndr:"pointer,conformant"` // List of all services that have been delegated through by this client and subsequent services or servers.. Size is value of TransitedListSize } // Unmarshal bytes into the S4UDelegationInfo struct func (k *S4UDelegationInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(k) if err != nil { err = fmt.Errorf("error unmarshaling S4UDelegationInfo: %v", err) } return } gokrb5-8.4.3/pac/signature_data.go000066400000000000000000000041001427031340300167730ustar00rootroot00000000000000package pac import ( "bytes" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/rpc.v1/mstypes" ) /* https://msdn.microsoft.com/en-us/library/cc237955.aspx The Key Usage Value MUST be KERB_NON_KERB_CKSUM_SALT (17) [MS-KILE] (section 3.1.5.9). Server Signature (SignatureType = 0x00000006) https://msdn.microsoft.com/en-us/library/cc237957.aspx KDC Signature (SignatureType = 0x00000007) https://msdn.microsoft.com/en-us/library/dd357117.aspx */ // SignatureData implements https://msdn.microsoft.com/en-us/library/cc237955.aspx type SignatureData struct { SignatureType uint32 // A 32-bit unsigned integer value in little-endian format that defines the cryptographic system used to calculate the checksum. This MUST be one of the following checksum types: KERB_CHECKSUM_HMAC_MD5 (signature size = 16), HMAC_SHA1_96_AES128 (signature size = 12), HMAC_SHA1_96_AES256 (signature size = 12). Signature []byte // Size depends on the type. See comment above. RODCIdentifier uint16 // A 16-bit unsigned integer value in little-endian format that contains the first 16 bits of the key version number ([MS-KILE] section 3.1.5.8) when the KDC is an RODC. When the KDC is not an RODC, this field does not exist. } // Unmarshal bytes into the SignatureData struct func (k *SignatureData) Unmarshal(b []byte) (rb []byte, err error) { r := mstypes.NewReader(bytes.NewReader(b)) k.SignatureType, err = r.Uint32() if err != nil { return } var c int switch k.SignatureType { case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED: c = 16 case uint32(chksumtype.HMAC_SHA1_96_AES128): c = 12 case uint32(chksumtype.HMAC_SHA1_96_AES256): c = 12 } k.Signature, err = r.ReadBytes(c) if err != nil { return } // When the KDC is not an Read Only Domain Controller (RODC), this field does not exist. if len(b) >= 4+c+2 { k.RODCIdentifier, err = r.Uint16() if err != nil { return } } // Create bytes with zeroed signature needed for checksum verification rb = make([]byte, len(b), len(b)) copy(rb, b) z := make([]byte, len(b), len(b)) copy(rb[4:4+c], z) return } gokrb5-8.4.3/pac/signature_data_test.go000066400000000000000000000034601427031340300200420ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestPAC_SignatureData_Unmarshal_Server_Signature(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_Server_Signature) if err != nil { t.Fatal("Could not decode test data hex string") } var k SignatureData bz, err := k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } sig, _ := hex.DecodeString("1e251d98d552be7df384f550") zeroed, _ := hex.DecodeString("10000000000000000000000000000000") assert.Equal(t, uint32(chksumtype.HMAC_SHA1_96_AES256), k.SignatureType, "Server signature type not as expected") assert.Equal(t, sig, k.Signature, "Server signature not as expected") assert.Equal(t, uint16(0), k.RODCIdentifier, "RODC Identifier not as expected") assert.Equal(t, zeroed, bz, "Returned bytes with zeroed signature not as expected") } func TestPAC_SignatureData_Unmarshal_KDC_Signature(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_KDC_Signature) if err != nil { t.Fatal("Could not decode test data hex string") } var k SignatureData bz, err := k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } sig, _ := hex.DecodeString("340be28b48765d0519ee9346cf53d822") zeroed, _ := hex.DecodeString("76ffffff00000000000000000000000000000000") assert.Equal(t, chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED, k.SignatureType, "Server signature type not as expected") assert.Equal(t, sig, k.Signature, "Server signature not as expected") assert.Equal(t, uint16(0), k.RODCIdentifier, "RODC Identifier not as expected") assert.Equal(t, zeroed, bz, "Returned bytes with zeroed signature not as expected") } gokrb5-8.4.3/pac/supplemental_cred.go000066400000000000000000000047361427031340300175260ustar00rootroot00000000000000package pac import ( "bytes" "encoding/binary" "errors" "fmt" "gopkg.in/jcmturner/rpc.v1/mstypes" "gopkg.in/jcmturner/rpc.v1/ndr" ) const ( // NTLMSupCredLMOWF indicates that the LM OWF member is present and valid. NTLMSupCredLMOWF uint32 = 31 // NTLMSupCredNTOWF indicates that the NT OWF member is present and valid. NTLMSupCredNTOWF uint32 = 30 ) // NTLMSupplementalCred implements https://msdn.microsoft.com/en-us/library/cc237949.aspx type NTLMSupplementalCred struct { Version uint32 // A 32-bit unsigned integer that defines the credential version.This field MUST be 0x00000000. Flags uint32 LMPassword []byte // A 16-element array of unsigned 8-bit integers that define the LM OWF. The LMPassword member MUST be ignored if the L flag is not set in the Flags member. NTPassword []byte // A 16-element array of unsigned 8-bit integers that define the NT OWF. The NTPassword member MUST be ignored if the N flag is not set in the Flags member. } // Unmarshal converts the bytes provided into a NTLMSupplementalCred. func (c *NTLMSupplementalCred) Unmarshal(b []byte) (err error) { r := mstypes.NewReader(bytes.NewReader(b)) c.Version, err = r.Uint32() if err != nil { return } if c.Version != 0 { err = errors.New("NTLMSupplementalCred version is not zero") return } c.Flags, err = r.Uint32() if err != nil { return } if isFlagSet(c.Flags, NTLMSupCredLMOWF) { c.LMPassword, err = r.ReadBytes(16) if err != nil { return } } if isFlagSet(c.Flags, NTLMSupCredNTOWF) { c.NTPassword, err = r.ReadBytes(16) if err != nil { return } } return } // isFlagSet tests if a flag is set in the uint32 little endian flag func isFlagSet(f uint32, i uint32) bool { //Which byte? b := int(i / 8) //Which bit in byte p := uint(7 - (int(i) - 8*b)) fb := make([]byte, 4) binary.LittleEndian.PutUint32(fb, f) if fb[b]&(1< - no domain specified // \ // @ if strings.Contains(vc[0], `\`) { u := strings.SplitN(vc[0], `\`, 2) domain = u[0] username = u[1] } else if strings.Contains(vc[0], `@`) { u := strings.SplitN(vc[0], `@`, 2) domain = u[1] username = u[0] } else { username = vc[0] } return } gokrb5-8.4.3/service/authenticator_test.go000066400000000000000000000006051427031340300206150ustar00rootroot00000000000000package service import ( "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/goidentity.v3" ) func TestImplementsInterface(t *testing.T) { t.Parallel() //s := new(SPNEGOAuthenticator) var s KRB5BasicAuthenticator a := new(goidentity.Authenticator) assert.Implements(t, a, s, "SPNEGOAuthenticator type does not implement the goidentity.Authenticator interface") } gokrb5-8.4.3/service/cache.go000066400000000000000000000066521427031340300157570ustar00rootroot00000000000000// Package service provides server side integrations for Kerberos authentication. package service import ( "gopkg.in/jcmturner/gokrb5.v7/types" "sync" "time" ) // Replay cache is required as specified in RFC 4120 section 3.2.3 // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets. type Cache struct { entries map[string]clientEntries mux sync.RWMutex } // clientEntries holds entries of client details sent to the service. type clientEntries struct { replayMap map[time.Time]replayCacheEntry seqNumber int64 subKey types.EncryptionKey } // Cache entry tracking client time values of tickets sent to the service. type replayCacheEntry struct { presentedTime time.Time sName types.PrincipalName cTime time.Time // This combines the ticket's CTime and Cusec } func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) { c.mux.RLock() defer c.mux.RUnlock() ce, ok := c.entries[cname.PrincipalNameString()] return ce, ok } func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) { if ce, ok := c.getClientEntries(cname); ok { c.mux.RLock() defer c.mux.RUnlock() if e, ok := ce.replayMap[t]; ok { return e, true } } return replayCacheEntry{}, false } // Instance of the ServiceCache. This needs to be a singleton. var replayCache Cache var once sync.Once // GetReplayCache returns a pointer to the Cache singleton. func GetReplayCache(d time.Duration) *Cache { // Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries once.Do(func() { replayCache = Cache{ entries: make(map[string]clientEntries), } go func() { for { // TODO consider using a context here. time.Sleep(d) replayCache.ClearOldEntries(d) } }() }) return &replayCache } // AddEntry adds an entry to the Cache. func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) { ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) if ce, ok := c.getClientEntries(a.CName); ok { c.mux.Lock() defer c.mux.Unlock() ce.replayMap[ct] = replayCacheEntry{ presentedTime: time.Now().UTC(), sName: sname, cTime: ct, } ce.seqNumber = a.SeqNumber ce.subKey = a.SubKey } else { c.mux.Lock() defer c.mux.Unlock() c.entries[a.CName.PrincipalNameString()] = clientEntries{ replayMap: map[time.Time]replayCacheEntry{ ct: { presentedTime: time.Now().UTC(), sName: sname, cTime: ct, }, }, seqNumber: a.SeqNumber, subKey: a.SubKey, } } } // ClearOldEntries clears entries from the Cache that are older than the duration provided. func (c *Cache) ClearOldEntries(d time.Duration) { c.mux.Lock() defer c.mux.Unlock() for ke, ce := range c.entries { for k, e := range ce.replayMap { if time.Now().UTC().Sub(e.presentedTime) > d { delete(ce.replayMap, k) } } if len(ce.replayMap) == 0 { delete(c.entries, ke) } } } // IsReplay tests if the Authenticator provided is a replay within the duration defined. If this is not a replay add the entry to the cache for tracking. func (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool { ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) if e, ok := c.getClientEntry(a.CName, ct); ok { if e.sName.Equal(sname) { return true } } c.AddEntry(sname, a) return false } gokrb5-8.4.3/service/settings.go000066400000000000000000000073511427031340300165510ustar00rootroot00000000000000package service import ( "log" "time" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/types" ) // Settings defines service side configuration settings. type Settings struct { Keytab *keytab.Keytab ktprinc *types.PrincipalName sname string requireHostAddr bool disablePACDecoding bool cAddr types.HostAddress maxClockSkew time.Duration logger *log.Logger } // NewSettings creates a new service Settings. func NewSettings(kt *keytab.Keytab, settings ...func(*Settings)) *Settings { s := new(Settings) s.Keytab = kt for _, set := range settings { set(s) } return s } // RequireHostAddr used to configure service side to required host addresses to be specified in Kerberos tickets. // // s := NewSettings(kt, RequireHostAddr(true)) func RequireHostAddr(b bool) func(*Settings) { return func(s *Settings) { s.requireHostAddr = b } } // RequireHostAddr indicates if the service should require the host address to be included in the ticket. func (s *Settings) RequireHostAddr() bool { return s.requireHostAddr } // DecodePAC used to configure service side to enable/disable PAC decoding if the PAC is present. // Defaults to enabled if not specified. // // s := NewSettings(kt, DecodePAC(false)) func DecodePAC(b bool) func(*Settings) { return func(s *Settings) { s.disablePACDecoding = !b } } // DecodePAC indicates whether the service should decode any PAC information present in the ticket. func (s *Settings) DecodePAC() bool { return !s.disablePACDecoding } // ClientAddress used to configure service side with the clients host address to be used during validation. // // s := NewSettings(kt, ClientAddress(h)) func ClientAddress(h types.HostAddress) func(*Settings) { return func(s *Settings) { s.cAddr = h } } // ClientAddress returns the client host address which has been provided to the service. func (s *Settings) ClientAddress() types.HostAddress { return s.cAddr } // Logger used to configure service side with a logger. // // s := NewSettings(kt, Logger(l)) func Logger(l *log.Logger) func(*Settings) { return func(s *Settings) { s.logger = l } } // Logger returns the logger instances configured for the service. If none is configured nill will be returned. func (s *Settings) Logger() *log.Logger { return s.logger } // KeytabPrincipal used to override the principal name used to find the key in the keytab. // // s := NewSettings(kt, KeytabPrincipal("someaccount")) func KeytabPrincipal(p string) func(*Settings) { return func(s *Settings) { pn, _ := types.ParseSPNString(p) s.ktprinc = &pn } } // KeytabPrincipal returns the principal name used to find the key in the keytab if it has been overridden. func (s *Settings) KeytabPrincipal() *types.PrincipalName { return s.ktprinc } // MaxClockSkew used to configure service side with the maximum acceptable clock skew // between the service and the issue time of kerberos tickets // // s := NewSettings(kt, MaxClockSkew(d)) func MaxClockSkew(d time.Duration) func(*Settings) { return func(s *Settings) { s.maxClockSkew = d } } // MaxClockSkew returns the maximum acceptable clock skew between the service and the issue time of kerberos tickets. // If none is defined a duration of 5 minutes is returned. func (s *Settings) MaxClockSkew() time.Duration { if s.maxClockSkew.Nanoseconds() == 0 { return time.Duration(5) * time.Minute } return s.maxClockSkew } // SName used provide a specific service name to the service settings. // // s := NewSettings(kt, SName("HTTP/some.service.com")) func SName(sname string) func(*Settings) { return func(s *Settings) { s.sname = sname } } // SName returns the specific service name to the service. func (s *Settings) SName() string { return s.sname } gokrb5-8.4.3/spnego/000077500000000000000000000000001427031340300142075ustar00rootroot00000000000000gokrb5-8.4.3/spnego/http.go000066400000000000000000000237311427031340300155230ustar00rootroot00000000000000package spnego import ( "bytes" "context" "encoding/base64" "errors" "fmt" "io" "io/ioutil" "net" "net/http" "net/http/cookiejar" "net/url" "strings" "gopkg.in/jcmturner/goidentity.v3" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/gssapi" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/types" ) // Client side functionality // // Client will negotiate authentication with a server using SPNEGO. type Client struct { *http.Client krb5Client *client.Client spn string reqs []*http.Request } type redirectErr struct { reqTarget *http.Request } func (e redirectErr) Error() string { return fmt.Sprintf("redirect to %v", e.reqTarget.URL) } type teeReadCloser struct { io.Reader io.Closer } // NewClient returns an SPNEGO enabled HTTP client. func NewClient(krb5Cl *client.Client, httpCl *http.Client, spn string) *Client { if httpCl == nil { httpCl = http.DefaultClient } // Add a cookie jar if there isn't one if httpCl.Jar == nil { httpCl.Jar, _ = cookiejar.New(nil) } // Add a CheckRedirect function that will execute any functional already defined and then error with a redirectErr f := httpCl.CheckRedirect httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error { if f != nil { err := f(req, via) if err != nil { return err } } return redirectErr{reqTarget: req} } return &Client{ Client: httpCl, krb5Client: krb5Cl, spn: spn, } } // Do is the SPNEGO enabled HTTP client's equivalent of the http.Client's Do method. func (c *Client) Do(req *http.Request) (resp *http.Response, err error) { var body bytes.Buffer if req.Body != nil { // Use a tee reader to capture any body sent in case we have to replay it again teeR := io.TeeReader(req.Body, &body) teeRC := teeReadCloser{teeR, req.Body} req.Body = teeRC } resp, err = c.Client.Do(req) if err != nil { if ue, ok := err.(*url.Error); ok { if e, ok := ue.Err.(redirectErr); ok { // Picked up a redirect e.reqTarget.Header.Del(HTTPHeaderAuthRequest) c.reqs = append(c.reqs, e.reqTarget) if len(c.reqs) >= 10 { return resp, errors.New("stopped after 10 redirects") } if req.Body != nil { // Refresh the body reader so the body can be sent again e.reqTarget.Body = ioutil.NopCloser(&body) } return c.Do(e.reqTarget) } } return resp, err } if respUnauthorizedNegotiate(resp) { err := SetSPNEGOHeader(c.krb5Client, req, c.spn) if err != nil { return resp, err } if req.Body != nil { // Refresh the body reader so the body can be sent again req.Body = ioutil.NopCloser(&body) } return c.Do(req) } return resp, err } // Get is the SPNEGO enabled HTTP client's equivalent of the http.Client's Get method. func (c *Client) Get(url string) (resp *http.Response, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } return c.Do(req) } // Post is the SPNEGO enabled HTTP client's equivalent of the http.Client's Post method. func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) { req, err := http.NewRequest("POST", url, body) if err != nil { return nil, err } req.Header.Set("Content-Type", contentType) return c.Do(req) } // PostForm is the SPNEGO enabled HTTP client's equivalent of the http.Client's PostForm method. func (c *Client) PostForm(url string, data url.Values) (resp *http.Response, err error) { return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } // Head is the SPNEGO enabled HTTP client's equivalent of the http.Client's Head method. func (c *Client) Head(url string) (resp *http.Response, err error) { req, err := http.NewRequest("HEAD", url, nil) if err != nil { return nil, err } return c.Do(req) } func respUnauthorizedNegotiate(resp *http.Response) bool { if resp.StatusCode == http.StatusUnauthorized { if resp.Header.Get(HTTPHeaderAuthResponse) == HTTPHeaderAuthResponseValueKey { return true } } return false } // SetSPNEGOHeader gets the service ticket and sets it as the SPNEGO authorization header on HTTP request object. // To auto generate the SPN from the request object pass a null string "". func SetSPNEGOHeader(cl *client.Client, r *http.Request, spn string) error { if spn == "" { h := strings.TrimSuffix(strings.SplitN(r.URL.Host, ":", 2)[0], ".") name, err := net.LookupCNAME(h) if err == nil { // Underlyng canonical name should be used for SPN h = strings.TrimSuffix(name, ".") } spn = "HTTP/" + h r.Host = h } cl.Log("using SPN %s", spn) s := SPNEGOClient(cl, spn) err := s.AcquireCred() if err != nil { return fmt.Errorf("could not acquire client credential: %v", err) } st, err := s.InitSecContext() if err != nil { return fmt.Errorf("could not initialize context: %v", err) } nb, err := st.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO") } hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb) r.Header.Set(HTTPHeaderAuthRequest, hs) return nil } // Service side functionality // type ctxKey string const ( // spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead. spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg==" // spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead. spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC" // spnegoNegTokenRespIncompleteKRB5 - Response token specifying incomplete context and KRB5 as the supported mechtype. spnegoNegTokenRespIncompleteKRB5 = "Negotiate oRQwEqADCgEBoQsGCSqGSIb3EgECAg==" // CTXKeyAuthenticated is the request context key holding a boolean indicating if the request has been authenticated. CTXKeyAuthenticated ctxKey = "github.com/jcmturner/gokrb5/CTXKeyAuthenticated" // CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v2/Identity object. CTXKeyCredentials ctxKey = "github.com/jcmturner/gokrb5/CTXKeyCredentials" // HTTPHeaderAuthRequest is the header that will hold authn/z information. HTTPHeaderAuthRequest = "Authorization" // HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server. HTTPHeaderAuthResponse = "WWW-Authenticate" // HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO. HTTPHeaderAuthResponseValueKey = "Negotiate" // UnauthorizedMsg is the message returned in the body when authentication fails. UnauthorizedMsg = "Unauthorised.\n" ) // SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper. func SPNEGOKRB5Authenticate(inner http.Handler, kt *keytab.Keytab, settings ...func(*service.Settings)) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get the auth header s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2) if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey { // No Authorization header set so return 401 with WWW-Authenticate Negotiate header w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey) http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) return } // Set up the SPNEGO GSS-API mechanism var spnego *SPNEGO h, err := types.GetHostAddress(r.RemoteAddr) if err == nil { // put in this order so that if the user provides a ClientAddress it will override the one here. o := append([]func(*service.Settings){service.ClientAddress(h)}, settings...) spnego = SPNEGOService(kt, o...) } else { spnego = SPNEGOService(kt, settings...) spnego.Log("%s - SPNEGO could not parse client address: %v", r.RemoteAddr, err) } // Decode the header into an SPNEGO context token b, err := base64.StdEncoding.DecodeString(s[1]) if err != nil { spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO error in base64 decoding negotiation header: %v", r.RemoteAddr, err) return } var st SPNEGOToken err = st.Unmarshal(b) if err != nil { spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO error in unmarshaling SPNEGO token: %v", r.RemoteAddr, err) return } // Validate the context token authed, ctx, status := spnego.AcceptSecContext(&st) if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded { spnegoResponseReject(spnego, w, "%s - SPNEGO validation error: %v", r.RemoteAddr, status) return } if status.Code == gssapi.StatusContinueNeeded { spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO GSS-API continue needed", r.RemoteAddr) return } if authed { id := ctx.Value(CTXKeyCredentials).(goidentity.Identity) requestCtx := r.Context() requestCtx = context.WithValue(requestCtx, CTXKeyCredentials, id) requestCtx = context.WithValue(requestCtx, CTXKeyAuthenticated, ctx.Value(CTXKeyAuthenticated)) spnegoResponseAcceptCompleted(spnego, w, "%s %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain()) inner.ServeHTTP(w, r.WithContext(requestCtx)) } else { spnegoResponseReject(spnego, w, "%s - SPNEGO Kerberos authentication failed", r.RemoteAddr) return } }) } func spnegoNegotiateKRB5MechType(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespIncompleteKRB5) http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) } func spnegoResponseReject(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject) http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) } func spnegoResponseAcceptCompleted(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted) } gokrb5-8.4.3/spnego/http_test.go000066400000000000000000000242261427031340300165620ustar00rootroot00000000000000package spnego import ( "bytes" "crypto/rand" "encoding/hex" "fmt" "io" "io/ioutil" "log" "mime/multipart" "net/http" "net/http/httptest" "os" "sync" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/goidentity.v3" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/config" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/test" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestClient_SetSPNEGOHeader(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} l := log.New(os.Stderr, "SPNEGO Client:", log.LstdFlags) cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c, client.Logger(l)) err := cl.Login() if err != nil { t.Fatalf("error on AS_REQ: %v\n", err) } urls := []string{ "http://cname.test.gokrb5", "http://host.test.gokrb5", } paths := []string{ "/modkerb/index.html", //"/modgssapi/index.html", } for _, url := range urls { for _, p := range paths { r, _ := http.NewRequest("GET", url+p, nil) httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("%s request error: %v", url+p, err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected") err = SetSPNEGOHeader(cl, r, "") if err != nil { t.Fatalf("error setting client SPNEGO header: %v", err) } httpResp, err = http.DefaultClient.Do(r) if err != nil { t.Fatalf("%s request error: %v\n", url+p, err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } } } func TestSPNEGOHTTPClient(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} l := log.New(os.Stderr, "SPNEGO Client:", log.LstdFlags) cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c, client.Logger(l)) err := cl.Login() if err != nil { t.Fatalf("error on AS_REQ: %v\n", err) } urls := []string{ "http://cname.test.gokrb5", "http://host.test.gokrb5", } // This path issues a redirect which the http client will automatically follow. // It should cause a replay issue if the negInit token is sent in the first instance. paths := []string{ "/modgssapi", // This issues a redirect which the http client will automatically follow. Could cause a replay issue "/redirect", } for _, url := range urls { for _, p := range paths { r, _ := http.NewRequest("GET", url+p, nil) httpCl := http.DefaultClient httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error { t.Logf("http client redirect: %+v", *req) return nil } spnegoCl := NewClient(cl, httpCl, "") httpResp, err := spnegoCl.Do(r) if err != nil { t.Fatalf("%s request error: %v", url+p, err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } } } func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) { s := httpServer() defer s.Close() r, _ := http.NewRequest("GET", s.URL, nil) httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected") assert.Equal(t, "Negotiate", httpResp.Header.Get("WWW-Authenticate"), "Negotiation header not set by server.") } func TestService_SPNEGOKRB_ValidUser(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() r, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() err := SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } func TestService_SPNEGOKRB_Replay(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() r1, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } // First request with this ticket should be accepted httpResp, err := http.DefaultClient.Do(r1) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") // Use ticket again should be rejected httpResp, err = http.DefaultClient.Do(r1) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.") // Form a 2nd ticket r2, _ := http.NewRequest("GET", s.URL, nil) err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } // First use of 2nd ticket should be accepted httpResp, err = http.DefaultClient.Do(r2) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") // Using the 1st ticket again should still be rejected httpResp, err = http.DefaultClient.Do(r1) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.") // Using the 2nd again should be rejected as replay httpResp, err = http.DefaultClient.Do(r2) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.") } func TestService_SPNEGOKRB_ReplayCache_Concurrency(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() r1, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } r2, _ := http.NewRequest("GET", s.URL, nil) err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } // Concurrent 1st requests should be OK var wg sync.WaitGroup wg.Add(2) go httpGet(r1, &wg) go httpGet(r2, &wg) wg.Wait() // A number of concurrent requests with the same ticket should be rejected due to replay var wg2 sync.WaitGroup noReq := 10 wg2.Add(noReq * 2) for i := 0; i < noReq; i++ { go httpGet(r1, &wg2) go httpGet(r2, &wg2) } wg2.Wait() } func TestService_SPNEGOKRB_Upload(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) fileWriter, err := bodyWriter.CreateFormFile("uploadfile", "testfile.bin") if err != nil { t.Fatalf("error writing to buffer: %v", err) } data := make([]byte, 10240) rand.Read(data) br := bytes.NewReader(data) _, err = io.Copy(fileWriter, br) if err != nil { t.Fatalf("error copying bytes: %v", err) } bodyWriter.Close() r, _ := http.NewRequest("POST", s.URL, bodyBuf) r.Header.Set("Content-Type", bodyWriter.FormDataContentType()) cl := getClient() spnegoCl := NewClient(cl, nil, "HTTP/host.test.gokrb5") httpResp, err := spnegoCl.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } if httpResp.StatusCode != http.StatusOK { bodyBytes, _ := ioutil.ReadAll(httpResp.Body) bodyString := string(bodyBytes) httpResp.Body.Close() t.Errorf("unexpected code from http server (%d): %s", httpResp.StatusCode, bodyString) } } func httpGet(r *http.Request, wg *sync.WaitGroup) { defer wg.Done() http.DefaultClient.Do(r) } func httpServer() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile) b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, service.Logger(l))) return s } func testAppHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { maxUploadSize := int64(11240) if err := r.ParseMultipartForm(maxUploadSize); err != nil { http.Error(w, fmt.Sprintf("cannot parse multipart form: %v", err), http.StatusBadRequest) return } r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) file, _, err := r.FormFile("uploadfile") if err != nil { http.Error(w, "INVALID_FILE", http.StatusBadRequest) return } defer file.Close() // write out to /dev/null _, err = io.Copy(ioutil.Discard, file) if err != nil { http.Error(w, "WRITE_ERR", http.StatusInternalServerError) return } } w.WriteHeader(http.StatusOK) ctx := r.Context() fmt.Fprintf(w, "\nTEST.GOKRB5 Handler\nAuthenticed user: %s\nUser's realm: %s\n", ctx.Value(CTXKeyCredentials).(goidentity.Identity).UserName(), ctx.Value(CTXKeyCredentials).(goidentity.Identity).Domain()) return } func getClient() *client.Client { b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF) c.LibDefaults.NoAddresses = true addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.TEST_KDC_ADDR } c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC} c.Realms[0].KPasswdServer = []string{addr + ":464"} cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c) return cl } gokrb5-8.4.3/spnego/krb5Token.go000066400000000000000000000146451427031340300164140ustar00rootroot00000000000000package spnego import ( "context" "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/gssapi" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/krberror" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/types" ) // GSSAPI KRB5 MechToken IDs. const ( TOK_ID_KRB_AP_REQ = "0100" TOK_ID_KRB_AP_REP = "0200" TOK_ID_KRB_ERROR = "0300" ) // KRB5Token context token implementation for GSSAPI. type KRB5Token struct { OID asn1.ObjectIdentifier tokID []byte APReq messages.APReq APRep messages.APRep KRBError messages.KRBError settings *service.Settings context context.Context } // Marshal a KRB5Token into a slice of bytes. func (m *KRB5Token) Marshal() ([]byte, error) { // Create the header b, _ := asn1.Marshal(m.OID) b = append(b, m.tokID...) var tb []byte var err error switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: tb, err = m.APReq.Marshal() if err != nil { return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err) } case TOK_ID_KRB_AP_REP: return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5") case TOK_ID_KRB_ERROR: return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5") } if err != nil { return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err) } b = append(b, tb...) return asn1tools.AddASNAppTag(b, 0), nil } // Unmarshal a KRB5Token. func (m *KRB5Token) Unmarshal(b []byte) error { var oid asn1.ObjectIdentifier r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err) } if !oid.Equal(gssapi.OID(gssapi.OIDKRB5)) { return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", oid.String(), gssapi.OID(gssapi.OIDKRB5).String()) } m.OID = oid if len(r) < 2 { return fmt.Errorf("krb5token too short") } m.tokID = r[0:2] switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: var a messages.APReq err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err) } m.APReq = a case TOK_ID_KRB_AP_REP: var a messages.APRep err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err) } m.APRep = a case TOK_ID_KRB_ERROR: var a messages.KRBError err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err) } m.KRBError = a } return nil } // Verify a KRB5Token. func (m *KRB5Token) Verify() (bool, gssapi.Status) { switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: ok, creds, err := service.VerifyAPREQ(m.APReq, m.settings) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"} } m.context = context.Background() m.context = context.WithValue(m.context, CTXKeyCredentials, creds) m.context = context.WithValue(m.context, CTXKeyAuthenticated, ok) return true, gssapi.Status{Code: gssapi.StatusComplete} case TOK_ID_KRB_AP_REP: // Client side // TODO how to verify the AP_REP - not yet implemented return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"} case TOK_ID_KRB_ERROR: if m.KRBError.MsgType != msgtype.KRB_ERROR { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"} } return true, gssapi.Status{Code: gssapi.StatusUnavailable} } return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"} } // IsAPReq tests if the MechToken contains an AP_REQ. func (m *KRB5Token) IsAPReq() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ { return true } return false } // IsAPRep tests if the MechToken contains an AP_REP. func (m *KRB5Token) IsAPRep() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP { return true } return false } // IsKRBError tests if the MechToken contains an KRB_ERROR. func (m *KRB5Token) IsKRBError() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR { return true } return false } // Context returns the KRB5 token's context which will contain any verify user identity information. func (m *KRB5Token) Context() context.Context { return m.context } // NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) { // TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client. var m KRB5Token m.OID = gssapi.OID(gssapi.OIDKRB5) tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ) m.tokID = tb auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags) if err != nil { return m, err } APReq, err := messages.NewAPReq( tkt, sessionKey, auth, ) if err != nil { return m, err } for _, o := range APOptions { types.SetFlag(&APReq.APOptions, o) } m.APReq = APReq return m, nil } // krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) { //RFC 4121 Section 4.1.1 auth, err := types.NewAuthenticator(creds.Domain(), creds.CName()) if err != nil { return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") } auth.Cksum = types.Checksum{ CksumType: chksumtype.GSSAPI, Checksum: newAuthenticatorChksum(flags), } return auth, nil } // Create new authenticator checksum for kerberos MechToken func newAuthenticatorChksum(flags []int) []byte { a := make([]byte, 24) binary.LittleEndian.PutUint32(a[:4], 16) for _, i := range flags { if i == gssapi.ContextFlagDeleg { x := make([]byte, 28-len(a)) a = append(a, x...) } f := binary.LittleEndian.Uint32(a[20:24]) f |= uint32(i) binary.LittleEndian.PutUint32(a[20:24], f) } return a } gokrb5-8.4.3/spnego/krb5Token_test.go000066400000000000000000000154701427031340300174500ustar00rootroot00000000000000package spnego import ( "encoding/hex" "math" "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/credentials" "gopkg.in/jcmturner/gokrb5.v7/gssapi" "gopkg.in/jcmturner/gokrb5.v7/iana/msgtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" "gopkg.in/jcmturner/gokrb5.v7/types" ) const ( KRB5TokenHex = "6082026306092a864886f71201020201006e8202523082024ea003020105a10302010ea20703050000000000a382015d6182015930820155a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382011830820114a003020112a103020103a28201060482010230621d868c97f30bf401e03bbffcd724bd9d067dce2afc31f71a356449b070cdafcc1ff372d0eb1e7a708b50c0152f3996c45b1ea312a803907fb97192d39f20cdcaea29876190f51de6e2b4a4df0460122ed97f363434e1e120b0e76c172b4424a536987152ac0b73013ab88af4b13a3fcdc63f739039dd46d839709cf5b51bb0ce6cb3af05fab3844caac280929955495235e9d0424f8a1fb9b4bd4f6bba971f40b97e9da60b9dabfcf0b1feebfca02c9a19b327a0004aa8e19192726cf347561fa8ac74afad5d6a264e50cf495b93aac86c77b2bc2d184234f6c2767dbea431485a25687b9044a20b601e968efaefffa1fc5283ff32aa6a53cb6c5cdd2eddcb26a481d73081d4a003020112a103020103a281c70481c4a1b29e420324f7edf9efae39df7bcaaf196a3160cf07e72f52a4ef8a965721b2f3343719c50699046e4fcc18ca26c2bfc7e4a9eddfc9d9cfc57ff2f6bdbbd1fc40ac442195bc669b9a0dbba12563b3e4cac9f4022fc01b8aa2d1ab84815bb078399ff7f4d5f9815eef896a0c7e3c049e6fd9932b97096cdb5861425b9d81753d0743212ded1a0fb55a00bf71a46be5ce5e1c8a5cc327b914347d9efcb6cb31ca363b1850d95c7b6c4c3cc6301615ad907318a0c5379d343610fab17eca9c7dc0a5a60658" AuthChksum = "100000000000000000000000000000000000000030000000" ) func TestKRB5Token_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(KRB5TokenHex) if err != nil { t.Fatalf("Error decoding KRB5Token hex: %v", err) } var mt KRB5Token err = mt.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling KRB5Token: %v", err) } assert.Equal(t, gssapi.OID(gssapi.OIDKRB5), mt.OID, "KRB5Token OID not as expected.") assert.Equal(t, []byte{1, 0}, mt.tokID, "TokID not as expected") assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "KRB5Token AP_REQ does not have the right message type.") assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in KRB5Token does not indicate no error.") assert.Equal(t, int32(18), mt.APReq.EncryptedAuthenticator.EType, "Authenticator within AP_REQ does not have the etype expected.") } func TestKRB5Token_newAuthenticatorChksum(t *testing.T) { t.Parallel() b, err := hex.DecodeString(AuthChksum) if err != nil { t.Fatalf("Error decoding KRB5Token hex: %v", err) } cb := newAuthenticatorChksum([]int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}) assert.Equal(t, b, cb, "SPNEGO Authenticator checksum not as expected") } // Test with explicit subkey generation. func TestKRB5Token_newAuthenticatorWithSubkeyGeneration(t *testing.T) { t.Parallel() creds := credentials.New("hftsai", testdata.TEST_REALM) creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING}) var etypeID int32 = 18 keyLen := 32 // etypeID 18 refers to AES256 -> 32 bytes key a, err := krb5TokenAuthenticator(creds, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}) if err != nil { t.Fatalf("Error creating authenticator: %v", err) } a.GenerateSeqNumberAndSubKey(etypeID, keyLen) assert.Equal(t, int32(32771), a.Cksum.CksumType, "Checksum type in authenticator for SPNEGO mechtoken not as expected.") assert.Equal(t, etypeID, a.SubKey.KeyType, "Subkey not of the expected type.") assert.Equal(t, keyLen, len(a.SubKey.KeyValue), "Subkey value not of the right length") var nz bool for _, b := range a.SubKey.KeyValue { if b != byte(0) { nz = true } } assert.True(t, nz, "subkey not initialised") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber > 0 }), "Sequence number is not greater than zero") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber <= math.MaxUint32 })) } // Test without subkey generation. func TestKRB5Token_newAuthenticator(t *testing.T) { t.Parallel() creds := credentials.New("hftsai", testdata.TEST_REALM) creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING}) a, err := krb5TokenAuthenticator(creds, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}) if err != nil { t.Fatalf("Error creating authenticator: %v", err) } assert.Equal(t, int32(32771), a.Cksum.CksumType, "Checksum type in authenticator for SPNEGO mechtoken not as expected.") assert.Equal(t, int32(0), a.SubKey.KeyType, "Subkey not of the expected type.") assert.Nil(t, a.SubKey.KeyValue, "Subkey should not be set.") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber > 0 }), "Sequence number is not greater than zero") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber <= math.MaxUint32 })) } func TestNewAPREQKRB5Token_and_Marshal(t *testing.T) { t.Parallel() creds := credentials.New("hftsai", testdata.TEST_REALM) creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING}) cl := client.Client{ Credentials: creds, } var tkt messages.Ticket b, err := hex.DecodeString(testdata.MarshaledKRB5ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = tkt.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } key := types.EncryptionKey{ KeyType: 18, KeyValue: make([]byte, 32), } mt, err := NewKRB5TokenAPREQ(&cl, tkt, key, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{}) if err != nil { t.Fatalf("Error creating KRB5Token: %v", err) } mb, err := mt.Marshal() if err != nil { t.Fatalf("Error unmarshalling KRB5Token: %v", err) } err = mt.Unmarshal(mb) if err != nil { t.Fatalf("Error unmarshalling KRB5Token: %v", err) } assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, mt.OID, "KRB5Token OID not as expected.") assert.Equal(t, []byte{1, 0}, mt.tokID, "TokID not as expected") assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "KRB5Token AP_REQ does not have the right message type.") assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in KRB5Token does not indicate no error.") assert.Equal(t, testdata.TEST_REALM, mt.APReq.Ticket.Realm, "Realm in ticket within the AP_REQ of the KRB5Token not as expected.") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, mt.APReq.Ticket.SName.NameString, "SName in ticket within the AP_REQ of the KRB5Token not as expected.") assert.Equal(t, int32(18), mt.APReq.EncryptedAuthenticator.EType, "Authenticator within AP_REQ does not have the etype expected.") } gokrb5-8.4.3/spnego/negotiationToken.go000066400000000000000000000211601427031340300200570ustar00rootroot00000000000000package spnego import ( "context" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/gssapi" "gopkg.in/jcmturner/gokrb5.v7/messages" "gopkg.in/jcmturner/gokrb5.v7/service" "gopkg.in/jcmturner/gokrb5.v7/types" ) // https://msdn.microsoft.com/en-us/library/ms995330.aspx // Negotiation state values. const ( NegStateAcceptCompleted NegState = 0 NegStateAcceptIncomplete NegState = 1 NegStateReject NegState = 2 NegStateRequestMIC NegState = 3 ) // NegState is a type to indicate the SPNEGO negotiation state. type NegState int // NegTokenInit implements Negotiation Token of type Init. type NegTokenInit struct { MechTypes []asn1.ObjectIdentifier ReqFlags gssapi.ContextFlags MechTokenBytes []byte MechListMIC []byte mechToken gssapi.ContextToken settings *service.Settings } type marshalNegTokenInit struct { MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"` ReqFlags gssapi.ContextFlags `asn1:"explicit,optional,tag:1"` MechTokenBytes []byte `asn1:"explicit,optional,omitempty,tag:2"` MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens } // NegTokenResp implements Negotiation Token of type Resp/Targ type NegTokenResp struct { NegState asn1.Enumerated SupportedMech asn1.ObjectIdentifier ResponseToken []byte MechListMIC []byte mechToken gssapi.ContextToken settings *service.Settings } type marshalNegTokenResp struct { NegState asn1.Enumerated `asn1:"explicit,tag:0"` SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"` ResponseToken []byte `asn1:"explicit,optional,omitempty,tag:2"` MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens } // NegTokenTarg implements Negotiation Token of type Resp/Targ type NegTokenTarg NegTokenResp // Marshal an Init negotiation token func (n *NegTokenInit) Marshal() ([]byte, error) { m := marshalNegTokenInit{ MechTypes: n.MechTypes, ReqFlags: n.ReqFlags, MechTokenBytes: n.MechTokenBytes, MechListMIC: n.MechListMIC, } b, err := asn1.Marshal(m) if err != nil { return nil, err } nt := asn1.RawValue{ Tag: 0, Class: 2, IsCompound: true, Bytes: b, } nb, err := asn1.Marshal(nt) if err != nil { return nil, err } return nb, nil } // Unmarshal an Init negotiation token func (n *NegTokenInit) Unmarshal(b []byte) error { init, nt, err := UnmarshalNegToken(b) if err != nil { return err } if !init { return errors.New("bytes were not that of a NegTokenInit") } nInit := nt.(NegTokenInit) n.MechTokenBytes = nInit.MechTokenBytes n.MechListMIC = nInit.MechListMIC n.MechTypes = nInit.MechTypes n.ReqFlags = nInit.ReqFlags return nil } // Verify an Init negotiation token func (n *NegTokenInit) Verify() (bool, gssapi.Status) { // Check if supported mechanisms are in the MechTypeList var mtSupported bool for _, m := range n.MechTypes { if m.Equal(gssapi.OID(gssapi.OIDKRB5)) || m.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5)) { if n.mechToken == nil && n.MechTokenBytes == nil { return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} } mtSupported = true break } } if !mtSupported { return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"} } // There should be some mechtoken bytes for a KRB5Token (other mech types are not supported) mt := new(KRB5Token) mt.settings = n.settings if n.mechToken == nil { err := mt.Unmarshal(n.MechTokenBytes) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } n.mechToken = mt } else { var ok bool mt, ok = n.mechToken.(*KRB5Token) if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"} } } // Verify the mechtoken return n.mechToken.Verify() } // Context returns the SPNEGO context which will contain any verify user identity information. func (n *NegTokenInit) Context() context.Context { if n.mechToken != nil { mt, ok := n.mechToken.(*KRB5Token) if !ok { return nil } return mt.Context() } return nil } // Marshal a Resp/Targ negotiation token func (n *NegTokenResp) Marshal() ([]byte, error) { m := marshalNegTokenResp{ NegState: n.NegState, SupportedMech: n.SupportedMech, ResponseToken: n.ResponseToken, MechListMIC: n.MechListMIC, } b, err := asn1.Marshal(m) if err != nil { return nil, err } nt := asn1.RawValue{ Tag: 1, Class: 2, IsCompound: true, Bytes: b, } nb, err := asn1.Marshal(nt) if err != nil { return nil, err } return nb, nil } // Unmarshal a Resp/Targ negotiation token func (n *NegTokenResp) Unmarshal(b []byte) error { init, nt, err := UnmarshalNegToken(b) if err != nil { return err } if init { return errors.New("bytes were not that of a NegTokenResp") } nResp := nt.(NegTokenResp) n.MechListMIC = nResp.MechListMIC n.NegState = nResp.NegState n.ResponseToken = nResp.ResponseToken n.SupportedMech = nResp.SupportedMech return nil } // Verify a Resp/Targ negotiation token func (n *NegTokenResp) Verify() (bool, gssapi.Status) { if n.SupportedMech.Equal(gssapi.OID(gssapi.OIDKRB5)) || n.SupportedMech.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5)) { if n.mechToken == nil && n.ResponseToken == nil { return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} } mt := new(KRB5Token) mt.settings = n.settings if n.mechToken == nil { err := mt.Unmarshal(n.ResponseToken) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } n.mechToken = mt } else { var ok bool mt, ok = n.mechToken.(*KRB5Token) if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"} } } if mt == nil { return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} } // Verify the mechtoken return mt.Verify() } return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"} } // State returns the negotiation state of the negotiation response. func (n *NegTokenResp) State() NegState { return NegState(n.NegState) } // Context returns the SPNEGO context which will contain any verify user identity information. func (n *NegTokenResp) Context() context.Context { if n.mechToken != nil { mt, ok := n.mechToken.(*KRB5Token) if !ok { return nil } return mt.Context() } return nil } // UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp. // // The boolean indicates if the response is a NegTokenInit. // If error is nil and the boolean is false the response is a NegTokenResp. func UnmarshalNegToken(b []byte) (bool, interface{}, error) { var a asn1.RawValue _, err := asn1.Unmarshal(b, &a) if err != nil { return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err) } switch a.Tag { case 0: var n marshalNegTokenInit _, err = asn1.Unmarshal(a.Bytes, &n) if err != nil { return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err) } nt := NegTokenInit{ MechTypes: n.MechTypes, ReqFlags: n.ReqFlags, MechTokenBytes: n.MechTokenBytes, MechListMIC: n.MechListMIC, } return true, nt, nil case 1: var n marshalNegTokenResp _, err = asn1.Unmarshal(a.Bytes, &n) if err != nil { return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err) } nt := NegTokenResp{ NegState: n.NegState, SupportedMech: n.SupportedMech, ResponseToken: n.ResponseToken, MechListMIC: n.MechListMIC, } return false, nt, nil default: return false, nil, errors.New("unknown choice type for NegotiationToken") } } // NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5 func NewNegTokenInitKRB5(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) { mt, err := NewKRB5TokenAPREQ(cl, tkt, sessionKey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{}) if err != nil { return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", err) } mtb, err := mt.Marshal() if err != nil { return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", err) } return NegTokenInit{ MechTypes: []asn1.ObjectIdentifier{gssapi.OID(gssapi.OIDKRB5)}, MechTokenBytes: mtb, }, nil } gokrb5-8.4.3/spnego/negotiationToken_test.go000066400000000000000000000101411427031340300211130ustar00rootroot00000000000000package spnego import ( "encoding/hex" "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" ) const ( testNegTokenInit = "a08202aa308202a6a027302506092a864886f71201020206052b0501050206092a864882f71201020206062b0601050205a2820279048202756082027106092a864886f71201020201006e8202603082025ca003020105a10302010ea20703050000000000a38201706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020103a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020102a282011904820115d4bd890abc456f44e2e7a2e8111bd6767abf03266dfcda97c629af2ece450a5ae1f145e4a4d1bc2c848e66a6c6b31d9740b26b03cdbd2570bfcf126e90adf5f5ebce9e283ff5086da47b129b14fc0aabd4d1df9c1f3c72b80cc614dfc28783450b2c7b7749651f432b47aaa2ff158c0066b757f3fb00dd7b4f63d68276c76373ecdd3f19c66ebc43a81e577f3c263b878356f57e8d6c4eccd587b81538e70392cf7e73fc12a6f7c537a894a7bb5566c83ac4d69757aa320a51d8d690017aebf952add1889adfc3307b0e6cd8c9b57cf8589fbe52800acb6461c25473d49faa1bdceb8bce3f61db23f9cd6a09d5adceb411e1c4546b30b33331e570fd6bc50aa403557e75f488e759750ea038aab6454667d9b64f41a481d23081cfa003020112a281c70481c4d67ba2ae4cf5d917caab1d863605249320e90482563662ed92408a543b6ad5edeb8f9375e9060a205491df082fd2a5fec93dfb76f41012bb60cae20f07adbb77a1aa56f0521f36e1ea10dc9fb762902b254dd7664d0bcc6f751f2003e41990af1b4330d10477bfad638b9f0b704ac80cc47731f8ec8d801762bad8884b8de90adb1dbe7fc7b0ffafd38fb5eb8b6547cee30d89873281ce63ad70042a13478b1a7c2bdde0f223ace62dbb84e2d06f1070f4265f66e0544449335e2fcc4d0aee5bf81c5999" testNegTokenResp = "a1143012a0030a0100a10b06092a864886f712010202" ) func TestUnmarshal_negTokenInit(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } isInit, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } assert.IsType(t, NegTokenInit{}, nt, "Not the expected type NegTokenInit") assert.True(t, isInit, "Boolean indicating type is negTokenInit is not true") nInit := nt.(NegTokenInit) assert.Equal(t, 4, len(nInit.MechTypes)) expectMechTypes := []asn1.ObjectIdentifier{ []int{1, 2, 840, 113554, 1, 2, 2}, []int{1, 3, 5, 1, 5, 2}, []int{1, 2, 840, 48018, 1, 2, 2}, []int{1, 3, 6, 1, 5, 2, 5}, } assert.Equal(t, expectMechTypes, nInit.MechTypes, "MechTypes list in NegTokenInit not as expected") } func TestMarshal_negTokenInit(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } _, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } nInit := nt.(NegTokenInit) mb, err := nInit.Marshal() if err != nil { t.Fatalf("Error marshalling negotiation init token: %v", err) } assert.Equal(t, b, mb, "Marshalled bytes not as expected for NegTokenInit") } func TestUnmarshal_negTokenResp(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } isInit, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } assert.IsType(t, NegTokenResp{}, nt, "Not the expected type NegTokenResp") assert.False(t, isInit, "Boolean indicating type is negTokenInit is not false") nResp := nt.(NegTokenResp) assert.Equal(t, asn1.Enumerated(0), nResp.NegState) assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, nResp.SupportedMech, "SupportedMech type not as expected.") } func TestMarshal_negTokenResp(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } _, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } nResp := nt.(NegTokenResp) mb, err := nResp.Marshal() if err != nil { t.Fatalf("Error marshalling negotiation init token: %v", err) } assert.Equal(t, b, mb, "Marshalled bytes not as expected for NegTokenResp") } gokrb5-8.4.3/spnego/spnego.go000066400000000000000000000136441427031340300160410ustar00rootroot00000000000000// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication. package spnego import ( "context" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/client" "gopkg.in/jcmturner/gokrb5.v7/gssapi" "gopkg.in/jcmturner/gokrb5.v7/keytab" "gopkg.in/jcmturner/gokrb5.v7/service" ) // SPNEGO implements the GSS-API mechanism for RFC 4178 type SPNEGO struct { serviceSettings *service.Settings client *client.Client spn string } // SPNEGOClient configures the SPNEGO mechanism suitable for client side use. func SPNEGOClient(cl *client.Client, spn string) *SPNEGO { s := new(SPNEGO) s.client = cl s.spn = spn s.serviceSettings = service.NewSettings(nil, service.SName(spn)) return s } // SPNEGOService configures the SPNEGO mechanism suitable for service side use. func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO { s := new(SPNEGO) s.serviceSettings = service.NewSettings(kt, options...) return s } // OID returns the GSS-API assigned OID for SPNEGO. func (s *SPNEGO) OID() asn1.ObjectIdentifier { return gssapi.OID(gssapi.OIDSPNEGO) } // AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO. func (s *SPNEGO) AcquireCred() error { return s.client.AffirmLogin() } // InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos. func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) { tkt, key, err := s.client.GetServiceTicket(s.spn) if err != nil { return &SPNEGOToken{}, err } negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key) if err != nil { return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err) } return &SPNEGOToken{ Init: true, NegTokenInit: negTokenInit, settings: s.serviceSettings, }, nil } // AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and // establish a context. func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) { var ctx context.Context t, ok := ct.(*SPNEGOToken) if !ok { return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"} } t.settings = s.serviceSettings var oid asn1.ObjectIdentifier if t.Init { oid = t.NegTokenInit.MechTypes[0] } if t.Resp { oid = t.NegTokenResp.SupportedMech } if !(oid.Equal(gssapi.OID(gssapi.OIDKRB5)) || oid.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5))) { return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"} } // Flags in the NegInit must be used t.NegTokenInit.ReqFlags ok, status := t.Verify() ctx = t.Context() return ok, ctx, status } // Log will write to the service's logger if it is configured. func (s *SPNEGO) Log(format string, v ...interface{}) { if s.serviceSettings.Logger() != nil { s.serviceSettings.Logger().Printf(format, v...) } } // SPNEGOToken is a GSS-API context token type SPNEGOToken struct { Init bool Resp bool NegTokenInit NegTokenInit NegTokenResp NegTokenResp settings *service.Settings context context.Context } // Marshal SPNEGO context token func (s *SPNEGOToken) Marshal() ([]byte, error) { var b []byte if s.Init { hb, _ := asn1.Marshal(gssapi.OID(gssapi.OIDSPNEGO)) tb, err := s.NegTokenInit.Marshal() if err != nil { return b, fmt.Errorf("could not marshal NegTokenInit: %v", err) } b = append(hb, tb...) return asn1tools.AddASNAppTag(b, 0), nil } if s.Resp { b, err := s.NegTokenResp.Marshal() if err != nil { return b, fmt.Errorf("could not marshal NegTokenResp: %v", err) } return b, nil } return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp") } // Unmarshal SPNEGO context token func (s *SPNEGOToken) Unmarshal(b []byte) error { var r []byte var err error // We need some data in the array if len(b) < 1 { return fmt.Errorf("provided byte array is empty") } if b[0] != byte(161) { // Not a NegTokenResp/Targ could be a NegTokenInit var oid asn1.ObjectIdentifier r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) if err != nil { return fmt.Errorf("not a valid SPNEGO token: %v", err) } // Check the OID is the SPNEGO OID value SPNEGOOID := gssapi.OID(gssapi.OIDSPNEGO) if !oid.Equal(SPNEGOOID) { return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String()) } } else { // Could be a NegTokenResp/Targ r = b } _, nt, err := UnmarshalNegToken(r) if err != nil { return err } switch v := nt.(type) { case NegTokenInit: s.Init = true s.NegTokenInit = v s.NegTokenInit.settings = s.settings case NegTokenResp: s.Resp = true s.NegTokenResp = v s.NegTokenResp.settings = s.settings default: return errors.New("unknown choice type for NegotiationToken") } return nil } // Verify the SPNEGOToken func (s *SPNEGOToken) Verify() (bool, gssapi.Status) { if (!s.Init && !s.Resp) || (s.Init && s.Resp) { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"} } if s.Init { s.NegTokenInit.settings = s.settings ok, status := s.NegTokenInit.Verify() if ok { s.context = s.NegTokenInit.Context() } return ok, status } if s.Resp { s.NegTokenResp.settings = s.settings ok, status := s.NegTokenResp.Verify() if ok { s.context = s.NegTokenResp.Context() } return ok, status } // should not be possible to get here return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"} } // Context returns the SPNEGO context which will contain any verify user identity information. func (s *SPNEGOToken) Context() context.Context { return s.context } gokrb5-8.4.3/spnego/spnego_test.go000066400000000000000000000112371427031340300170740ustar00rootroot00000000000000package spnego import ( "encoding/hex" "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" ) const ( testGSSAPIInit = "608202b606062b0601050502a08202aa308202a6a027302506092a864886f71201020206052b0501050206092a864882f71201020206062b0601050205a2820279048202756082027106092a864886f71201020201006e8202603082025ca003020105a10302010ea20703050000000000a38201706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020103a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020102a282011904820115d4bd890abc456f44e2e7a2e8111bd6767abf03266dfcda97c629af2ece450a5ae1f145e4a4d1bc2c848e66a6c6b31d9740b26b03cdbd2570bfcf126e90adf5f5ebce9e283ff5086da47b129b14fc0aabd4d1df9c1f3c72b80cc614dfc28783450b2c7b7749651f432b47aaa2ff158c0066b757f3fb00dd7b4f63d68276c76373ecdd3f19c66ebc43a81e577f3c263b878356f57e8d6c4eccd587b81538e70392cf7e73fc12a6f7c537a894a7bb5566c83ac4d69757aa320a51d8d690017aebf952add1889adfc3307b0e6cd8c9b57cf8589fbe52800acb6461c25473d49faa1bdceb8bce3f61db23f9cd6a09d5adceb411e1c4546b30b33331e570fd6bc50aa403557e75f488e759750ea038aab6454667d9b64f41a481d23081cfa003020112a281c70481c4eb593beb5afcb1a2a669d54cb85a3772231559f2d40c9f8f053f218ba6eb084ed7efc467d94b88bcd189dda920d6e675ec001a6a2bca11f0a1de37f2f7ae9929f94a86d625b2ec1b213a88cbae6099dda7b172cd3bd1802cb177ae4554d59277004bfd3435248f55044fe7af7b2c9c5a3c43763278c585395aebe2856cdff9f2569d8b823564ce6be2d19748b910ec06bd3c0a9bc5de51ddcf7d875f1108ca6ad935f52d90cb62a18197d9b8e796bef0fbe1463f61df61cfbce6008ae9e1a2d2314a986d" testGSSAPIResp = "a1143012a0030a0100a10b06092a864886f712010202" ) func TestUnmarshal_SPNEGO_Init(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenInit: %v", err) } assert.True(t, s.Init, "SPNEGO does not indicate it contains NegTokenInit as expected") assert.False(t, s.Resp, "SPNEGO indicates is contains a NegTokenResp but it shouldn't") assert.Equal(t, 4, len(s.NegTokenInit.MechTypes)) expectMechTypes := []asn1.ObjectIdentifier{ []int{1, 2, 840, 113554, 1, 2, 2}, []int{1, 3, 5, 1, 5, 2}, []int{1, 2, 840, 48018, 1, 2, 2}, []int{1, 3, 6, 1, 5, 2, 5}, } assert.Equal(t, expectMechTypes, s.NegTokenInit.MechTypes, "MechTypes list in NegTokenInit not as expected") assert.NotZero(t, len(s.NegTokenInit.MechTokenBytes), "MechToken is zero in length") } func TestUnMarshal_SPNEGO_Empty(t *testing.T) { sp := new(SPNEGOToken) // The following tests are intended to ensure we don't panic. if err := sp.Unmarshal(nil); err == nil { t.Fatal("should have errored, input is absent") } if err := sp.Unmarshal([]byte{}); err == nil { t.Fatal("should have errored, input is empty") } if err := sp.Unmarshal([]byte{1}); err == nil { t.Fatal("should have errored, input is too low") } } func TestUnmarshal_SPNEGO_RespTarg(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenResp/NegTokenTarg: %v", err) } assert.True(t, s.Resp, "SPNEGO does not indicate it contains NegTokenResp/Targ as expected") assert.False(t, s.Init, "SPNEGO indicates is contains a NegTokenInit but it shouldn't") assert.Equal(t, asn1.Enumerated(0), s.NegTokenResp.NegState, "Negotiation state not as expected.") assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, s.NegTokenResp.SupportedMech, "SupportedMech type not as expected.") } func TestMarshal_SPNEGO_Init(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenInit: %v", err) } mb, err := s.Marshal() if err != nil { t.Fatalf("Error marshalling SPNEGO containing NegTokenInit: %v", err) } assert.Equal(t, b, mb, "Marshaled bytes not as expected") } func TestMarshal_SPNEGO_RespTarg(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenResp: %v", err) } mb, err := s.Marshal() if err != nil { t.Fatalf("Error marshalling SPNEGO containing NegTokenResp: %v", err) } assert.Equal(t, b, mb, "Marshaled bytes not as expected") } gokrb5-8.4.3/test/000077500000000000000000000000001427031340300136735ustar00rootroot00000000000000gokrb5-8.4.3/test/README.md000066400000000000000000000001411427031340300151460ustar00rootroot00000000000000Source for integration test dependencies can be found at https://github.com/jcmturner/gokrb5-testgokrb5-8.4.3/test/test.go000066400000000000000000000015431427031340300152040ustar00rootroot00000000000000// Package test provides useful resources for the testing of gokrb5. package test import ( "os" "testing" ) // Test enabling environment variable key values. const ( IntegrationEnvVar = "INTEGRATION" ADIntegrationEnvVar = "TESTAD" PrivIntegrationEnvVar = "TESTPRIVILEGED" ) // Integration skips the test unless the integration test environment variable is set. func Integration(t *testing.T) { if os.Getenv(IntegrationEnvVar) != "1" { t.Skip("Skipping integration test") } } // AD skips the test unless the AD test environment variable is set. func AD(t *testing.T) { if os.Getenv(ADIntegrationEnvVar) != "1" { t.Skip("Skipping AD integration test") } } // Privileged skips the test that require local root privilege. func Privileged(t *testing.T) { if os.Getenv(PrivIntegrationEnvVar) != "1" { t.Skip("Skipping DNS integration test") } } gokrb5-8.4.3/test/testdata/000077500000000000000000000000001427031340300155045ustar00rootroot00000000000000gokrb5-8.4.3/test/testdata/test_vectors.go000066400000000000000000002073021427031340300205630ustar00rootroot00000000000000// Package testdata provides Kerberos 5 test reference data. package testdata var TEST_PRINCIPALNAME_NAMESTRING = []string{"hftsai", "extra"} const ( //Expected unmarshaled values TEST_REALM = "ATHENA.MIT.EDU" TEST_CIPHERTEXT = "krbASN.1 test message" TEST_TIME_FORMAT = "20060102150405" TEST_TIME = "19940610060317" TEST_ETYPE int32 = 0 TEST_NONCE = 42 TEST_AUTHORIZATION_DATA_VALUE = "foobar" TEST_PADATA_VALUE = "pa-data" //The test vectors have been sourced from https://github.com/krb5/krb5/blob/master/src/tests/asn.1/reference_encode.out MarshaledKRB5authenticator = "6281A130819EA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A30F300DA003020101A106040431323334A405020301E240A511180F31393934303631303036303331375AA6133011A003020101A10A04083132333435363738A703020111A8243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5authenticatorOptionalsEmpty = "624F304DA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A405020301E240A511180F31393934303631303036303331375A" MarshaledKRB5authenticatorOptionalsNULL = "624F304DA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A405020301E240A511180F31393934303631303036303331375A" MarshaledKRB5ticket = "615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5keyblock = "3011A003020101A10A04083132333435363738" MarshaledKRB5enc_tkt_part = "6382011430820110A007030500FEDCBA98A1133011A003020101A10A04083132333435363738A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A42E302CA003020101A12504234544552C4D49542E2C415448454E412E2C57415348494E47544F4E2E4544552C43532EA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA811180F31393934303631303036303331375AA920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5enc_tkt_partOptionalsNULL = "6381A53081A2A007030500FEDCBA98A1133011A003020101A10A04083132333435363738A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A42E302CA003020101A12504234544552C4D49542E2C415448454E412E2C57415348494E47544F4E2E4544552C43532EA511180F31393934303631303036303331375AA711180F31393934303631303036303331375A" MarshaledKRB5enc_kdc_rep_part = "7A82010E3082010AA0133011A003020101A10A04083132333435363738A13630343018A0030201FBA111180F31393934303631303036303331375A3018A0030201FBA111180F31393934303631303036303331375AA20302012AA311180F31393934303631303036303331375AA407030500FEDCBA98A511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA811180F31393934303631303036303331375AA9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261AB20301E300DA003020102A106040412D00023300DA003020102A106040412D00023" MarshaledKRB5enc_kdc_rep_partOptionalsNULL = "7A81B23081AFA0133011A003020101A10A04083132333435363738A13630343018A0030201FBA111180F31393934303631303036303331375A3018A0030201FBA111180F31393934303631303036303331375AA20302012AA407030500FE5CBA98A511180F31393934303631303036303331375AA711180F31393934303631303036303331375AA9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261" MarshaledKRB5as_rep = "6B81EA3081E7A003020105A10302010BA22630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5as_repOptionalsNULL = "6B81C23081BFA003020105A10302010BA3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_rep = "6D81EA3081E7A003020105A10302010DA22630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_repOptionalsNULL = "6D81C23081BFA003020105A10302010DA3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5ap_req = "6E819D30819AA003020105A10302010EA207030500FEDCBA98A35E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A4253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5ap_rep = "6F333031A003020105A10302010FA2253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5ap_rep_enc_part = "7B363034A011180F31393934303631303036303331375AA105020301E240A2133011A003020101A10A04083132333435363738A303020111" MarshaledKRB5ap_rep_enc_partOptionalsNULL = "7B1C301AA011180F31393934303631303036303331375AA105020301E240" MarshaledKRB5as_req = "6A8201E4308201E0A103020105A20302010AA32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A48201AA308201A6A007030500FEDCBA90A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5as_reqOptionalsNULLexceptsecond_ticket = "6A82011430820110A103020105A20302010AA48201023081FFA007030500FEDCBA98A2101B0E415448454E412E4D49542E454455A511180F31393934303631303036303331375AA70302012AA8083006020100020101AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5as_reqOptionalsNULLexceptserver = "6A693067A103020105A20302010AA45B3059A007030500FEDCBA90A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A511180F31393934303631303036303331375AA70302012AA8083006020100020101" MarshaledKRB5tgs_req = "6C8201E4308201E0A103020105A20302010CA32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A48201AA308201A6A007030500FEDCBA90A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_reqOptionalsNULLexceptsecond_ticket = "6C82011430820110A103020105A20302010CA48201023081FFA007030500FEDCBA98A2101B0E415448454E412E4D49542E454455A511180F31393934303631303036303331375AA70302012AA8083006020100020101AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_reqOptionalsNULLexceptserver = "6C693067A103020105A20302010CA45B3059A007030500FEDCBA90A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A511180F31393934303631303036303331375AA70302012AA8083006020100020101" MarshaledKRB5kdc_req_body = "308201A6A007030500FEDCBA90A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5kdc_req_bodyOptionalsNULLexceptsecond_ticket = "3081FFA007030500FEDCBA98A2101B0E415448454E412E4D49542E454455A511180F31393934303631303036303331375AA70302012AA8083006020100020101AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5kdc_req_bodyOptionalsNULLexceptserver = "3059A007030500FEDCBA90A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A511180F31393934303631303036303331375AA70302012AA8083006020100020101" MarshaledKRB5safe = "746E306CA003020105A103020114A24F304DA00A04086B72623564617461A111180F31393934303631303036303331375AA205020301E240A303020111A40F300DA003020102A106040412D00023A50F300DA003020102A106040412D00023A30F300DA003020101A106040431323334" MarshaledKRB5safeOptionalsNULL = "743E303CA003020105A103020114A21F301DA00A04086B72623564617461A40F300DA003020102A106040412D00023A30F300DA003020101A106040431323334" MarshaledKRB5priv = "75333031A003020105A103020115A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_priv_part = "7C4F304DA00A04086B72623564617461A111180F31393934303631303036303331375AA205020301E240A303020111A40F300DA003020102A106040412D00023A50F300DA003020102A106040412D00023" MarshaledKRB5enc_priv_partOptionalsNULL = "7C1F301DA00A04086B72623564617461A40F300DA003020102A106040412D00023" MarshaledKRB5cred = "7681F63081F3A003020105A103020116A281BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_cred_part = "7D8202233082021FA08201DA308201D63081E8A0133011A003020101A10A04083132333435363738A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A307030500FEDCBA98A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA8101B0E415448454E412E4D49542E454455A91A3018A003020101A111300F1B066866747361691B056578747261AA20301E300DA003020102A106040412D00023300DA003020102A106040412D000233081E8A0133011A003020101A10A04083132333435363738A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A307030500FEDCBA98A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA8101B0E415448454E412E4D49542E454455A91A3018A003020101A111300F1B066866747361691B056578747261AA20301E300DA003020102A106040412D00023300DA003020102A106040412D00023A10302012AA211180F31393934303631303036303331375AA305020301E240A40F300DA003020102A106040412D00023A50F300DA003020102A106040412D00023" MarshaledKRB5enc_cred_partOptionalsNULL = "7D82010E3082010AA0820106308201023015A0133011A003020101A10A040831323334353637383081E8A0133011A003020101A10A04083132333435363738A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A307030500FEDCBA98A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA8101B0E415448454E412E4D49542E454455A91A3018A003020101A111300F1B066866747361691B056578747261AA20301E300DA003020102A106040412D00023300DA003020102A106040412D00023" MarshaledKRB5error = "7E81BA3081B7A003020105A10302011EA211180F31393934303631303036303331375AA305020301E240A411180F31393934303631303036303331375AA505020301E240A60302013CA7101B0E415448454E412E4D49542E454455A81A3018A003020101A111300F1B066866747361691B056578747261A9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261AB0A1B086B72623564617461AC0A04086B72623564617461" MarshaledKRB5errorOptionalsNULL = "7E60305EA003020105A10302011EA305020301E240A411180F31393934303631303036303331375AA505020301E240A60302013CA9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261" MarshaledKRB5authorization_data = "3022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5padata_sequence = "30243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461" MarshaledKRB5typed_data = "30243010A00302010DA109040770612D646174613010A00302010DA109040770612D64617461" MarshaledKRB5padataSequenceEmpty = "3000" MarshaledKRB5etype_info = "30333014A003020100A10D040B4D6F72746F6E27732023303005A0030201013014A003020102A10D040B4D6F72746F6E2773202332" MarshaledKRB5etype_infoOnly1 = "30163014A003020100A10D040B4D6F72746F6E2773202330" MarshaledKRB5etype_infoNoInfo = "3000" MarshaledKRB5etype_info2 = "3051301EA003020100A10D1B0B4D6F72746F6E2773202330A208040673326B3A2030300FA003020101A208040673326B3A2031301EA003020102A10D1B0B4D6F72746F6E2773202332A208040673326B3A2032" MarshaledKRB5etype_info2Only1 = "3020301EA003020100A10D1B0B4D6F72746F6E2773202330A208040673326B3A2030" MarshaledKRB5pa_enc_ts = "301AA011180F31393934303631303036303331375AA105020301E240" MarshaledKRB5pa_enc_tsNoUsec = "3013A011180F31393934303631303036303331375A" MarshaledKRB5enc_data = "3023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_dataMSBSetkvno = "3026A003020100A1060204FF000000A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_dataKVNONegOne = "3023A003020100A1030201FFA21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5sam_challenge_2 = "3022A00D300B04096368616C6C656E6765A111300F300DA003020101A106040431323334" MarshaledKRB5sam_challenge_2_body = "3064A00302012AA10703050080000000A20B040974797065206E616D65A411040F6368616C6C656E6765206C6162656CA510040E6368616C6C656E67652069707365A6160414726573706F6E73655F70726F6D70742069707365A8050203543210A903020101" MarshaledKRB5sam_response_2 = "3042A00302012BA10703050080000000A20C040A747261636B2064617461A31D301BA003020101A10402020D36A20E040C6E6F6E6365206F7220736164A4050203543210" MarshaledKRB5enc_sam_response_enc_2 = "301FA003020158A1180416656E635F73616D5F726573706F6E73655F656E635F32" MarshaledKRB5pa_for_user = "304BA01A3018A003020101A111300F1B066866747361691B056578747261A1101B0E415448454E412E4D49542E454455A20F300DA003020101A106040431323334A30A1B086B72623564617461" MarshaledKRB5pa_s4u_x509_user = "3068A0553053A006020400CA149AA11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A312041070615F7334755F783530395F75736572A40703050080000000A10F300DA003020101A106040431323334" MarshaledKRB5ad_kdcissued = "3065A00F300DA003020101A106040431323334A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5ad_signedpath_data = "3081C7A030302EA01A3018A003020101A111300F1B066866747361691B056578747261A1101B0E415448454E412E4D49542E454455A111180F31393934303631303036303331375AA2323030302EA01A3018A003020101A111300F1B066866747361691B056578747261A1101B0E415448454E412E4D49542E454455A32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A4243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5ad_signedpath = "303EA003020101A10F300DA003020101A106040431323334A32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461" MarshaledKRB5iakerb_header = "3018A10A04086B72623564617461A20A04086B72623564617461" MarshaledKRB5iakerb_finished = "3011A10F300DA003020101A106040431323334" MarshaledKRB5fast_response = "30819FA02630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A1133011A003020101A10A04083132333435363738A25B3059A011180F31393934303631303036303331375AA105020301E240A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A40F300DA003020101A106040431323334A30302012A" MarshaledKRB5pa_fx_fast_reply = "A0293027A0253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5otp_tokeninfoOptionalsNULL = "300780050000000000" MarshaledKRB5otp_tokeninfo = "307280050077000000810B4578616D706C65636F727082056861726B2183010A8401028509796F7572746F6B656E862875726E3A696574663A706172616D733A786D6C3A6E733A6B657970726F763A70736B633A686F7470A716300B0609608648016503040201300706052B0E03021A880203E8" MarshaledKRB5pa_otp_challengeOptionalsNULL = "301580086D696E6E6F6E6365A209300780050000000000" MarshaledKRB5pa_otp_challenge = "3081A580086D61786E6F6E6365810B7465737473657276696365A27D300780050000000000307280050077000000810B4578616D706C65636F727082056861726B2183010A8401028509796F7572746F6B656E862875726E3A696574663A706172616D733A786D6C3A6E733A6B657970726F763A70736B633A686F7470A716300B0609608648016503040201300706052B0E03021A880203E883076B657973616C74840431323334" MarshaledKRB5pa_otp_reqOptionalsNULL = "302C80050000000000A223A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5pa_otp_req = "3081B98005006000000081056E6F6E6365A223A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A30B0609608648016503040201840203E8850566726F6773860A6D79666972737470696E87056861726B21880F31393934303631303036303331375A89033334368A01028B09796F7572746F6B656E8C2875726E3A696574663A706172616D733A786D6C3A6E733A6B657970726F763A70736B633A686F74708D0B4578616D706C65636F7270" MarshaledKRB5pa_otp_enc_req = "300A80086B72623564617461" MarshaledKRB5kkdcp_message = "308201FCA08201EC048201E86A8201E4308201E0A103020105A20302010AA32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A48201AA308201A6A007030500FEDCBA98A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A10A1B086B72623564617461" MarshaledKRB5cammacOptionalsNULL = "3012A010300E300CA003020101A1050403616431" MarshaledKRB5cammac = "3081F2A01E301C300CA003020101A1050403616431300CA003020102A1050403616432A13D303BA01A3018A003020101A111300F1B066866747361691B056578747261A103020105A203020110A3133011A003020101A10A0408636B73756D6B6463A23D303BA01A3018A003020101A111300F1B066866747361691B056578747261A103020105A203020110A3133011A003020101A10A0408636B73756D737663A35230503013A311300FA003020101A1080406636B73756D313039A01A3018A003020101A111300F1B066866747361691B056578747261A103020105A203020110A311300FA003020101A1080406636B73756D32" MarshaledKRB5secure_cookie = "302C02042DF8022530243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461" //MS PAC Vectors MarshaledPAC_AuthorizationData_MS = "308205523082054ea00402020080a182054404820540040000000000000001000000b004000048000000000000000a00000012000000f804000000000000060000001400000010050000000000000700000014000000280500000000000001100800cccccccca00400000000000000000200d186660f656ac601ffffffffffffff7fffffffffffffff7f17d439fe784ac6011794a328424bc601175424977a81c60108000800040002002400240008000200120012000c0002000000000010000200000000001400020000000000180002005410000097792c00010200001a0000001c000200200000000000000000000000000000000000000016001800200002000a000c002400020028000200000000000000000010000000000000000000000000000000000000000000000000000000000000000d0000002c0002000000000000000000000000000400000000000000040000006c007a00680075001200000000000000120000004c0069007100690061006e00670028004c006100720072007900290020005a00680075000900000000000000090000006e0074006400730032002e0062006100740000000000000000000000000000000000000000000000000000000000000000000000000000001a00000061c433000700000009c32d00070000005eb4320007000000010200000700000097b92c00070000002bf1320007000000ce30330007000000a72e2e00070000002af132000700000098b92c000700000062c4330007000000940133000700000076c4330007000000aefe2d000700000032d22c00070000001608320007000000425b2e00070000005fb4320007000000ca9c35000700000085442d0007000000c2f0320007000000e9ea310007000000ed8e2e0007000000b6eb310007000000ab2e2e0007000000720e2e00070000000c000000000000000b0000004e0054004400450056002d00440043002d003000350000000600000000000000050000004e0054004400450056000000040000000104000000000005150000005951b81766725d2564633b0b0d0000003000020007000000340002000700002038000200070000203c000200070000204000020007000020440002000700002048000200070000204c000200070000205000020007000020540002000700002058000200070000205c00020007000020600002000700002005000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000050000000105000000000005150000005951b81766725d2564633b0b74542f00050000000105000000000005150000005951b81766725d2564633b0be8383200050000000105000000000005150000005951b81766725d2564633b0bcd383200050000000105000000000005150000005951b81766725d2564633b0b5db43200050000000105000000000005150000005951b81766725d2564633b0b41163500050000000105000000000005150000005951b81766725d2564633b0be8ea3100050000000105000000000005150000005951b81766725d2564633b0bc1193200050000000105000000000005150000005951b81766725d2564633b0b29f13200050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00050000000105000000000005150000005951b81766725d2564633b0bef8f3100050000000105000000000005150000005951b81766725d2564633b0b075f2e00000000000049d90e656ac60108006c007a006800750000000000000076ffffff41edce9a34815d3aef7bc98874805d250000000076fffffff7a534dab2c02986efe0fbe5110a4f3200000000" MarshaledPAC_Kerb_Validation_Info_MS = "01100800cccccccca00400000000000000000200d186660f656ac601ffffffffffffff7fffffffffffffff7f17d439fe784ac6011794a328424bc601175424977a81c60108000800040002002400240008000200120012000c0002000000000010000200000000001400020000000000180002005410000097792c00010200001a0000001c000200200000000000000000000000000000000000000016001800200002000a000c002400020028000200000000000000000010000000000000000000000000000000000000000000000000000000000000000d0000002c0002000000000000000000000000000400000000000000040000006c007a00680075001200000000000000120000004c0069007100690061006e00670028004c006100720072007900290020005a00680075000900000000000000090000006e0074006400730032002e0062006100740000000000000000000000000000000000000000000000000000000000000000000000000000001a00000061c433000700000009c32d00070000005eb4320007000000010200000700000097b92c00070000002bf1320007000000ce30330007000000a72e2e00070000002af132000700000098b92c000700000062c4330007000000940133000700000076c4330007000000aefe2d000700000032d22c00070000001608320007000000425b2e00070000005fb4320007000000ca9c35000700000085442d0007000000c2f0320007000000e9ea310007000000ed8e2e0007000000b6eb310007000000ab2e2e0007000000720e2e00070000000c000000000000000b0000004e0054004400450056002d00440043002d003000350000000600000000000000050000004e0054004400450056000000040000000104000000000005150000005951b81766725d2564633b0b0d0000003000020007000000340002000700002038000200070000203c000200070000204000020007000020440002000700002048000200070000204c000200070000205000020007000020540002000700002058000200070000205c00020007000020600002000700002005000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000050000000105000000000005150000005951b81766725d2564633b0b74542f00050000000105000000000005150000005951b81766725d2564633b0be8383200050000000105000000000005150000005951b81766725d2564633b0bcd383200050000000105000000000005150000005951b81766725d2564633b0b5db43200050000000105000000000005150000005951b81766725d2564633b0b41163500050000000105000000000005150000005951b81766725d2564633b0be8ea3100050000000105000000000005150000005951b81766725d2564633b0bc1193200050000000105000000000005150000005951b81766725d2564633b0b29f13200050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00050000000105000000000005150000005951b81766725d2564633b0bef8f3100050000000105000000000005150000005951b81766725d2564633b0b075f2e0000000000" MarshaledPAC_AuthorizationData_GOKRB5 = "3082034730820343a003020101a182033a04820336308203323082032ea00402020080a1820324048203200500000000000000010000002802000058000000000000000a0000001c00000080020000000000000c00000058000000a0020000000000000600000010000000f8020000000000000700000014000000080300000000000001100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000808dd1dc80c6d2011200740065007300740075007300650072003100000000002a001000160040000000000000000000740065007300740075007300650072003100400074006500730074002e0067006f006b0072006200350000000000000054004500530054002e0047004f004b005200420035000000100000001e251d98d552be7df384f55076ffffff340be28b48765d0519ee9346cf53d82200000000" MarshaledPAC_AD_WIN2K_PAC = "0500000000000000010000002802000058000000000000000a0000001c00000080020000000000000c00000058000000a0020000000000000600000010000000f8020000000000000700000014000000080300000000000001100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000808dd1dc80c6d2011200740065007300740075007300650072003100000000002a001000160040000000000000000000740065007300740075007300650072003100400074006500730074002e0067006f006b0072006200350000000000000054004500530054002e0047004f004b005200420035000000100000001e251d98d552be7df384f55076ffffff340be28b48765d0519ee9346cf53d82200000000" MarshaledPAC_Kerb_Validation_Info = "01100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000" MarshaledPAC_Client_Info = "808dd1dc80c6d2011200740065007300740075007300650072003100" MarshaledPAC_UPN_DNS_Info = "2a001000160040000000000000000000740065007300740075007300650072003100400074006500730074002e0067006f006b0072006200350000000000000054004500530054002e0047004f004b005200420035000000" MarshaledPAC_Server_Signature = "100000001e251d98d552be7df384f550" MarshaledPAC_KDC_Signature = "76ffffff340be28b48765d0519ee9346cf53d822" MarshaledPAC_Kerb_Validation_Info_Trust = "01100800cccccccc000200000000000000000200c30bcc79e444d301ffffffffffffff7fffffffffffffff7fc764125a0842d301c7247c84d142d301ffffffffffffff7f12001200040002001600160008000200000000000c0002000000000010000200000000001400020000000000180002002e0000005204000001020000030000001c0002002002000000000000000000000000000000000000060008002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000010000002c00020034000200020000003800020009000000000000000900000074006500730074007500730065007200310000000b000000000000000b0000005400650073007400310020005500730065007200310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000056040000070000000102000007000000550400000700000004000000000000000300000055004400430000000500000000000000040000005500530045005200040000000104000000000005150000002057308834e7d1d0a2fb0444010000003000020007000000010000000101000000000012010000000400000001040000000000051500000062dc8db6c8705249b5459e75020000005304000007000020540400000700002000000000" MarshaledPAC_ClientClaimsInfoStr = "01100800cccccccc000100000000000000000200d80000000400020000000000d8000000000000000000000000000000d800000001100800ccccccccc80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000100000010000200290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a0038003800640035006400390030003800350065006100350063003000630030000000000001000000140002000a000000000000000a00000074006500730074007500730065007200310000000000000000000000" MarshaledPAC_ClientClaimsInfoInt = "01100800cccccccce00000000000000000000200b80000000400020000000000b8000000000000000000000000000000b800000001100800cccccccca80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c0002000100010001000000100002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c0000000000000000000000" MarshaledPAC_ClientClaimsInfoMulti = "01100800cccccccc780100000000000000000200500100000400020000000000500100000000000000000000000000005001000001100800cccccccc400100000000000000000200010000000400020000000000000000000000000001000000010000000200000008000200020000000c000200010001000100000010000200140002000300030001000000180002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c00000000000000290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a00380038006400350064003900300038003500650061003500630030006300300000000000010000001c0002000a000000000000000a000000740065007300740075007300650072003100000000000000" MarshaledPAC_ClientClaimsInfoMultiUint = "01100800ccccccccf00000000000000000000200c80000000400020000000000c8000000000000000000000000000000c800000001100800ccccccccb80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200020002000400000010000200260000000000000026000000610064003a002f002f006500780074002f006f0062006a0065006300740043006c006100730073003a00380038006400350064006500370039003100650037006200320037006500360000000400000009000a000000000007000100000000000600010000000000000001000000000000000000" MarshaledPAC_ClientClaimsInfoMultiStr = "01100800cccccccc480100000000000000000200200100000400020000000000200100000000000000000000000000002001000001100800cccccccc100100000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000400000010000200270000000000000027000000610064003a002f002f006500780074002f006f00740068006500720049007000500068006f006e0065003a003800380064003500640065003900660036006200340061006600390038003500000000000400000014000200180002001c000200200002000500000000000000050000007300740072003100000000000500000000000000050000007300740072003200000000000500000000000000050000007300740072003300000000000500000000000000050000007300740072003400000000000000000000000000" MarshaledPAC_ClientClaimsInfo_XPRESS_HUFF = "01100800ccccccccd00100000000000000000200a80100000400020004000000e0010000000000000000000000000000a8010000727807888708080007000800080008000800080880000080870870887807000080800000000080080000080000000000605767070007777707677700770000000000000000000000000000000000000000000000000000000000000000000000000000000000070007000000000000000000000000000000000000000000000076000700700000007600000000000000750700000000000064770700000000007607000000000000060700000000000077060700000000707770700070000770007700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a85652950bb9d8bae030b2212b90df95764d1b182da22f2c848b23b3cc4efc8e3499701e481cf938e490986a384c3d572250aaab2446572fc26be279c263e4a4c9c2c24f9649e2444d8ddb3277373c600363beb73200baaa783da183dd85830af863e1a00d5cf718aac4879519fbf0745bcc59214493a330f940bf99a446f1ade6df2610c5f154b432eaba964d7ad1f1182e522019fc21ce498a204d06b96a476f7386e6003000000000000" MarshaledChangePasswdData = "3036a00d040b6e657770617373776f7264a1163014a003020101a10d300b1b09746573747573657231a20d1b0b544553542e474f4b524235" MarshaledKpasswd_Req = "037aff8002c16e8202bd308202b9a003020105a10302010ea20703050000000000a38201f1618201ed308201e9a003020105a10d1b0b544553542e474f4b524235a21d301ba003020101a11430121b066b61646d696e1b086368616e67657077a38201b2308201aea003020111a103020101a28201a00482019ca3de94df50e8e9fe7a8c9386f594f469bf08874407fc7b95ddcf22110ef63e62ff0ba3c31c3bb725dc1dde1f4c2f69a4973b4b43c9b4b31f71f676d5e8e7b4d7906b1dfacc9897d865b17f934fb96b802344463bb0746fdd39e9e48ff1b2665dc895a74d3d3aac89512b43bd8ead8f455b9b819cc6f6a34fb7c5975d7c2dbd4349524961215b98f33f5747f1e0c89f3b3637462308953940741ab7fc38ae817ba85800dd911bb78b42264f2d285c2a0a33ca21c1a3d281ec14614010db31c3e3f4d4622b799f97b3d31c4445411278fec62dd8e6e349db280aaa4419b53ef6fbc01f0206bcfea2cbe835b46764c03c138722e54dab53a1080e5d6c99f8cd7a948880677176cfc2d3800f9ef64d1ec4f8bdadc1ae409990c4855a82e265682e8ddaa6dea70a1d7855f3e1e766f5efe428dd6da71c585f5d17d8f81e8f2a4f4b2245f5ff2cc444a2a1ae5d16a15d588597219d5659da537f752ca9b572b635088b325b60e8e62fd99487872261f41dcc466516b89992d277bb8b3a1ca770671fca36dd33c3dd6dab643e6710280661029254054273151ccfca9aaddc55a481ae3081aba003020112a103020101a2819e04819be66387f971d751d7d3ebb6acd815a0991e0ed9f07e2643783e7961fb88127b31f767bf00d1d071a81858b101f4d45460412d8013228f942bc51891e95a06aefa8cedd95e5a3e6e65597c0f05c19ee54dc6dc00b1a3f9d7a95516b5e447c40cd5b462ed6b17a007670311efa44dbe939cab11072b9af1443c3203767bb1a3240542db06dffcaebcedd5c335bb295127bc0e6d99f2c1e87f68de1f547581b03081ada003020105a103020115a381a030819da003020112a103020101a2819004818df272b2726c8f31c578f3b4275bc283828716010a20f0c4369bff474fcf202537060a71edcbe8ba720d0d9b2bac26b58353dc5b2945570374928a819eb3526362eda328e704f1a5ebe3272eed0fa6a6aa7d0f32c4fc0bd2e4ea52a8834ea7b5fb018934df87c18ab625f5c07f6c28e202e0cec63bcc37b1d381d64937998c1bdcd1585695eeffb75f8ce9e736b3" MarshaledKpasswd_Rep = "00ec0001008c6f8189308186a003020105a10302010fa27a3078a003020112a271046f57cb442fd321312aff0b2dcda70fe436812f9805611adf3403ab6cd7708604e86e77f765a8486864f0dbf8d5d065a63790370bc110ed1e3c7eae9890e02407e8a8b349703fed1e7f165e1261a822c5b3e6823c282884f59afeb9f84f2a9845994135dd307eb2f544874393c1c455d475583056a003020105a103020115a34a3048a003020112a241043fdd3edaf0b6cbcab5b663189bafc0a19e6cc03b3c59d989c403735748ebc36088bad852add0f62581eed515fc1f297324df4fa12cb94b7ad5db257165369db5" TESTUSER1_PASSWORD = "passwordvalue" // AD Integration Test Environment KEYTAB_TESTUSER1_USER_GOKRB5 = "05020000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750100110010528b8ba0ae5131fbf71f6ddc5870cdce000000010000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c275010012002016475f89eba70e62af20a20e7bf3ca4ccad5ae22485c93ffb133650bc6f12585000000010000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750100130010a3ddea306fa06c068bc3e1fcf4b280ca000000010000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c27501001400205d2a66a8af5142db59bcaabac8310777bf60a85e8881469e2063bba4dff0c00a000000010000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750200110010528b8ba0ae5131fbf71f6ddc5870cdce000000020000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c275020012002016475f89eba70e62af20a20e7bf3ca4ccad5ae22485c93ffb133650bc6f12585000000020000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750200130010a3ddea306fa06c068bc3e1fcf4b280ca000000020000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c27502001400205d2a66a8af5142db59bcaabac8310777bf60a85e8881469e2063bba4dff0c00a00000002" KEYTAB_TESTUSER2_USER_GOKRB5 = "05020000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f30100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f30100110010a771a31fae504621fffc644a521e0cee000000010000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f301001200203262b201af7ec73c77bbee75a4ff10950cf2bda56529ead30ced4f0f0b9a591d000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f301001300104be17a4cf1761f0494475617f671fa6a000000010000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f301001400208b1c60589b6c0d78d5e8fe265e92c2babf920a6a2828c37fc343e43497ad9fab000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a0200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a0200110010a771a31fae504621fffc644a521e0cee000000020000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a02001200203262b201af7ec73c77bbee75a4ff10950cf2bda56529ead30ced4f0f0b9a591d000000020000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a02001300104be17a4cf1761f0494475617f671fa6a000000020000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a02001400208b1c60589b6c0d78d5e8fe265e92c2babf920a6a2828c37fc343e43497ad9fab00000002" KEYTAB_TESTUSER3_USER_GOKRB5 = "05020000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba950100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba95010011001025b79e18723ecd0fdf76c3a5bb21d5dd000000010000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba950100120020c98c6dcc3ee520d5712aba339b2aa1930414b24fb52b9f70bf46259a57c1740b000000010000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba95010013001007f06e524ee5d738b5bb464c876a5087000000010000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba95010014002024cb938c683c9fcbe548f2febc93f8090fbaf44541751fc2b781e453dba36a11000000010000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d0200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d020011001025b79e18723ecd0fdf76c3a5bb21d5dd000000020000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d0200120020c98c6dcc3ee520d5712aba339b2aa1930414b24fb52b9f70bf46259a57c1740b000000020000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d020013001007f06e524ee5d738b5bb464c876a5087000000020000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d020014002024cb938c683c9fcbe548f2febc93f8090fbaf44541751fc2b781e453dba36a1100000002" KEYTAB_SYSHTTP_RES_GOKRB5 = "0502000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200170010084768c373663b3bef1f6385883cf7ff00000002000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200110010c622e44a32022f4cb81775263151140d00000002000000480001000a5245532e474f4b524235000773797348545450000000015e7a7e2f02001200209da0dc4802bf5d375dfe2a77ddfc5065b3bf789126c2dc89ff4c2aa90dfa43ce00000002000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200130010541beb216c1cdf22ef7c2225066a385e00000002000000480001000a5245532e474f4b524235000773797348545450000000015e7a7e2f02001400205f0983acd70fcaee0acb7ac4a14f8ad89f3a35915914e696200370637d8fef2300000002" TESTUSER1_KEYTAB = "05020000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80100110010698c4df8e9f60e7eea5a21bf4526ad25000000010000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80100120020bbdc430aab7e2d4622a0b6951481453b0962e9db8e2f168942ad175cda6d9de9000000010000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80200110010698c4df8e9f60e7eea5a21bf4526ad25000000020000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80200120020bbdc430aab7e2d4622a0b6951481453b0962e9db8e2f168942ad175cda6d9de9000000020000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d801001300102eb8501967a7886e1f0c63ac9be8c4a0000000010000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d802001300102eb8501967a7886e1f0c63ac9be8c4a0000000020000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d801001400208ad66f209bb07daa186f8a229830f5ba06a3a2a33638f4ec66e1d29324e417ee000000010000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d802001400208ad66f209bb07daa186f8a229830f5ba06a3a2a33638f4ec66e1d29324e417ee00000002000000430001000b544553542e474f4b52423500097465737475736572310000000159beb1d801001000184580fb91760dabe6f808c22c26494f644cb35d61d32c79e300000001000000430001000b544553542e474f4b52423500097465737475736572310000000159beb1d802001000184580fb91760dabe6f808c22c26494f644cb35d61d32c79e3000000020000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80200170010084768c373663b3bef1f6385883cf7ff00000002" TESTUSER1_USERKRB5_AD_KEYTAB = "05020000003b0001000b555345522e474f4b52423500097465737475736572310000000159de7c040100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b52423500097465737475736572310000000159de7c040100110010528b8ba0ae5131fbf71f6ddc5870cdce000000010000004b0001000b555345522e474f4b52423500097465737475736572310000000159de7c04010012002016475f89eba70e62af20a20e7bf3ca4ccad5ae22485c93ffb133650bc6f12585000000010000003b0001000b555345522e474f4b52423500097465737475736572310000000159de7c040100130010a3ddea306fa06c068bc3e1fcf4b280ca000000010000004b0001000b555345522e474f4b52423500097465737475736572310000000159de7c0401001400205d2a66a8af5142db59bcaabac8310777bf60a85e8881469e2063bba4dff0c00a000000010000003b0001000b555345522e474f4b52423500097465737475736572310000000159de7c040200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b52423500097465737475736572310000000159de7c040200110010528b8ba0ae5131fbf71f6ddc5870cdce000000020000004b0001000b555345522e474f4b52423500097465737475736572310000000159de7c04020012002016475f89eba70e62af20a20e7bf3ca4ccad5ae22485c93ffb133650bc6f12585000000020000003b0001000b555345522e474f4b52423500097465737475736572310000000159de7c040200130010a3ddea306fa06c068bc3e1fcf4b280ca000000020000004b0001000b555345522e474f4b52423500097465737475736572310000000159de7c0402001400205d2a66a8af5142db59bcaabac8310777bf60a85e8881469e2063bba4dff0c00a00000002" TESTUSER2_USERKRB5_AD_KEYTAB = "05020000003b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d0100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d0100110010a771a31fae504621fffc644a521e0cee000000010000004b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d01001200203262b201af7ec73c77bbee75a4ff10950cf2bda56529ead30ced4f0f0b9a591d000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d01001300104be17a4cf1761f0494475617f671fa6a000000010000004b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d01001400208b1c60589b6c0d78d5e8fe265e92c2babf920a6a2828c37fc343e43497ad9fab000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d0200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d0200110010a771a31fae504621fffc644a521e0cee000000020000004b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d02001200203262b201af7ec73c77bbee75a4ff10950cf2bda56529ead30ced4f0f0b9a591d000000020000003b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d02001300104be17a4cf1761f0494475617f671fa6a000000020000004b0001000b555345522e474f4b5242350009746573747573657232000000015b2e4f5d02001400208b1c60589b6c0d78d5e8fe265e92c2babf920a6a2828c37fc343e43497ad9fab00000002" TESTUSER1_WRONGPASSWD = "0502000000370001000b544553542e474f4b52423500097465737475736572310000000158ef4bc5010011001039a9a382153105f8708e80f93382654e000000470001000b544553542e474f4b52423500097465737475736572310000000158ef4bc60100120020fc5bb940d6075214e0c6fc0456ce68c33306094198a927b4187d7cf3f4aea50d" TESTUSER2_KEYTAB = "05020000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb240010011001086824c55ff5de30386dd83dc62b44bb7000000010000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400100120020d8ed27f96be76fd5b281ee9f8029db93cc5fb06c7eb3be9ee753106d3488fa92000000010000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb240020011001086824c55ff5de30386dd83dc62b44bb7000000020000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400200120020d8ed27f96be76fd5b281ee9f8029db93cc5fb06c7eb3be9ee753106d3488fa92000000020000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb24001001300106ccff358aaa8a4a41c444e173b1463c2000000010000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb24002001300106ccff358aaa8a4a41c444e173b1463c2000000020000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb24001001400205cf3773dd920be800229ac1c6f9bf59c6706c583f82c2dea66c9a29152118cd7000000010000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb24002001400205cf3773dd920be800229ac1c6f9bf59c6706c583f82c2dea66c9a29152118cd700000002000000430001000b544553542e474f4b52423500097465737475736572320000000159beb2400100100018bc025746e9e66bd6b62a918f6413d529803192a28aabf79200000001000000430001000b544553542e474f4b52423500097465737475736572320000000159beb2400200100018bc025746e9e66bd6b62a918f6413d529803192a28aabf792000000020000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400200170010084768c373663b3bef1f6385883cf7ff00000002" TESTUSER3_KEYTAB = "05020000003b0001000b544553542e474f4b52423500097465737475736572330000000159beb2740100110010220789f5cf68e44a852c73b1e3729efc000000010000004b0001000b544553542e474f4b52423500097465737475736572330000000159beb27401001200205e1b7287f78f3f88c9eefe92c08ec1613f35d72bc6ee2c2e1c5cb6fb9d8b0580000000010000003b0001000b544553542e474f4b52423500097465737475736572330000000159beb2740200110010220789f5cf68e44a852c73b1e3729efc000000020000004b0001000b544553542e474f4b52423500097465737475736572330000000159beb27402001200205e1b7287f78f3f88c9eefe92c08ec1613f35d72bc6ee2c2e1c5cb6fb9d8b0580000000020000003b0001000b544553542e474f4b52423500097465737475736572330000000159beb2740100130010d2509e2bf9c08f73aafe08745c85e8fd000000010000003b0001000b544553542e474f4b52423500097465737475736572330000000159beb2740200130010d2509e2bf9c08f73aafe08745c85e8fd000000020000004b0001000b544553542e474f4b52423500097465737475736572330000000159beb274010014002060e5e3fb9166db825eb3e4ff4bd1f4307e3f5fe85cdf7faccf8d65330557f74e000000010000004b0001000b544553542e474f4b52423500097465737475736572330000000159beb274020014002060e5e3fb9166db825eb3e4ff4bd1f4307e3f5fe85cdf7faccf8d65330557f74e00000002000000430001000b544553542e474f4b52423500097465737475736572330000000159beb27401001000182cad768989a125fd26f24adf671976456d8097548c4c83f100000001000000430001000b544553542e474f4b52423500097465737475736572330000000159beb27402001000182cad768989a125fd26f24adf671976456d8097548c4c83f1000000020000003b0001000b544553542e474f4b52423500097465737475736572330000000159beb2740100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b544553542e474f4b52423500097465737475736572330000000159beb2740200170010084768c373663b3bef1f6385883cf7ff00000002" HTTP_KEYTAB = "0502000000440002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc010011001057a7754c70c4d85c155c718c2f1292b0000000540002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc01001200209cad00bbc72d703258e911dc18e6d5487cf737bf67fd111f0c2463ad6033bf51000000440002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc020011001057a7754c70c4d85c155c718c2f1292b0000000540002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc02001200209cad00bbc72d703258e911dc18e6d5487cf737bf67fd111f0c2463ad6033bf51" SYSHTTP_KEYTAB = "0502000000450001000b544553542e474f4b52423500077379734854545000000001590dc5af020012002043763702868978d1b6d91a36704b987e27e517250055bdfc40b8a6b3848d9aae" SYSHTTP_RESDOM_KEYTAB = "05020000005c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d94070100120020e53945463c231ab747635c96d5fc48f6591ce41cec98ad2620b50f52c2bafa96000000010000004c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d940701001100103ae5388332dc948e00427332658c537800000001000000540002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d940701001000183da7b93eb698233de3b07f080b07191a49a83d32d3587c8f000000010000004c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d9407010013001036e58aaaf739aad8bc49115dfd8d304b000000010000005c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d9407010014002055e4bf018dfdd3906f0f84149b6d503a03bdf494ba40f482faf67e7a77b9c05f000000010000004c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d94070100170010c050d33acce5fac748f6f26bd686e1c700000001" SYSHTTP_RESGOKRB5_AD_KEYTAB = "0502000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200170010084768c373663b3bef1f6385883cf7ff00000002000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200110010c622e44a32022f4cb81775263151140d00000002000000480001000a5245532e474f4b524235000773797348545450000000015e7a7e2f02001200209da0dc4802bf5d375dfe2a77ddfc5065b3bf789126c2dc89ff4c2aa90dfa43ce00000002000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200130010541beb216c1cdf22ef7c2225066a385e00000002000000480001000a5245532e474f4b524235000773797348545450000000015e7a7e2f02001400205f0983acd70fcaee0acb7ac4a14f8ad89f3a35915914e696200370637d8fef2300000002" TEST_AS_REQ = "6a81a63081a3a103020105a20302010aa30e300c300aa10402020095a2020400a48186308183a00703050040000010a1163014a003020101a10d300b1b09746573747573657231a20d1b0b544553542e474f4b524235a320301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a511180f32303137303232303134323530315aa70602040f6755a6a814301202011202011102011002011702011902011a" TEST_AS_REP = "6b8202f3308202efa003020105a10302010ba22e302c302aa103020113a2230421301f301da003020112a1161b14544553542e474f4b524235746573747573657231a30d1b0b544553542e474f4b524235a4163014a003020101a10d300b1b09746573747573657231a582015a6182015630820152a003020105a10d1b0b544553542e474f4b524235a220301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a382011830820114a003020112a103020101a28201060482010264d3fa49d89b627ed471298846ff92cd8632f657c58fe25322a61fffa32bb7966dc4c44c86a81353def2a11c36c537191406a609147f424a63266c00d02bcc56a27b0969d86ff4352634be9e2a4ac0ad5a36b0b0a3d689f128c0afa97401796e88037a35ad19efaf31d1ed4f3213769c03a58bc90ffac2051db152c0ed0809ad05ffb03aa3afaf731ed85f7a73020cb72355e0de27842dcf7eae3de9f7c14aa237edb25153b217ef3693373bc3cacbebe406910ff9ae9d00b7b08f726cb29a213cb9ad51ba80a8c24fa4b6692a445686889702cfa6ea749bac03e27e982407aca623fbd48586bcf566cfe87e1d9f17a74b1315669c16480f93e9d8782e71a8f11000a682012c30820128a003020112a282011f0482011b99b86153c0393c0e4130628f3e1e0f0a1f034e7e61a111b7fad15884e231c8fd8727e0bc945c9b35be20c57d057c8b09b0de74c53fb38cc15c9a2d483023fc369f5bde4da7324b4732b5a3d9504d92f67026aaa01df4f0138245d2ccb1c5a4014804cf295c7e7e56a867e6cf0c534f667f32da7aa5e700af1461764f1c276a8ff0fbee0e99322fe2059d2321853be09d0956c3afcfd07e3e702646a4678926a77bea20d9aaf3086b6d384821c81900af9013a3519f0e50eab6e1491d72e4ee17c2a44441b2ebc8a796cc3d876e328347dce65f61104e14d4c31532885776c9c8a70186b8b39f928972945c98bd60381ead5448e7ebe93fea308054287ac34b0583b4b9b5e43c5f8518d693ba9eb48a219c27344466b3c693a70462" TEST_TGS_REQ = "6c82038f3082038ba103020105a20302010ca382031a3082031630820245a103020101a282023c048202386e82023430820230a003020105a10302010ea20703050000000000a382015a6182015630820152a003020105a10d1b0b544553542e474f4b524235a220301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a382011830820114a003020112a103020101a28201060482010264d3fa49d89b627ed471298846ff92cd8632f657c58fe25322a61fffa32bb7966dc4c44c86a81353def2a11c36c537191406a609147f424a63266c00d02bcc56a27b0969d86ff4352634be9e2a4ac0ad5a36b0b0a3d689f128c0afa97401796e88037a35ad19efaf31d1ed4f3213769c03a58bc90ffac2051db152c0ed0809ad05ffb03aa3afaf731ed85f7a73020cb72355e0de27842dcf7eae3de9f7c14aa237edb25153b217ef3693373bc3cacbebe406910ff9ae9d00b7b08f726cb29a213cb9ad51ba80a8c24fa4b6692a445686889702cfa6ea749bac03e27e982407aca623fbd48586bcf566cfe87e1d9f17a74b1315669c16480f93e9d8782e71a8f11000a481bc3081b9a003020112a281b10481ae8ae3cb8ac47d77cfc7b0b6bf0d3c5f8fcc6dd569344256a6a40c004fc2d23ebbe6ee0b9e00eccf37e710b7c01a7d2a63bbed6d75f2b230d24d724ef90edad2c5680e7e2436ab1145ff68481673444ebd61e3aef79b9ee05809551672c6c436eb8ac732a7fe78bd8f380e68a541191e3125554e4bab63dcc19ea931c1477366a6039ff7b7e62521ebfeffd6784b6ef0c97f653ac4d8dfb304f3e2e843faab12d838c23f1105f0a281c39325987cb03081caa10402020088a281c10481bea081bb3081b8a1173015a003020110a10e040ce613d8e9d544f0e56c60d3bba2819c308199a003020112a2819104818ec4fabcb1ec2f24e04ef51f9247239b28275653fa5cbc1dc9e747530c597631050fe86a5f3cba2ff54270aa771dcefa87efc8c8604407f84e603f5c01a2d929e18103561c3ffbc3a0cf63340bdd67a0739d4d81989827fc1d3f7f13e9dd5cc2346ca08e26a2aaf6d0102fbef8f7a6ee0a1caae7880e953ea678da619038786122a0b71853e8d0b95f544f8fbd6945a461305fa00703050040810000a20d1b0b544553542e474f4b524235a3233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a511180f32303137303232303032323634325aa706020458a9ab2aa8053003020112" TEST_TGS_REP = "6d82039d30820399a003020105a10302010da281df3081dc3081d9a10402020088a281d00481cda081ca3081c7a081c43081c1a003020112a281b90481b62e2b31bd998e2747a447175bcdbe14b7bc043c747c3289e10cde7403ee685d64f9724055c667ad2d3ce6240b5457ddfe3505a5b4d5d5e35238522ba5d86f1b691f28bbe7290487ffebf7e720ecf772cef061ac2c433cabbd0d5973fbedeb5418c8ba1bb7149dd625a4ff4cb465d9599c9d4cfb899807fa088521fa0e0ed5cfa4dd6d02b2a6854c4feb4ec382de5820134ca7c140be4b0f416ca5ebb328c8a470a55154062b8070683d0897a40277bdc752e94437920aa30d1b0b544553542e474f4b524235a4163014a003020101a10d300b1b09746573747573657231a58201706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020102a28201190482011524db012d81dde2cb3b7b40a35ce4fe17f7898166c7a7534ad73ca761e75316a06504415b1bc62508edb6ea58544a3ac0f98911cd55332442d30e007e0f39efa939b726a5228514984a133d1ba7d93d60fec5b2f7fbb16356c85ad1b0bb7ed6108420514086ea9959037e794b535fa052651385060da83f93acffbde0cb486b2f236f8955bb521ef90bc0a52944d2a8da82389e6861065064cc5178a5a7d302e9950761648726fc4015f8772339ef401fd8327dd335c6692c010f2c31b5ccccbcd83e68f2a1b66f16ca44e0bfb2903801c27ed8dd9a7b2dcea3c2bc91d8055c603f218494b1342490fbb805492d999491c2570b3ab392ba7e62c23659663509838e91b6f560a284889075071c348c701017a0a5e5c7a682010e3082010aa003020112a28201010481fece77ad9ebec9b094b686d4885895a2530471e9a56a93c2a463eaa988932695d77a12b6c02e69edfea6759ed49b029bdef43e446129b80a1ef95b9cea69a382d5c074868e68b676869f0990cad588c6d3589b9bba795ff0572ba648915f68a3b49df1def285645e93f4285a843ead01122c4f00bebefa33ecf6df2584e33da359d3d53e0a9b8ec0f41120a0d612d3a1e4504ce464dddec5e2aa3f0d2b6dae7b8800091760d7a590eaa6d7b33539c1700d9daf22a7c8cc1e108aee75267434997666132f52c08ea7c0c898859214ac4e71e185d3bcec1467ab6e8d91e79ba2b2041d40714da495c79d232d3c4e72a0910b9143fd648fe24bc46ec3416a3c34" CCACHE_TEST = "0504000c00010008000000060000000000000001000000010000000b544553542e474f4b5242350000000974657374757365723100000001000000010000000b544553542e474f4b5242350000000974657374757365723100000002000000020000000b544553542e474f4b524235000000066b72627467740000000b544553542e474f4b52423500120000002088b94319f2dcd1de20ebd3bf3174778769323bce76ef71fb37a8ba4be93c38df59665b8e59665b8e5967044e5967ad080040c1000000000000000000000000015a6182015630820152a003020105a10d1b0b544553542e474f4b524235a220301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a382011830820114a003020112a103020101a282010604820102ee32bb7e27ad6f71869be098c4002b291f370d26302c87ffa3eb670345a11fc113a9e5ab9e26ea659104b29e2a60c07dda559654c58aaf5f48bbb3bb9a238745861be336a0672554dac9b38126b2929ce9df2add185d1043c6dd89c7308b9def7b98ba7bcdcd1c00eeb5d99e273e1fe53b88c057106ec3dbcf2a86c38a4c1372418f1afb0227975747edf2172e23716ab5f6fa9a2ee5c0d94e9f66936df767498677861926812d1f887de6f44e5ebd93b63fd8313a499372ea9e889620bd0842bc8a8f8a17e5dea328c77b771cfcd49ac7afa4a9c7236efa30fec1b2072255543aee48cd935ece367e08d24f51bea4b407ace8ed7e67a8d5e1cb528eb16c7ebe7ac50000000000000001000000010000000b544553542e474f4b5242350000000974657374757365723100000000000000030000000c582d4341434845434f4e463a000000156b7262355f6363616368655f636f6e665f646174610000000a666173745f617661696c0000001e6b72627467742f544553542e474f4b52423540544553542e474f4b5242350000000000000000000000000000000000000000000000000000000000000000000000000000037965730000000000000001000000010000000b544553542e474f4b5242350000000974657374757365723100000001000000020000000b544553542e474f4b524235000000044854545000000010686f73742e746573742e676f6b726235001200000020fd325da3f905d743894e828de41b21af7876b6281b66d9e4bb2eefd64078b47659665b8e59665bce5967044e5967ad0800408900000000000000000000000001706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020101a282011904820115ad55d79858ce41647e835769b40540bc32ff4debe101217a7a024016697ee5ff758829940ca576905a260732c43c2996d96b83f9bff010fdbfc8f3bff51cef202a956f8d73d18c2c8865553f55229075270f42dca23d7618ff35e578a972d40746398efd478cf4f1094d99371273b3fbe5b95707011b446ff605ea8cb0e6631ea0ffdd7b562b5aa2de5dd455388e1aa18d8a3a8e81dab058e1b223410a752e5ec82797164dabafdbec8eeef7b072304e46d7d15b575f44cce69a368a9004612ba179b41d4655964933f7eb114a457aa1127291fc6d63deb271e5504de6fccca33260645ef5bd1ea301d74a8dbf751aa181ed92f5edb493d68222e1a34892035b88b6fb0ce104db23f7da22a8e73359d9c322b8e1cc00000000" TEST_HTTP_URL = "http://10.80.88.88" TEST_KDC_ADDR = "10.80.88.88" TEST_KDC = "88" TEST_KDC_LASTEST = "98" TEST_KDC_RESDOM = "188" TEST_KDC_OLD = "78" TEST_KDC_SHORTTICKETS = "58" TEST_KDC_BADADDR = "10.80.88.153" TEST_KDC_AD = "10.80.88.68:88" TEST_KDC_AD_TRUST_USER_DOMAIN = "192.168.88.100:88" TEST_KDC_AD_TRUST_RES_DOMAIN = "192.168.88.101:88" TEST_NS = "10.80.88.88:53" TEST_KRB5CONF = `[libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 noaddresses = false [realms] TEST.GOKRB5 = { kdc = 127.0.0.1:88 admin_server = 127.0.0.1:749 default_domain = test.gokrb5 } RESDOM.GOKRB5 = { kdc = 10.80.88.88:188 admin_server = 127.0.0.1:749 default_domain = resdom.gokrb5 } USER.GOKRB5 = { kdc = 192.168.88.100:88 admin_server = 192.168.88.100:464 default_domain = user.gokrb5 } RES.GOKRB5 = { kdc = 192.168.88.101:88 admin_server = 192.168.88.101:464 default_domain = res.gokrb5 } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 .resdom.gokrb5 = RESDOM.GOKRB5 resdom.gokrb5 = RESDOM.GOKRB5 .user.gokrb5 = USER.GOKRB5 user.gokrb5 = USER.GOKRB5 .res.gokrb5 = RES.GOKRB5 res.gokrb5 = RES.GOKRB5 ` KRB5_CONF_AD = `[libdefaults] default_realm = USER.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 noaddresses = false [realms] USER.GOKRB5 = { kdc = 192.168.88.100:88 admin_server = 192.168.88.100:464 default_domain = user.gokrb5 } RES.GOKRB5 = { kdc = 192.168.88.101:88 admin_server = 192.168.88.101:464 default_domain = res.gokrb5 } [domain_realm] .user.gokrb5 = USER.GOKRB5 user.gokrb5 = USER.GOKRB5 .res.gokrb5 = RES.GOKRB5 res.gokrb5 = RES.GOKRB5 ` ) gokrb5-8.4.3/test/testdata/testuser1.testtab000066400000000000000000000015061427031340300210350ustar00rootroot00000000000000; TEST.GOKRB5 testuser1YiM~Z!E&%K TEST.GOKRB5 testuser1Y C ~-F"E; bێ/B\m; TEST.GOKRB5 testuser1YiM~Z!E&%K TEST.GOKRB5 testuser1Y C ~-F"E; bێ/B\m; TEST.GOKRB5 testuser1Y.Pgn cĠ; TEST.GOKRB5 testuser1Y.Pgn cĠK TEST.GOKRB5 testuser1Y o }o"068fғ$K TEST.GOKRB5 testuser1Y o }o"068fғ$C TEST.GOKRB5 testuser1YEv ,&IOdL]a,yC TEST.GOKRB5 testuser1YEv ,&IOdL]a,y; TEST.GOKRB5 testuser1YGhsf;;c<; TEST.GOKRB5 testuser1YGhsf;;c<gokrb5-8.4.3/types/000077500000000000000000000000001427031340300140605ustar00rootroot00000000000000gokrb5-8.4.3/types/Authenticator.go000066400000000000000000000050021427031340300172160ustar00rootroot00000000000000// Package types provides Kerberos 5 data types. package types import ( "crypto/rand" "fmt" "math" "math/big" "time" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag" ) // Authenticator - A record containing information that can be shown to have been recently generated using the session // key known only by the client and server. // https://tools.ietf.org/html/rfc4120#section-5.5.1 type Authenticator struct { AVNO int `asn1:"explicit,tag:0"` CRealm string `asn1:"generalstring,explicit,tag:1"` CName PrincipalName `asn1:"explicit,tag:2"` Cksum Checksum `asn1:"explicit,optional,tag:3"` Cusec int `asn1:"explicit,tag:4"` CTime time.Time `asn1:"generalized,explicit,tag:5"` SubKey EncryptionKey `asn1:"explicit,optional,tag:6"` SeqNumber int64 `asn1:"explicit,optional,tag:7"` AuthorizationData AuthorizationData `asn1:"explicit,optional,tag:8"` } // NewAuthenticator creates a new Authenticator. func NewAuthenticator(realm string, cname PrincipalName) (Authenticator, error) { seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) if err != nil { return Authenticator{}, err } t := time.Now().UTC() return Authenticator{ AVNO: iana.PVNO, CRealm: realm, CName: cname, Cksum: Checksum{}, Cusec: int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)), CTime: t, SeqNumber: seq.Int64(), }, nil } // GenerateSeqNumberAndSubKey sets the Authenticator's sequence number and subkey. func (a *Authenticator) GenerateSeqNumberAndSubKey(keyType int32, keySize int) error { seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) if err != nil { return err } a.SeqNumber = seq.Int64() //Generate subkey value sk := make([]byte, keySize, keySize) rand.Read(sk) a.SubKey = EncryptionKey{ KeyType: keyType, KeyValue: sk, } return nil } // Unmarshal bytes into the Authenticator. func (a *Authenticator) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.Authenticator)) return err } // Marshal the Authenticator. func (a *Authenticator) Marshal() ([]byte, error) { b, err := asn1.Marshal(*a) if err != nil { return nil, err } b = asn1tools.AddASNAppTag(b, asnAppTag.Authenticator) return b, nil } gokrb5-8.4.3/types/Authenticator_test.go000066400000000000000000000107321427031340300202630ustar00rootroot00000000000000package types import ( "encoding/hex" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/iana/adtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func unmarshalAuthenticatorTest(t *testing.T, v string) Authenticator { var a Authenticator //t.Logf("Starting unmarshal tests of %s", v) b, err := hex.DecodeString(v) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } return a } func TestUnmarshalAuthenticator(t *testing.T) { t.Parallel() a := unmarshalAuthenticatorTest(t, testdata.MarshaledKRB5authenticator) //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.AVNO, "Authenticator version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, int32(1), a.Cksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.Cksum.Checksum, "Checsum not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.CTime, "Client time not as expected") assert.Equal(t, int32(1), a.SubKey.KeyType, "Subkey type not as expected") assert.Equal(t, []byte("12345678"), a.SubKey.KeyValue, "Subkey value not as expected") assert.Equal(t, 2, len(a.AuthorizationData), "Number of Authorization data items not as expected") for i, entry := range a.AuthorizationData { assert.Equal(t, adtype.ADIfRelevant, entry.ADType, fmt.Sprintf("Authorization type of entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_AUTHORIZATION_DATA_VALUE), entry.ADData, fmt.Sprintf("Authorization data of entry %d not as expected", i+1)) } } func TestUnmarshalAuthenticator_optionalsempty(t *testing.T) { t.Parallel() a := unmarshalAuthenticatorTest(t, testdata.MarshaledKRB5authenticatorOptionalsEmpty) //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.AVNO, "Authenticator version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.CTime, "Client time not as expected") } func TestUnmarshalAuthenticator_optionalsNULL(t *testing.T) { t.Parallel() a := unmarshalAuthenticatorTest(t, testdata.MarshaledKRB5authenticatorOptionalsNULL) //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.AVNO, "Authenticator version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.CTime, "Client time not as expected") } func TestMarshalAuthenticator(t *testing.T) { t.Parallel() var a Authenticator b, err := hex.DecodeString(testdata.MarshaledKRB5authenticator) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of Authenticator not as expected") } gokrb5-8.4.3/types/AuthorizationData.go000066400000000000000000000034431427031340300200450ustar00rootroot00000000000000package types import ( "github.com/jcmturner/gofork/encoding/asn1" ) // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.6 // AuthorizationData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6 type AuthorizationData []AuthorizationDataEntry // AuthorizationDataEntry implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6 type AuthorizationDataEntry struct { ADType int32 `asn1:"explicit,tag:0"` ADData []byte `asn1:"explicit,tag:1"` } // ADIfRelevant implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.1 type ADIfRelevant AuthorizationData // ADKDCIssued implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.2 type ADKDCIssued struct { ADChecksum Checksum `asn1:"explicit,tag:0"` IRealm string `asn1:"optional,generalstring,explicit,tag:1"` Isname PrincipalName `asn1:"optional,explicit,tag:2"` Elements AuthorizationData `asn1:"explicit,tag:3"` } // ADAndOr implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.3 type ADAndOr struct { ConditionCount int32 `asn1:"explicit,tag:0"` Elements AuthorizationData `asn1:"explicit,tag:1"` } // ADMandatoryForKDC implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.4 type ADMandatoryForKDC AuthorizationData // Unmarshal bytes into the ADKDCIssued. func (a *ADKDCIssued) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Unmarshal bytes into the AuthorizationData. func (a *AuthorizationData) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Unmarshal bytes into the AuthorizationDataEntry. func (a *AuthorizationDataEntry) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } gokrb5-8.4.3/types/AuthorizationData_test.go000066400000000000000000000040541427031340300211030ustar00rootroot00000000000000package types import ( "encoding/hex" "fmt" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/adtype" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalAuthorizationData(t *testing.T) { t.Parallel() var a AuthorizationData b, err := hex.DecodeString(testdata.MarshaledKRB5authorization_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, 2, len(a), "Number of authorization data entries not as expected") for i, entry := range a { assert.Equal(t, adtype.ADIfRelevant, entry.ADType, fmt.Sprintf("Authorization data type of entry %d not as expected", i+1)) assert.Equal(t, []byte("foobar"), entry.ADData, fmt.Sprintf("Authorization data of entry %d not as expected", i+1)) } } func TestUnmarshalAuthorizationData_kdcissued(t *testing.T) { t.Parallel() var a ADKDCIssued b, err := hex.DecodeString(testdata.MarshaledKRB5ad_kdcissued) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, int32(1), a.ADChecksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.ADChecksum.Checksum, "Checksum not as expected") assert.Equal(t, testdata.TEST_REALM, a.IRealm, "Issuing realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Isname.NameType, "Issuing name type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Isname.NameString, "Issuing name string entries not as expected") assert.Equal(t, 2, len(a.Elements), "Number of authorization data elements not as expected") for i, ele := range a.Elements { assert.Equal(t, adtype.ADIfRelevant, ele.ADType, fmt.Sprintf("Authorization data type of element %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_AUTHORIZATION_DATA_VALUE), ele.ADData, fmt.Sprintf("Authorization data of element %d not as expected", i+1)) } } gokrb5-8.4.3/types/Cryptosystem.go000066400000000000000000000025651427031340300171440ustar00rootroot00000000000000package types import ( "github.com/jcmturner/gofork/encoding/asn1" ) // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.9 // EncryptedData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9 type EncryptedData struct { EType int32 `asn1:"explicit,tag:0"` KVNO int `asn1:"explicit,optional,tag:1"` Cipher []byte `asn1:"explicit,tag:2"` } // EncryptionKey implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9 // AKA KeyBlock type EncryptionKey struct { KeyType int32 `asn1:"explicit,tag:0"` KeyValue []byte `asn1:"explicit,tag:1"` } // Checksum implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9 type Checksum struct { CksumType int32 `asn1:"explicit,tag:0"` Checksum []byte `asn1:"explicit,tag:1"` } // Unmarshal bytes into the EncryptedData. func (a *EncryptedData) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Marshal the EncryptedData. func (a *EncryptedData) Marshal() ([]byte, error) { edb, err := asn1.Marshal(*a) if err != nil { return edb, err } return edb, nil } // Unmarshal bytes into the EncryptionKey. func (a *EncryptionKey) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Unmarshal bytes into the Checksum. func (a *Checksum) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } gokrb5-8.4.3/types/Cryptosystem_test.go000066400000000000000000000053411427031340300201760ustar00rootroot00000000000000package types import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalEncryptedData(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, testdata.TEST_ETYPE, a.EType, "Encrypted data Etype not as expected") assert.Equal(t, iana.PVNO, a.KVNO, "Encrypted data KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Cipher, "Ecrypted data ciphertext not as expected") } func TestUnmarshalEncryptedData_MSBsetkvno(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_dataMSBSetkvno) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, testdata.TEST_ETYPE, a.EType, "Encrypted data Etype not as expected") assert.Equal(t, -16777216, a.KVNO, "Encrypted data KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Cipher, "Ecrypted data ciphertext not as expected") } func TestUnmarshalEncryptedData_kvno_neg1(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_dataKVNONegOne) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, testdata.TEST_ETYPE, a.EType, "Encrypted data Etype not as expected") assert.Equal(t, -1, a.KVNO, "Encrypted data KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Cipher, "Ecrypted data ciphertext not as expected") } func TestUnmarshalEncryptionKey(t *testing.T) { t.Parallel() var a EncryptionKey b, err := hex.DecodeString(testdata.MarshaledKRB5keyblock) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, int32(1), a.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.KeyValue, "Key value not as expected") } func TestMarshalEncryptedData(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of Encrypted Data not as expected") } gokrb5-8.4.3/types/HostAddress.go000066400000000000000000000077241427031340300166440ustar00rootroot00000000000000package types // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.5 import ( "bytes" "fmt" "net" "github.com/jcmturner/gofork/encoding/asn1" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" ) // HostAddresses implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.5 type HostAddresses []HostAddress // HostAddress implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.5 type HostAddress struct { AddrType int32 `asn1:"explicit,tag:0"` Address []byte `asn1:"explicit,tag:1"` } // GetHostAddress returns a HostAddress struct from a string in the format : func GetHostAddress(s string) (HostAddress, error) { var h HostAddress cAddr, _, err := net.SplitHostPort(s) if err != nil { return h, fmt.Errorf("invalid format of client address: %v", err) } ip := net.ParseIP(cAddr) var ht int32 if ip.To4() != nil { ht = addrtype.IPv4 ip = ip.To4() } else if ip.To16() != nil { ht = addrtype.IPv6 ip = ip.To16() } else { return h, fmt.Errorf("could not determine client's address types: %v", err) } h = HostAddress{ AddrType: ht, Address: ip, } return h, nil } // GetAddress returns a string representation of the HostAddress. func (h *HostAddress) GetAddress() (string, error) { var b []byte _, err := asn1.Unmarshal(h.Address, &b) return string(b), err } // LocalHostAddresses returns a HostAddresses struct for the local machines interface IP addresses. func LocalHostAddresses() (ha HostAddresses, err error) { ifs, err := net.Interfaces() if err != nil { return } for _, iface := range ifs { if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 { // Interface is either loopback of not up continue } addrs, err := iface.Addrs() if err != nil { continue } for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: ip = v.IP case *net.IPAddr: ip = v.IP } var a HostAddress if ip.To16() == nil { //neither IPv4 or IPv6 continue } if ip.To4() != nil { //Is IPv4 a.AddrType = addrtype.IPv4 a.Address = ip.To4() } else { a.AddrType = addrtype.IPv6 a.Address = ip.To16() } ha = append(ha, a) } } return ha, nil } // HostAddressesFromNetIPs returns a HostAddresses type from a slice of net.IP func HostAddressesFromNetIPs(ips []net.IP) (ha HostAddresses) { for _, ip := range ips { ha = append(ha, HostAddressFromNetIP(ip)) } return ha } // HostAddressFromNetIP returns a HostAddress type from a net.IP func HostAddressFromNetIP(ip net.IP) HostAddress { if ip.To4() != nil { //Is IPv4 return HostAddress{ AddrType: addrtype.IPv4, Address: ip.To4(), } } return HostAddress{ AddrType: addrtype.IPv6, Address: ip.To16(), } } // HostAddressesEqual tests if two HostAddress slices are equal. func HostAddressesEqual(h, a []HostAddress) bool { if len(h) != len(a) { return false } for _, e := range a { var found bool for _, i := range h { if e.Equal(i) { found = true break } } if !found { return false } } return true } // HostAddressesContains tests if a HostAddress is contained in a HostAddress slice. func HostAddressesContains(h []HostAddress, a HostAddress) bool { for _, e := range h { if e.Equal(a) { return true } } return false } // Equal tests if the HostAddress is equal to another HostAddress provided. func (h *HostAddress) Equal(a HostAddress) bool { if h.AddrType != a.AddrType { return false } return bytes.Equal(h.Address, a.Address) } // Contains tests if a HostAddress is contained within the HostAddresses struct. func (h *HostAddresses) Contains(a HostAddress) bool { for _, e := range *h { if e.Equal(a) { return true } } return false } // Equal tests if a HostAddress slice is equal to the HostAddresses struct. func (h *HostAddresses) Equal(a []HostAddress) bool { if len(*h) != len(a) { return false } for _, e := range a { if !h.Contains(e) { return false } } return true } gokrb5-8.4.3/types/HostAddress_test.go000066400000000000000000000014111427031340300176660ustar00rootroot00000000000000package types import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/addrtype" ) func TestGetHostAddress(t *testing.T) { tests := []struct { str string ipType int32 hex string }{ {"192.168.1.100", addrtype.IPv4, "c0a80164"}, {"127.0.0.1", addrtype.IPv4, "7f000001"}, {"[fe80::1cf3:b43b:df29:d43e]", addrtype.IPv6, "fe800000000000001cf3b43bdf29d43e"}, } for _, test := range tests { h, err := GetHostAddress(test.str + ":1234") if err != nil { t.Errorf("error getting host for %s: %v", test.str, err) } assert.Equal(t, test.ipType, h.AddrType, "wrong address type for %s", test.str) assert.Equal(t, test.hex, hex.EncodeToString(h.Address), "wrong address bytes for %s", test.str) } } gokrb5-8.4.3/types/KerberosFlags.go000066400000000000000000000030041427031340300171350ustar00rootroot00000000000000package types // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.8 import ( "github.com/jcmturner/gofork/encoding/asn1" ) // NewKrbFlags returns an ASN1 BitString struct of the right size for KrbFlags. func NewKrbFlags() asn1.BitString { f := asn1.BitString{} f.Bytes = make([]byte, 4) f.BitLength = len(f.Bytes) * 8 return f } // SetFlags sets the flags of an ASN1 BitString. func SetFlags(f *asn1.BitString, j []int) { for _, i := range j { SetFlag(f, i) } } // SetFlag sets a flag in an ASN1 BitString. func SetFlag(f *asn1.BitString, i int) { for l := len(f.Bytes); l < 4; l++ { (*f).Bytes = append((*f).Bytes, byte(0)) (*f).BitLength = len((*f).Bytes) * 8 } //Which byte? b := i / 8 //Which bit in byte p := uint(7 - (i - 8*b)) (*f).Bytes[b] = (*f).Bytes[b] | (1 << p) } // UnsetFlags unsets flags in an ASN1 BitString. func UnsetFlags(f *asn1.BitString, j []int) { for _, i := range j { UnsetFlag(f, i) } } // UnsetFlag unsets a flag in an ASN1 BitString. func UnsetFlag(f *asn1.BitString, i int) { for l := len(f.Bytes); l < 4; l++ { (*f).Bytes = append((*f).Bytes, byte(0)) (*f).BitLength = len((*f).Bytes) * 8 } //Which byte? b := i / 8 //Which bit in byte p := uint(7 - (i - 8*b)) (*f).Bytes[b] = (*f).Bytes[b] &^ (1 << p) } // IsFlagSet tests if a flag is set in the ASN1 BitString. func IsFlagSet(f *asn1.BitString, i int) bool { //Which byte? b := i / 8 //Which bit in byte p := uint(7 - (i - 8*b)) if (*f).Bytes[b]&(1</@ // a PrincipalName type will be returned with the name type set to KRB_NT_PRINCIPAL(1) // and the realm will be returned as a string. If the "@" suffix // is not included in the SPN then the value of realm string returned will be "" func ParseSPNString(spn string) (pn PrincipalName, realm string) { if strings.Contains(spn, "@") { s := strings.Split(spn, "@") realm = s[len(s)-1] spn = strings.TrimSuffix(spn, "@"+realm) } pn = NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) return } gokrb5-8.4.3/types/PrincipalName_test.go000066400000000000000000000030461427031340300201730ustar00rootroot00000000000000package types import ( "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" "testing" ) func TestPrincipalName_GetSalt(t *testing.T) { t.Parallel() pn := PrincipalName{ NameType: 1, NameString: []string{"firststring", "secondstring"}, } assert.Equal(t, "TEST.GOKRB5firststringsecondstring", pn.GetSalt("TEST.GOKRB5"), "Principal name default salt not as expected") } func TestParseSPNString(t *testing.T) { pn, realm := ParseSPNString("HTTP/www.example.com@REALM.COM") assert.Equal(t, "REALM.COM", realm, "realm value not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, pn.NameType, "name type not as expected") assert.Equal(t, "HTTP", pn.NameString[0], "first element of name string not as expected") assert.Equal(t, "www.example.com", pn.NameString[1], "second element of name string not as expected") pn, realm = ParseSPNString("HTTP/www.example.com") assert.Equal(t, "", realm, "realm value not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, pn.NameType, "name type not as expected") assert.Equal(t, "HTTP", pn.NameString[0], "first element of name string not as expected") assert.Equal(t, "www.example.com", pn.NameString[1], "second element of name string not as expected") pn, realm = ParseSPNString("www.example.com@REALM.COM") assert.Equal(t, "REALM.COM", realm, "realm value not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, pn.NameType, "name type not as expected") assert.Equal(t, "www.example.com", pn.NameString[0], "second element of name string not as expected") } gokrb5-8.4.3/types/TypedData.go000066400000000000000000000010561427031340300162700ustar00rootroot00000000000000package types import "github.com/jcmturner/gofork/encoding/asn1" // TypedData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.9.1 type TypedData struct { DataType int32 `asn1:"explicit,tag:0"` DataValue []byte `asn1:"optional,explicit,tag:1"` } // TypedDataSequence implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.9.1 type TypedDataSequence []TypedData // Unmarshal bytes into the TypedDataSequence. func (a *TypedDataSequence) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } gokrb5-8.4.3/types/TypedData_test.go000066400000000000000000000014771427031340300173360ustar00rootroot00000000000000package types import ( "encoding/hex" "fmt" "testing" "github.com/stretchr/testify/assert" "gopkg.in/jcmturner/gokrb5.v7/iana/patype" "gopkg.in/jcmturner/gokrb5.v7/test/testdata" ) func TestUnmarshalTypedData(t *testing.T) { t.Parallel() var a TypedDataSequence b, err := hex.DecodeString(testdata.MarshaledKRB5typed_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, 2, len(a), "Number of typed data elements not as expected") for i, d := range a { assert.Equal(t, patype.PA_SAM_RESPONSE, d.DataType, fmt.Sprintf("Data type of element %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), d.DataValue, fmt.Sprintf("Data value of element %d not as expected", i+1)) } } gokrb5-8.4.3/v8/000077500000000000000000000000001427031340300132515ustar00rootroot00000000000000gokrb5-8.4.3/v8/README.md000066400000000000000000000161231427031340300145330ustar00rootroot00000000000000# gokrb5 It is recommended to use the latest version: [![Version](https://img.shields.io/github/release/jcmturner/gokrb5.svg)](https://github.com/jcmturner/gokrb5/releases) Development will be focused on the latest major version. New features will only be targeted at this version. | Versions | Dependency Management | Import Path | Usage | Godoc | Go Report Card | |----------|-----------------------|-------------|-------|-------|----------------| | [![v8](https://github.com/jcmturner/gokrb5/workflows/v8/badge.svg)](https://github.com/jcmturner/gokrb5/actions?query=workflow%3Av8) | Go modules | import "github.com/jcmturner/gokrb5/v8/{sub-package}" | [![Usage](https://img.shields.io/badge/v8-usage-blue)](https://github.com/jcmturner/gokrb5/blob/master/v8/USAGE.md) | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue)](https://pkg.go.dev/github.com/jcmturner/gokrb5/v8) | [![Go Report Card](https://goreportcard.com/badge/github.com/jcmturner/gokrb5/v8)](https://goreportcard.com/report/github.com/jcmturner/gokrb5/v8) | | [![v7](https://github.com/jcmturner/gokrb5/workflows/v7/badge.svg)](https://github.com/jcmturner/gokrb5/actions?query=workflow%3Av7) | gopkg.in | import "gopkg.in/jcmturner/gokrb5.v7/{sub-package}" | [![Usage](https://img.shields.io/badge/v7-usage-blue)](https://github.com/jcmturner/gokrb5/blob/master/USAGE.md) | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue)](https://pkg.go.dev/github.com/jcmturner/gokrb5@v7.5.0+incompatible) | [![Go Report Card](https://goreportcard.com/badge/gopkg.in/jcmturner/gokrb5.v7)](https://goreportcard.com/report/gopkg.in/jcmturner/gokrb5.v7) | #### Go Version Support ![Go version](https://img.shields.io/badge/Go-1.18-brightgreen.svg) ![Go version](https://img.shields.io/badge/Go-1.17-brightgreen.svg) ![Go version](https://img.shields.io/badge/Go-1.16-brightgreen.svg) gokrb5 may work with other versions of Go but they are not formally tested. It has been reported that gokrb5 also works with the [gollvm](https://go.googlesource.com/gollvm/) compiler but this is not formally tested. ## Features * **Pure Go** - no dependency on external libraries * No platform specific code * Server Side * HTTP handler wrapper implements SPNEGO Kerberos authentication * HTTP handler wrapper decodes Microsoft AD PAC authorization data * Client Side * Client that can authenticate to an SPNEGO Kerberos authenticated web service * Ability to change client's password * General * Kerberos libraries for custom integration * Parsing Keytab files * Parsing krb5.conf files * Parsing client credentials cache files such as `/tmp/krb5cc_$(id -u $(whoami))` #### Implemented Encryption & Checksum Types | Implementation | Encryption ID | Checksum ID | RFC | |-------|-------------|------------|------| | des3-cbc-sha1-kd | 16 | 12 | 3961 | | aes128-cts-hmac-sha1-96 | 17 | 15 | 3962 | | aes256-cts-hmac-sha1-96 | 18 | 16 | 3962 | | aes128-cts-hmac-sha256-128 | 19 | 19 | 8009 | | aes256-cts-hmac-sha384-192 | 20 | 20 | 8009 | | rc4-hmac | 23 | -138 | 4757 | The following is working/tested: * Tested against MIT KDC (1.6.3 is the oldest version tested against) and Microsoft Active Directory (Windows 2008 R2) * Tested against a KDC that supports PA-FX-FAST. * Tested against users that have pre-authentication required using PA-ENC-TIMESTAMP. * Microsoft PAC Authorization Data is processed and exposed in the HTTP request context. Available if Microsoft Active Directory is used as the KDC. ## Contributing If you are interested in contributing to gokrb5, great! Please read the [contribution guidelines](https://github.com/jcmturner/gokrb5/blob/master/CONTRIBUTING.md). --- ## References * [RFC 3244 Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols](https://tools.ietf.org/html/rfc3244) * [RFC 4120 The Kerberos Network Authentication Service (V5)](https://tools.ietf.org/html/rfc4120) * [RFC 3961 Encryption and Checksum Specifications for Kerberos 5](https://tools.ietf.org/html/rfc3961) * [RFC 3962 Advanced Encryption Standard (AES) Encryption for Kerberos 5](https://tools.ietf.org/html/rfc3962) * [RFC 4121 The Kerberos Version 5 GSS-API Mechanism](https://tools.ietf.org/html/rfc4121) * [RFC 4178 The Simple and Protected Generic Security Service Application Program Interface (GSS-API) Negotiation Mechanism](https://tools.ietf.org/html/rfc4178.html) * [RFC 4559 SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows](https://tools.ietf.org/html/rfc4559.html) * [RFC 4757 The RC4-HMAC Kerberos Encryption Types Used by Microsoft Windows](https://tools.ietf.org/html/rfc4757) * [RFC 6806 Kerberos Principal Name Canonicalization and Cross-Realm Referrals](https://tools.ietf.org/html/rfc6806.html) * [RFC 6113 A Generalized Framework for Kerberos Pre-Authentication](https://tools.ietf.org/html/rfc6113.html) * [RFC 8009 AES Encryption with HMAC-SHA2 for Kerberos 5](https://tools.ietf.org/html/rfc8009) * [IANA Assigned Kerberos Numbers](http://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml) * [HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol - Part 1](https://msdn.microsoft.com/en-us/library/ms995329.aspx) * [HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol - Part 2](https://msdn.microsoft.com/en-us/library/ms995330.aspx) * [Microsoft PAC Validation](https://blogs.msdn.microsoft.com/openspecification/2009/04/24/understanding-microsoft-kerberos-pac-validation/) * [Microsoft Kerberos Protocol Extensions](https://msdn.microsoft.com/en-us/library/cc233855.aspx) * [Windows Data Types](https://msdn.microsoft.com/en-us/library/cc230273.aspx) ### Useful Links * https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing ## Thanks * Greg Hudson from the MIT Consortium for Kerberos and Internet Trust for providing useful advice. ## Contributing Thank you for your interest in contributing to gokrb5 please read the [contribution guide](https://github.com/jcmturner/gokrb5/blob/master/CONTRIBUTING.md) as it should help you get started. ## Known Issues | Issue | Worked around? | References | |-------|-------------|------------| | The Go standard library's encoding/asn1 package cannot unmarshal into slice of asn1.RawValue | Yes | https://github.com/golang/go/issues/17321 | | The Go standard library's encoding/asn1 package cannot marshal into a GeneralString | Yes - using https://github.com/jcmturner/gofork/tree/master/encoding/asn1 | https://github.com/golang/go/issues/18832 | | The Go standard library's encoding/asn1 package cannot marshal into slice of strings and pass stringtype parameter tags to members | Yes - using https://github.com/jcmturner/gofork/tree/master/encoding/asn1 | https://github.com/golang/go/issues/18834 | | The Go standard library's encoding/asn1 package cannot marshal with application tags | Yes | | | The Go standard library's x/crypto/pbkdf2.Key function uses the int type for iteraction count limiting meaning the 4294967296 count specified in https://tools.ietf.org/html/rfc3962 section 4 cannot be met on 32bit systems | Yes - using https://github.com/jcmturner/gofork/tree/master/x/crypto/pbkdf2 | https://go-review.googlesource.com/c/crypto/+/85535 | gokrb5-8.4.3/v8/USAGE.md000066400000000000000000000234751427031340300144520ustar00rootroot00000000000000## Version 8 Usage ### Configuration The gokrb5 libraries use the same krb5.conf configuration file format as MIT Kerberos, described [here](https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html). Config instances can be created by loading from a file path or by passing a string, io.Reader or bufio.Scanner to the relevant method: ```go import "github.com/jcmturner/gokrb5/v8/config" cfg, err := config.Load("/path/to/config/file") cfg, err := config.NewFromString(krb5Str) //String must have appropriate newline separations cfg, err := config.NewFromReader(reader) cfg, err := config.NewFromScanner(scanner) ``` ### Keytab files Standard keytab files can be read from a file or from a slice of bytes: ```go import "github.com/jcmturner/gokrb5/v8/keytab" ktFromFile, err := keytab.Load("/path/to/file.keytab") ktFromBytes, err := keytab.Parse(b) ``` --- ### Kerberos Client **Create** a client instance with either a password or a keytab. A configuration must also be passed. Additionally optional additional settings can be provided. ```go import "github.com/jcmturner/gokrb5/v8/client" cl := client.NewWithPassword("username", "REALM.COM", "password", cfg) cl := client.NewWithKeytab("username", "REALM.COM", kt, cfg) ``` Optional settings are provided using the functions defined in the ``client/settings.go`` source file. **Login**: ```go err := cl.Login() ``` Kerberos Ticket Granting Tickets (TGT) will be automatically renewed unless the client was created from a CCache. A client can be **destroyed** with the following method: ```go cl.Destroy() ``` #### Active Directory KDC and FAST negotiation Active Directory does not commonly support FAST negotiation so you will need to disable this on the client. If this is the case you will see this error: ```KDC did not respond appropriately to FAST negotiation``` To resolve this disable PA-FX-Fast on the client before performing Login(). This is done with one of the optional client settings as shown below: ```go cl := client.NewWithPassword("username", "REALM.COM", "password", cfg, client.DisablePAFXFAST(true)) ``` #### Authenticate to a Service ##### HTTP SPNEGO Create the HTTP request object and then create an SPNEGO client and use this to process the request with methods that are the same as on a HTTP client. If nil is passed as the HTTP client when creating the SPNEGO client the http.DefaultClient is used. When creating the SPNEGO client pass the Service Principal Name (SPN) or auto generate the SPN from the request object by passing a null string "". ```go r, _ := http.NewRequest("GET", "http://host.test.gokrb5/index.html", nil) spnegoCl := spnego.NewClient(cl, nil, "") resp, err := spnegoCl.Do(r) ``` ##### Generic Kerberos Client To authenticate to a service a client will need to request a service ticket for a Service Principal Name (SPN) and form into an AP_REQ message along with an authenticator encrypted with the session key that was delivered from the KDC along with the service ticket. The steps below outline how to do this. * Get the service ticket and session key for the service the client is authenticating to. The following method will use the client's cache either returning a valid cached ticket, renewing a cached ticket with the KDC or requesting a new ticket from the KDC. Therefore the GetServiceTicket method can be continually used for the most efficient interaction with the KDC. ```go tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") ``` The steps after this will be specific to the application protocol but it will likely involve a client/server Authentication Protocol exchange (AP exchange). This will involve these steps: * Generate a new Authenticator and generate a sequence number and subkey: ```go auth, _ := types.NewAuthenticator(cl.Credentials.Realm, cl.Credentials.CName) etype, _ := crypto.GetEtype(key.KeyType) auth.GenerateSeqNumberAndSubKey(key.KeyType, etype.GetKeyByteSize()) ``` * Set the checksum on the authenticator The checksum is an application specific value. Set as follows: ```go auth.Cksum = types.Checksum{ CksumType: checksumIDint, Checksum: checksumBytesSlice, } ``` * Create the AP_REQ: ```go APReq, err := messages.NewAPReq(tkt, key, auth) ``` Now send the AP_REQ to the service. How this is done will be specific to the application use case. #### Changing a Client Password This feature uses the Microsoft Kerberos Password Change protocol (RFC 3244). This is implemented in Microsoft Active Directory and in MIT krb5kdc as of version 1.7. Typically the kpasswd server listens on port 464. Below is example code for how to use this feature: ```go cfg, err := config.Load("/path/to/config/file") if err != nil { panic(err.Error()) } kt, err := keytab.Load("/path/to/file.keytab") if err != nil { panic(err.Error()) } cl := client.NewWithKeytab("username", "REALM.COM", kt) cl.WithConfig(cfg) ok, err := cl.ChangePasswd("newpassword") if err != nil { panic(err.Error()) } if !ok { panic("failed to change password") } ``` The client kerberos config (krb5.conf) will need to have either the kpassd_server or admin_server defined in the relevant [realms] section. For example: ``` REALM.COM = { kdc = 127.0.0.1:88 kpasswd_server = 127.0.0.1:464 default_domain = realm.com } ``` See https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms for more information. #### Client Diagnostics In the event of issues the configuration of a client can be investigated with its ``Diagnostics`` method. This will check that the required enctypes defined in the client's krb5 config are available in its keytab. It will also check that KDCs can be resolved for the client's REALM. The error returned will contain details of any failed checks. The configuration details of the client will be written to the ``io.Writer`` provided. --- ### Kerberised Service #### SPNEGO/Kerberos HTTP Service A HTTP handler wrapper can be used to implement Kerberos SPNEGO authentication for web services. To configure the wrapper the keytab for the SPN and a Logger are required: ```go kt, err := keytab.Load("/path/to/file.keytab") l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile) ``` Create a handler function of the application's handling method (apphandler in the example below): ```go h := http.HandlerFunc(apphandler) ``` Configure the HTTP handler: ```go http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l))) ``` The handler to be wrapped and the keytab are required arguments. Additional optional settings can be provided, such as the logger shown above. Another example of optional settings may be that when using Active Directory where the SPN is mapped to a user account the keytab may contain an entry for this user account. In this case this should be specified as below with the ``KeytabPrincipal``: ```go http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.KeytabPrincipal(pn))) ``` ##### Session Management For efficiency reasons it is not desirable to authenticate on every call to a web service. Therefore most authenticated web applications implement some form of session with the user. Such sessions can be supported by passing a "session manager" into the ``SPNEGOKRB5Authenticate`` wrapper handler. In order to not demand a specific session manager solution, the session manager must implement a simple interface: ```go type SessionMgr interface { New(w http.ResponseWriter, r *http.Request, k string, v []byte) error Get(r *http.Request, k string) ([]byte, error) } ``` - New - creates a new session for the request and adds a piece of data (key/value pair) to the session - Get - extract from an existing session the value held within it under the key provided. This should return nil bytes or an error if there is no existing session. The session manager (sm) that implements this interface should then be passed to the ``SPNEGOKRB5Authenticate`` wrapper handler as below: ```go http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.SessionManager(sm))) ``` The ``httpServer.go`` source file in the examples directory shows how this can be used with the popular gorilla web toolkit. ##### Validating Users and Accessing Users' Details If authentication succeeds then the request's context will have a credentials objected added to it. This object implements the ``github.com/jcmturner/goidentity/identity`` interface. If Microsoft Active Directory is used as the KDC then additional ADCredentials are available in the ``credentials.Attributes`` map under the key ``credentials.AttributeKeyADCredentials``. For example the SIDs of the users group membership are available and can be used by your application for authorization. Checking and access the credentials within your application: ```go // Get a goidentity credentials object from the request's context creds := goidentity.FromHTTPRequestContext(r) // Check if it indicates it is authenticated if creds != nil && creds.Authenticated() { // Check for Active Directory attributes if ADCredsJSON, ok := creds.Attributes()[credentials.AttributeKeyADCredentials]; ok { ADCreds := new(credentials.ADCredentials) // Unmarshal the AD attributes err := json.Unmarshal([]byte(ADCredsJSON), ADCreds) if err == nil { // Now access the fields of the ADCredentials struct. For example: ADCreds.GroupMembershipSIDs } } } else { // Not authenticated user w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Authentication failed") } ``` #### Generic Kerberised Service - Validating Client Details To validate the AP_REQ sent by the client on the service side call this method: ```go import "github.com/jcmturner/gokrb5/v8/service" s := service.NewSettings(&kt) // kt is a keytab and optional settings can also be provided. if ok, creds, err := service.VerifyAPREQ(&APReq, s); ok { // Perform application specific actions // creds object has details about the client identity } ``` gokrb5-8.4.3/v8/asn1tools/000077500000000000000000000000001427031340300151745ustar00rootroot00000000000000gokrb5-8.4.3/v8/asn1tools/tools.go000066400000000000000000000057011427031340300166660ustar00rootroot00000000000000// Package asn1tools provides tools for managing ASN1 marshaled data. package asn1tools import ( "github.com/jcmturner/gofork/encoding/asn1" ) // MarshalLengthBytes returns the ASN1 encoded bytes for the length 'l' // // There are two forms: short (for lengths between 0 and 127), and long definite (for lengths between 0 and 2^1008 -1). // // Short form: One octet. Bit 8 has value "0" and bits 7-1 give the length. // // Long form: Two to 127 octets. Bit 8 of first octet has value "1" and bits 7-1 give the number of additional length octets. Second and following octets give the length, base 256, most significant digit first. func MarshalLengthBytes(l int) []byte { if l <= 127 { return []byte{byte(l)} } var b []byte p := 1 for i := 1; i < 127; { b = append([]byte{byte((l % (p * 256)) / p)}, b...) p = p * 256 l = l - l%p if l <= 0 { break } } return append([]byte{byte(128 + len(b))}, b...) } // GetLengthFromASN returns the length of a slice of ASN1 encoded bytes from the ASN1 length header it contains. func GetLengthFromASN(b []byte) int { if int(b[1]) <= 127 { return int(b[1]) } // The bytes that indicate the length lb := b[2 : 2+int(b[1])-128] base := 1 l := 0 for i := len(lb) - 1; i >= 0; i-- { l += int(lb[i]) * base base = base * 256 } return l } // GetNumberBytesInLengthHeader returns the number of bytes in the ASn1 header that indicate the length. func GetNumberBytesInLengthHeader(b []byte) int { if int(b[1]) <= 127 { return 1 } // The bytes that indicate the length return 1 + int(b[1]) - 128 } // AddASNAppTag adds an ASN1 encoding application tag value to the raw bytes provided. func AddASNAppTag(b []byte, tag int) []byte { r := asn1.RawValue{ Class: asn1.ClassApplication, IsCompound: true, Tag: tag, Bytes: b, } ab, _ := asn1.Marshal(r) return ab } /* // The Marshal method of golang's asn1 package does not enable you to define wrapping the output in an application tag. // This method adds that wrapping tag. func AddASNAppTag(b []byte, tag int) []byte { // The ASN1 wrapping consists of 2 bytes: // 1st byte -> Identifier Octet - Application Tag // 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here. // Application Tag: //| Bit: | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | //| Value: | 0 | 1 | 1 | From the RFC spec 4120 | //| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value | // Therefore the value of the byte is an integer = ( Application tag value + 96 ) //b = append(MarshalLengthBytes(int(b[1])+2), b...) b = append(MarshalLengthBytes(len(b)), b...) b = append([]byte{byte(96 + tag)}, b...) return b } */ gokrb5-8.4.3/v8/client/000077500000000000000000000000001427031340300145275ustar00rootroot00000000000000gokrb5-8.4.3/v8/client/ASExchange.go000066400000000000000000000156151427031340300170340ustar00rootroot00000000000000package client import ( "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/crypto/etype" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // ASExchange performs an AS exchange for the client to retrieve a TGT. func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) { if ok, err := cl.IsConfigured(); !ok { return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed") } // Set PAData if required err := setPAData(cl, nil, &ASReq) if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ") } b, err := ASReq.Marshal() if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ") } var ASRep messages.ASRep rb, err := cl.sendToKDC(b, realm) if err != nil { if e, ok := err.(messages.KRBError); ok { switch e.ErrorCode { case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED: // From now on assume this client will need to do this pre-auth and set the PAData cl.settings.assumePreAuthentication = true err = setPAData(cl, &e, &ASReq) if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required") } b, err := ASReq.Marshal() if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData") } rb, err = cl.sendToKDC(b, realm) if err != nil { if _, ok := err.(messages.KRBError); ok { return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC") } return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC") } case errorcode.KDC_ERR_WRONG_REALM: // Client referral https://tools.ietf.org/html/rfc6806.html#section-7 if referral > 5 { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded") } referral++ return cl.ASExchange(e.CRealm, ASReq, referral) default: return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC") } } else { return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC") } } err = ASRep.Unmarshal(rb) if err != nil { return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP") } if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok { return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect") } return ASRep, nil } // setPAData adds pre-authentication data to the AS_REQ. func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error { if !cl.settings.DisablePAFXFAST() { pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP} ASReq.PAData = append(ASReq.PAData, pa) } if cl.settings.AssumePreAuthentication() { // Identify the etype to use to encrypt the PA Data var et etype.EType var err error var key types.EncryptionKey var kvno int if krberr == nil { // This is not in response to an error from the KDC. It is preemptive or renewal // There is no KRB Error that tells us the etype to use etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated if etn == 0 { etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config } et, err = crypto.GetEtype(etn) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption") } key, kvno, err = cl.Key(et, 0, nil) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials") } } else { // Get the etype to use from the PA data in the KRBError e-data et, err = preAuthEType(krberr) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption") } cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use key, kvno, err = cl.Key(et, 0, krberr) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials") } } // Generate the PA data paTSb, err := types.GetPAEncTSEncAsnMarshalled() if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication") } paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, kvno) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp") } pb, err := paEncTS.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data") } pa := types.PAData{ PADataType: patype.PA_ENC_TIMESTAMP, PADataValue: pb, } // Look for and delete any exiting patype.PA_ENC_TIMESTAMP for i, pa := range ASReq.PAData { if pa.PADataType == patype.PA_ENC_TIMESTAMP { ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1] ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1] } } ASReq.PAData = append(ASReq.PAData, pa) } return nil } // preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC. func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) { //RFC 4120 5.2.7.5 covers the preference order of ETYPE-INFO2 and ETYPE-INFO. var etypeID int32 var pas types.PADataSequence e := pas.Unmarshal(krberr.EData) if e != nil { err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data") return } Loop: for _, pa := range pas { switch pa.PADataType { case patype.PA_ETYPE_INFO2: info, e := pa.GetETypeInfo2() if e != nil { err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data") return } etypeID = info[0].EType break Loop case patype.PA_ETYPE_INFO: info, e := pa.GetETypeInfo() if e != nil { err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data") return } etypeID = info[0].EType } } etype, e = crypto.GetEtype(etypeID) if e != nil { err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype") return } return etype, nil } gokrb5-8.4.3/v8/client/TGSExchange.go000066400000000000000000000115531427031340300171630ustar00rootroot00000000000000package client import ( "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN. func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) { tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal) if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ") } return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0) } // TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP. // Referrals are automatically handled. // The client's cache is updated with the ticket received. func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) { var tgsRep messages.TGSRep b, err := tgsReq.Marshal() if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ") } r, err := cl.sendToKDC(b, kdcRealm) if err != nil { if _, ok := err.(messages.KRBError); ok { return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString()) } return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC") } err = tgsRep.Unmarshal(r) if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP") } err = tgsRep.DecryptEncPart(sessionKey) if err != nil { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP") } if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok { return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid") } if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) { if referral > 5 { return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded") } // Server referral https://tools.ietf.org/html/rfc6806.html#section-8 // The TGS Rep contains a TGT for another domain as the service resides in that domain. cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart) realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1] referral++ if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 { tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0]) if err != nil { return tgsReq, tgsRep, err } } tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal) if err != nil { return tgsReq, tgsRep, err } return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral) } cl.cache.addEntry( tgsRep.Ticket, tgsRep.DecryptedEncPart.AuthTime, tgsRep.DecryptedEncPart.StartTime, tgsRep.DecryptedEncPart.EndTime, tgsRep.DecryptedEncPart.RenewTill, tgsRep.DecryptedEncPart.Key, ) cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime) return tgsReq, tgsRep, err } // GetServiceTicket makes a request to get a service ticket for the SPN specified // SPN format: / Eg. HTTP/www.example.com // The ticket will be added to the client's ticket cache func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) { var tkt messages.Ticket var skey types.EncryptionKey if tkt, skey, ok := cl.GetCachedTicket(spn); ok { // Already a valid ticket in the cache return tkt, skey, nil } princ := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) realm := cl.spnRealm(princ) // if we don't know the SPN's realm, ask the client realm's KDC if realm == "" { realm = cl.Credentials.Realm() } tgt, skey, err := cl.sessionTGT(realm) if err != nil { return tkt, skey, err } _, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false) if err != nil { return tkt, skey, err } return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil } gokrb5-8.4.3/v8/client/cache.go000066400000000000000000000066211427031340300161260ustar00rootroot00000000000000package client import ( "encoding/json" "errors" "sort" "sync" "time" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // Cache for service tickets held by the client. type Cache struct { Entries map[string]CacheEntry mux sync.RWMutex } // CacheEntry holds details for a cache entry. type CacheEntry struct { SPN string Ticket messages.Ticket `json:"-"` AuthTime time.Time StartTime time.Time EndTime time.Time RenewTill time.Time SessionKey types.EncryptionKey `json:"-"` } // NewCache creates a new client ticket cache instance. func NewCache() *Cache { return &Cache{ Entries: map[string]CacheEntry{}, } } // getEntry returns a cache entry that matches the SPN. func (c *Cache) getEntry(spn string) (CacheEntry, bool) { c.mux.RLock() defer c.mux.RUnlock() e, ok := (*c).Entries[spn] return e, ok } // JSON returns information about the cached service tickets in a JSON format. func (c *Cache) JSON() (string, error) { c.mux.RLock() defer c.mux.RUnlock() var es []CacheEntry keys := make([]string, 0, len(c.Entries)) for k := range c.Entries { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { es = append(es, c.Entries[k]) } b, err := json.MarshalIndent(&es, "", " ") if err != nil { return "", err } return string(b), nil } // addEntry adds a ticket to the cache. func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry { spn := tkt.SName.PrincipalNameString() c.mux.Lock() defer c.mux.Unlock() (*c).Entries[spn] = CacheEntry{ SPN: spn, Ticket: tkt, AuthTime: authTime, StartTime: startTime, EndTime: endTime, RenewTill: renewTill, SessionKey: sessionKey, } return c.Entries[spn] } // clear deletes all the cache entries func (c *Cache) clear() { c.mux.Lock() defer c.mux.Unlock() for k := range c.Entries { delete(c.Entries, k) } } // RemoveEntry removes the cache entry for the defined SPN. func (c *Cache) RemoveEntry(spn string) { c.mux.Lock() defer c.mux.Unlock() delete(c.Entries, spn) } // GetCachedTicket returns a ticket from the cache for the SPN. // Only a ticket that is currently valid will be returned. func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) { if e, ok := cl.cache.getEntry(spn); ok { //If within time window of ticket return it if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) { cl.Log("ticket received from cache for %s", spn) return e.Ticket, e.SessionKey, true } else if time.Now().UTC().Before(e.RenewTill) { e, err := cl.renewTicket(e) if err != nil { return e.Ticket, e.SessionKey, false } return e.Ticket, e.SessionKey, true } } var tkt messages.Ticket var key types.EncryptionKey return tkt, key, false } // renewTicket renews a cache entry ticket. // To renew from outside the client package use GetCachedTicket func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) { spn := e.Ticket.SName _, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true) if err != nil { return e, err } e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString()) if !ok { return e, errors.New("ticket was not added to cache") } cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime) return e, nil } gokrb5-8.4.3/v8/client/cache_test.go000066400000000000000000000107211427031340300171610ustar00rootroot00000000000000package client import ( "fmt" "sync" "testing" "time" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestCache_addEntry_getEntry_remove_clear(t *testing.T) { t.Parallel() c := NewCache() cnt := 10 var wg sync.WaitGroup for i := 0; i < cnt; i++ { wg.Add(1) tkt := messages.Ticket{ SName: types.PrincipalName{ NameType: 1, NameString: []string{fmt.Sprintf("%d", i), "test.cache"}, }, } key := types.EncryptionKey{ KeyType: 1, KeyValue: []byte{byte(i)}, } go func(i int) { e := c.addEntry(tkt, time.Unix(int64(0+i), 0).UTC(), time.Unix(int64(10+i), 0).UTC(), time.Unix(int64(20+i), 0).UTC(), time.Unix(int64(30+i), 0).UTC(), key) assert.Equal(t, fmt.Sprintf("%d/test.cache", i), e.SPN, "SPN cache key not as expected") wg.Done() }(i) } wg.Wait() for i := 0; i < cnt; i++ { wg.Add(1) go func(i int) { e, ok := c.getEntry(fmt.Sprintf("%d/test.cache", i)) assert.True(t, ok, "cache entry %d was not found", i) assert.Equal(t, time.Unix(int64(0+i), 0).UTC(), e.AuthTime, "auth time not as expected") assert.Equal(t, time.Unix(int64(10+i), 0).UTC(), e.StartTime, "start time not as expected") assert.Equal(t, time.Unix(int64(20+i), 0).UTC(), e.EndTime, "end time not as expected") assert.Equal(t, time.Unix(int64(30+i), 0).UTC(), e.RenewTill, "renew time not as expected") assert.Equal(t, []string{fmt.Sprintf("%d", i), "test.cache"}, e.Ticket.SName.NameString, "ticket not correct") assert.Equal(t, []byte{byte(i)}, e.SessionKey.KeyValue, "session key not correct") wg.Done() }(i) } wg.Wait() _, ok := c.getEntry(fmt.Sprintf("%d/test.cache", cnt+1)) assert.False(t, ok, "entry found in cache when it shouldn't have been") // Remove just the even entries for i := 0; i < cnt; i += 2 { wg.Add(1) go func(i int) { c.RemoveEntry(fmt.Sprintf("%d/test.cache", i)) wg.Done() }(i) } wg.Wait() for i := 0; i < cnt; i++ { wg.Add(1) go func(i int) { if i%2 == 0 { _, ok := c.getEntry(fmt.Sprintf("%d/test.cache", cnt+1)) assert.False(t, ok, "entry %d found in cache when it shouldn't have been", i) } else { e, ok := c.getEntry(fmt.Sprintf("%d/test.cache", i)) assert.True(t, ok, "cache entry %d was not found", i) assert.Equal(t, time.Unix(int64(0+i), 0).UTC(), e.AuthTime, "auth time not as expected") assert.Equal(t, time.Unix(int64(10+i), 0).UTC(), e.StartTime, "start time not as expected") assert.Equal(t, time.Unix(int64(20+i), 0).UTC(), e.EndTime, "end time not as expected") assert.Equal(t, time.Unix(int64(30+i), 0).UTC(), e.RenewTill, "renew time not as expected") assert.Equal(t, []string{fmt.Sprintf("%d", i), "test.cache"}, e.Ticket.SName.NameString, "ticket not correct") assert.Equal(t, []byte{byte(i)}, e.SessionKey.KeyValue, "session key not correct") } wg.Done() }(i) } wg.Wait() // Clear the cache c.clear() for i := 0; i < cnt; i++ { wg.Add(1) go func(i int) { _, ok := c.getEntry(fmt.Sprintf("%d/test.cache", cnt+1)) assert.False(t, ok, "entry %d found in cache when it shouldn't have been", i) wg.Done() }(i) } wg.Wait() } func TestCache_JSON(t *testing.T) { t.Parallel() c := NewCache() cnt := 3 for i := 0; i < cnt; i++ { tkt := messages.Ticket{ SName: types.PrincipalName{ NameType: 1, NameString: []string{fmt.Sprintf("%d", i), "test.cache"}, }, } key := types.EncryptionKey{ KeyType: 1, KeyValue: []byte{byte(i)}, } e := c.addEntry(tkt, time.Unix(int64(0+i), 0).UTC(), time.Unix(int64(10+i), 0).UTC(), time.Unix(int64(20+i), 0).UTC(), time.Unix(int64(30+i), 0).UTC(), key) assert.Equal(t, fmt.Sprintf("%d/test.cache", i), e.SPN, "SPN cache key not as expected") } expected := `[ { "SPN": "0/test.cache", "AuthTime": "1970-01-01T00:00:00Z", "StartTime": "1970-01-01T00:00:10Z", "EndTime": "1970-01-01T00:00:20Z", "RenewTill": "1970-01-01T00:00:30Z" }, { "SPN": "1/test.cache", "AuthTime": "1970-01-01T00:00:01Z", "StartTime": "1970-01-01T00:00:11Z", "EndTime": "1970-01-01T00:00:21Z", "RenewTill": "1970-01-01T00:00:31Z" }, { "SPN": "2/test.cache", "AuthTime": "1970-01-01T00:00:02Z", "StartTime": "1970-01-01T00:00:12Z", "EndTime": "1970-01-01T00:00:22Z", "RenewTill": "1970-01-01T00:00:32Z" } ]` j, err := c.JSON() if err != nil { t.Errorf("error getting json output of cache: %v", err) } assert.Equal(t, expected, j, "json output not as expected") } gokrb5-8.4.3/v8/client/client.go000066400000000000000000000254211427031340300163400ustar00rootroot00000000000000// Package client provides a client library and methods for Kerberos 5 authentication. package client import ( "encoding/json" "errors" "fmt" "io" "strings" "time" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/crypto/etype" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // Client side configuration and state. type Client struct { Credentials *credentials.Credentials Config *config.Config settings *Settings sessions *sessions cache *Cache } // NewWithPassword creates a new client from a password credential. // Set the realm to empty string to use the default realm from config. func NewWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client { creds := credentials.New(username, realm) return &Client{ Credentials: creds.WithPassword(password), Config: krb5conf, settings: NewSettings(settings...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } } // NewWithKeytab creates a new client from a keytab credential. func NewWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client { creds := credentials.New(username, realm) return &Client{ Credentials: creds.WithKeytab(kt), Config: krb5conf, settings: NewSettings(settings...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } } // NewFromCCache create a client from a populated client cache. // // WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires. func NewFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) { cl := &Client{ Credentials: c.GetClientCredentials(), Config: krb5conf, settings: NewSettings(settings...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } spn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", c.DefaultPrincipal.Realm}, } cred, ok := c.GetEntry(spn) if !ok { return cl, errors.New("TGT not found in CCache") } var tgt messages.Ticket err := tgt.Unmarshal(cred.Ticket) if err != nil { return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err) } cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{ realm: c.DefaultPrincipal.Realm, authTime: cred.AuthTime, endTime: cred.EndTime, renewTill: cred.RenewTill, tgt: tgt, sessionKey: cred.Key, } for _, cred := range c.GetEntries() { var tkt messages.Ticket err = tkt.Unmarshal(cred.Ticket) if err != nil { return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err) } cl.cache.addEntry( tkt, cred.AuthTime, cred.StartTime, cred.EndTime, cred.RenewTill, cred.Key, ) } return cl, nil } // Key returns the client's encryption key for the specified encryption type and its kvno (kvno of zero will find latest). // The key can be retrieved either from the keytab or generated from the client's password. // If the client has both a keytab and a password defined the keytab is favoured as the source for the key // A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive // the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument. func (cl *Client) Key(etype etype.EType, kvno int, krberr *messages.KRBError) (types.EncryptionKey, int, error) { if cl.Credentials.HasKeytab() && etype != nil { return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), kvno, etype.GetETypeID()) } else if cl.Credentials.HasPassword() { if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED { var pas types.PADataSequence err := pas.Unmarshal(krberr.EData) if err != nil { return types.EncryptionKey{}, 0, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err) } key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas) return key, 0, err } key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{}) return key, 0, err } return types.EncryptionKey{}, 0, errors.New("credential has neither keytab or password to generate key") } // IsConfigured indicates if the client has the values required set. func (cl *Client) IsConfigured() (bool, error) { if cl.Credentials.UserName() == "" { return false, errors.New("client does not have a username") } if cl.Credentials.Domain() == "" { return false, errors.New("client does not have a define realm") } // Client needs to have either a password, keytab or a session already (later when loading from CCache) if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() { authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil || authTime.IsZero() { return false, errors.New("client has neither a keytab nor a password set and no session") } } if !cl.Config.LibDefaults.DNSLookupKDC { for _, r := range cl.Config.Realms { if r.Realm == cl.Credentials.Domain() { if len(r.KDC) > 0 { return true, nil } return false, errors.New("client krb5 config does not have any defined KDCs for the default realm") } } } return true, nil } // Login the client with the KDC via an AS exchange. func (cl *Client) Login() error { if ok, err := cl.IsConfigured(); !ok { return err } if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() { _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session") } if time.Now().UTC().After(endTime) { return krberror.New(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session") } // no credentials but there is a session with tgt already return nil } ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName()) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ") } ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0) if err != nil { return err } cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart) return nil } // AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT. func (cl *Client) AffirmLogin() error { _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil || time.Now().UTC().After(endTime) { err := cl.Login() if err != nil { return fmt.Errorf("could not get valid TGT for client's realm: %v", err) } } return nil } // realmLogin obtains or renews a TGT and establishes a session for the realm specified. func (cl *Client) realmLogin(realm string) error { if realm == cl.Credentials.Domain() { return cl.Login() } _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain()) if err != nil || time.Now().UTC().After(endTime) { err := cl.Login() if err != nil { return fmt.Errorf("could not get valid TGT for client's realm: %v", err) } } tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain()) if err != nil { return err } spn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } _, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false) if err != nil { return err } cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart) return nil } // Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client. func (cl *Client) Destroy() { creds := credentials.New("", "") cl.sessions.destroy() cl.cache.clear() cl.Credentials = creds cl.Log("client destroyed") } // Diagnostics runs a set of checks that the client is properly configured and writes details to the io.Writer provided. func (cl *Client) Diagnostics(w io.Writer) error { cl.Print(w) var errs []string if cl.Credentials.HasKeytab() { var loginRealmEncTypes []int32 for _, e := range cl.Credentials.Keytab().Entries { if e.Principal.Realm == cl.Credentials.Realm() { loginRealmEncTypes = append(loginRealmEncTypes, e.Key.KeyType) } } for _, et := range cl.Config.LibDefaults.DefaultTktEnctypeIDs { var etInKt bool for _, val := range loginRealmEncTypes { if val == et { etInKt = true break } } if !etInKt { errs = append(errs, fmt.Sprintf("default_tkt_enctypes specifies %d but this enctype is not available in the client's keytab", et)) } } for _, et := range cl.Config.LibDefaults.PreferredPreauthTypes { var etInKt bool for _, val := range loginRealmEncTypes { if int(val) == et { etInKt = true break } } if !etInKt { errs = append(errs, fmt.Sprintf("preferred_preauth_types specifies %d but this enctype is not available in the client's keytab", et)) } } } udpCnt, udpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false) if err != nil { errs = append(errs, fmt.Sprintf("error when resolving KDCs for UDP communication: %v", err)) } if udpCnt < 1 { errs = append(errs, "no KDCs resolved for communication via UDP.") } else { b, _ := json.MarshalIndent(&udpKDC, "", " ") fmt.Fprintf(w, "UDP KDCs: %s\n", string(b)) } tcpCnt, tcpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false) if err != nil { errs = append(errs, fmt.Sprintf("error when resolving KDCs for TCP communication: %v", err)) } if tcpCnt < 1 { errs = append(errs, "no KDCs resolved for communication via TCP.") } else { b, _ := json.MarshalIndent(&tcpKDC, "", " ") fmt.Fprintf(w, "TCP KDCs: %s\n", string(b)) } if errs == nil || len(errs) < 1 { return nil } err = fmt.Errorf(strings.Join(errs, "\n")) return err } // Print writes the details of the client to the io.Writer provided. func (cl *Client) Print(w io.Writer) { c, _ := cl.Credentials.JSON() fmt.Fprintf(w, "Credentials:\n%s\n", c) s, _ := cl.sessions.JSON() fmt.Fprintf(w, "TGT Sessions:\n%s\n", s) c, _ = cl.cache.JSON() fmt.Fprintf(w, "Service ticket cache:\n%s\n", c) s, _ = cl.settings.JSON() fmt.Fprintf(w, "Settings:\n%s\n", s) j, _ := cl.Config.JSON() fmt.Fprintf(w, "Krb5 config:\n%s\n", j) k, _ := cl.Credentials.Keytab().JSON() fmt.Fprintf(w, "Keytab:\n%s\n", k) } gokrb5-8.4.3/v8/client/client_ad_integration_test.go000066400000000000000000000131771427031340300224530ustar00rootroot00000000000000package client import ( "bytes" "encoding/hex" "log" "testing" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestClient_SuccessfulLogin_AD(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF_AD) cl := NewWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true)) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } } func TestClient_SuccessfulLogin_AD_Without_PreAuth(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER3_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF_AD) cl := NewWithKeytab("testuser3", "USER.GOKRB5", kt, c, DisablePAFXFAST(true)) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } } func TestClient_GetServiceTicket_AD(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF_AD) cl := NewWithKeytab("testuser1", "USER.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } spn := "HTTP/user2.user.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("Error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_USER_GOKRB5) skt := keytab.New() skt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser2"}} err = tkt.DecryptEncPart(skt, &sname) if err != nil { t.Errorf("could not decrypt service ticket: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(skt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "should have PAC") assert.Equal(t, "USER", pac.KerbValidationInfo.LogonDomainName.String(), "domain name in PAC not correct") } func TestClient_GetServiceTicket_AD_TRUST_USER_DOMAIN(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF_AD) c.LibDefaults.Canonicalize = true c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} cl := NewWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true)) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } spn := "HTTP/host.res.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("Error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_SYSHTTP_RES_GOKRB5) skt := keytab.New() skt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}} err = tkt.DecryptEncPart(skt, &sname) if err != nil { t.Errorf("error decrypting ticket with service keytab: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(skt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "Did not find PAC in service ticket") assert.Equal(t, "testuser1", pac.KerbValidationInfo.EffectiveName.Value, "PAC value not parsed") } func TestClient_GetServiceTicket_AD_USER_DOMAIN(t *testing.T) { test.AD(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF_AD) c.LibDefaults.Canonicalize = true c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]} cl := NewWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true)) err := cl.Login() if err != nil { t.Fatalf("Error on login: %v\n", err) } spn := "HTTP/user2.user.gokrb5" tkt, _, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("Error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) //assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_USER_GOKRB5) skt := keytab.New() skt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser2"}} err = tkt.DecryptEncPart(skt, &sname) if err != nil { t.Errorf("error decrypting ticket with service keytab: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(skt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "Did not find PAC in service ticket") assert.Equal(t, "testuser1", pac.KerbValidationInfo.EffectiveName.Value, "PAC value not parsed") } gokrb5-8.4.3/v8/client/client_dns_test.go000066400000000000000000000015631427031340300202440ustar00rootroot00000000000000package client import ( "encoding/hex" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "testing" ) func TestClient_Login_DNSKDCs(t *testing.T) { test.Privileged(t) //ns := os.Getenv("DNSUTILS_OVERRIDE_NS") //if ns == "" { // os.Setenv("DNSUTILS_OVERRIDE_NS", testdata.TEST_NS) //} c, _ := config.NewFromString(testdata.KRB5_CONF) // Set to lookup KDCs in DNS c.LibDefaults.DNSLookupKDC = true //Blank out the KDCs to ensure they are not being used c.Realms = []config.Realm{} b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) cl := NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on logging in using DNS lookup of KDCs: %v\n", err) } } gokrb5-8.4.3/v8/client/client_integration_test.go000066400000000000000000000532321427031340300220030ustar00rootroot00000000000000package client_test import ( "bytes" "encoding/hex" "errors" "io" "net/http" "os" "os/exec" "os/user" "runtime" "testing" "time" "fmt" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" "strings" "sync" ) func TestClient_SuccessfulLogin_Keytab(t *testing.T) { test.Integration(t) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) var tests = []string{ testdata.KDC_PORT_TEST_GOKRB5, testdata.KDC_PORT_TEST_GOKRB5_OLD, testdata.KDC_PORT_TEST_GOKRB5_LASTEST, } for _, tst := range tests { c.Realms[0].KDC = []string{addr + ":" + tst} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on logging in with KDC %s: %v\n", tst, err) } } } func TestClient_SuccessfulLogin_Password(t *testing.T) { test.Integration(t) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c, _ := config.NewFromString(testdata.KRB5_CONF) var tests = []string{ testdata.KDC_PORT_TEST_GOKRB5, testdata.KDC_PORT_TEST_GOKRB5_OLD, testdata.KDC_PORT_TEST_GOKRB5_LASTEST, } for _, tst := range tests { c.Realms[0].KDC = []string{addr + ":" + tst} cl := client.NewWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue", c) err := cl.Login() if err != nil { t.Errorf("error on logging in with KDC %s: %v\n", tst, err) } } } func TestClient_SuccessfulLogin_TCPOnly(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} c.LibDefaults.UDPPreferenceLimit = 1 cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } } func TestClient_ASExchange_TGSExchange_EncTypes_Keytab(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5_LASTEST} var tests = []string{ "des3-cbc-sha1-kd", "aes128-cts-hmac-sha1-96", "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha256-128", "aes256-cts-hmac-sha384-192", "rc4-hmac", } for _, tst := range tests { c.LibDefaults.DefaultTktEnctypes = []string{tst} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[tst]} c.LibDefaults.DefaultTGSEnctypes = []string{tst} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[tst]} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on login using enctype %s: %v\n", tst, err) } tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") if err != nil { t.Errorf("error in TGS exchange using enctype %s: %v", tst, err) } assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", tst) assert.Equal(t, etypeID.ETypesByName[tst], key.KeyType, "Key is not for enctype %s", tst) } } func TestClient_ASExchange_TGSExchange_EncTypes_Password(t *testing.T) { test.Integration(t) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5_LASTEST} var tests = []string{ "des3-cbc-sha1-kd", "aes128-cts-hmac-sha1-96", "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha256-128", "aes256-cts-hmac-sha384-192", "rc4-hmac", } for _, tst := range tests { c.LibDefaults.DefaultTktEnctypes = []string{tst} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[tst]} c.LibDefaults.DefaultTGSEnctypes = []string{tst} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[tst]} cl := client.NewWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue", c) err := cl.Login() if err != nil { t.Errorf("error on login using enctype %s: %v\n", tst, err) } tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") if err != nil { t.Errorf("error in TGS exchange using enctype %s: %v", tst, err) } assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", tst) assert.Equal(t, etypeID.ETypesByName[tst], key.KeyType, "Key is not for enctype %s", tst) } } func TestClient_FailedLogin(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5_WRONGPASSWD) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err == nil { t.Fatal("login with incorrect password did not error") } } func TestClient_SuccessfulLogin_UserRequiringPreAuth(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER2_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := client.NewWithKeytab("testuser2", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } } func TestClient_SuccessfulLogin_UserRequiringPreAuth_TCPOnly(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER2_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} c.LibDefaults.UDPPreferenceLimit = 1 cl := client.NewWithKeytab("testuser2", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } } func TestClient_NetworkTimeout(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) c.Realms[0].KDC = []string{testdata.KDC_IP_TEST_GOKRB5_BADADDR + ":88"} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err == nil { t.Fatal("login with incorrect KDC address did not error") } } func TestClient_NetworkTryNextKDC(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } // Two out fo three times this should fail the first time. // So will run login twice to expect at least once the first time it will be to a bad KDC c.Realms[0].KDC = []string{testdata.KDC_IP_TEST_GOKRB5_BADADDR + ":88", testdata.KDC_IP_TEST_GOKRB5_BADADDR + ":88", addr + ":" + testdata.KDC_PORT_TEST_GOKRB5, } cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatal("login failed") } err = cl.Login() if err != nil { t.Fatal("login failed") } } func TestClient_GetServiceTicket(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) //Check cache use - should get the same values back again tkt2, key2, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, tkt.EncPart.Cipher, tkt2.EncPart.Cipher) assert.Equal(t, key.KeyValue, key2.KeyValue) } func TestClient_GetServiceTicket_CanonicalizeTrue(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) c.LibDefaults.Canonicalize = true addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) //Check cache use - should get the same values back again tkt2, key2, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, tkt.EncPart.Cipher, tkt2.EncPart.Cipher) assert.Equal(t, key.KeyValue, key2.KeyValue) } func TestClient_GetServiceTicket_InvalidSPN(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "host.test.gokrb5" _, _, err = cl.GetServiceTicket(spn) assert.NotNil(t, err, "Expected unknown principal error") assert.True(t, strings.Contains(err.Error(), "KDC_ERR_S_PRINCIPAL_UNKNOWN"), "Error text not as expected") } func TestClient_GetServiceTicket_OlderKDC(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5_OLD} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) } func TestMultiThreadedClientUse(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go func() { defer wg.Done() err := cl.Login() if err != nil { panic(err) } }() } wg.Wait() var wg2 sync.WaitGroup wg2.Add(5) for i := 0; i < 5; i++ { go func() { defer wg2.Done() err := spnegoGet(cl) if err != nil { panic(err) } }() } wg2.Wait() } func spnegoGet(cl *client.Client) error { url := os.Getenv("TEST_HTTP_URL") if url == "" { url = testdata.TEST_HTTP_URL } r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil) httpResp, err := http.DefaultClient.Do(r) if err != nil { return fmt.Errorf("request error: %v\n", err) } if httpResp.StatusCode != http.StatusUnauthorized { return errors.New("did not get unauthorized code when no SPNEGO header set") } err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { return fmt.Errorf("error setting client SPNEGO header: %v", err) } httpResp, err = http.DefaultClient.Do(r) if err != nil { return fmt.Errorf("request error: %v\n", err) } if httpResp.StatusCode != http.StatusOK { return errors.New("did not get OK code when SPNEGO header set") } return nil } func TestNewFromCCache(t *testing.T) { test.Integration(t) b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatalf("error decoding test data") } cc := new(credentials.CCache) err = cc.Unmarshal(b) if err != nil { t.Fatal("error getting test CCache") } c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl, err := client.NewFromCCache(cc, c) if err != nil { t.Fatalf("error creating client from CCache: %v", err) } if ok, err := cl.IsConfigured(); !ok { t.Fatalf("client was not configured from CCache: %v", err) } } // Login to the TEST.GOKRB5 domain and request service ticket for resource in the RESDOM.GOKRB5 domain. // There is a trust between the two domains. func TestClient_GetServiceTicket_Trusted_Resource_Domain(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } for i, r := range c.Realms { if r.Realm == "TEST.GOKRB5" { c.Realms[i].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} } if r.Realm == "RESDOM.GOKRB5" { c.Realms[i].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5_RESDOM} } } c.LibDefaults.DefaultRealm = "TEST.GOKRB5" cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) c.LibDefaults.DefaultTktEnctypes = []string{"aes256-cts-hmac-sha1-96"} c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["aes256-cts-hmac-sha1-96"]} c.LibDefaults.DefaultTGSEnctypes = []string{"aes256-cts-hmac-sha1-96"} c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["aes256-cts-hmac-sha1-96"]} err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.resdom.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], key.KeyType) b, _ = hex.DecodeString(testdata.KEYTAB_SYSHTTP_RESDOM_GOKRB5) skt := keytab.New() skt.Unmarshal(b) err = tkt.DecryptEncPart(skt, nil) if err != nil { t.Errorf("error decrypting ticket with service keytab: %v", err) } } const ( kinitCmd = "kinit" kvnoCmd = "kvno" spn = "HTTP/host.test.gokrb5" ) func login() error { file, err := os.Create("/etc/krb5.conf") if err != nil { return fmt.Errorf("cannot open krb5.conf: %v", err) } defer file.Close() fmt.Fprintf(file, testdata.KRB5_CONF) cmd := exec.Command(kinitCmd, "testuser1@TEST.GOKRB5") stdinR, stdinW := io.Pipe() stderrR, stderrW := io.Pipe() cmd.Stdin = stdinR cmd.Stderr = stderrW err = cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kinitCmd, err) } go func() { io.WriteString(stdinW, "passwordvalue") stdinW.Close() }() errBuf := new(bytes.Buffer) go func() { io.Copy(errBuf, stderrR) stderrR.Close() }() err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v stderr: %s", kinitCmd, err, string(errBuf.Bytes())) } return nil } func getServiceTkt() error { cmd := exec.Command(kvnoCmd, spn) err := cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kvnoCmd, err) } err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v", kvnoCmd, err) } return nil } func loadCCache() (*credentials.CCache, error) { usr, _ := user.Current() cpath := "/tmp/krb5cc_" + usr.Uid return credentials.LoadCCache(cpath) } func TestGetServiceTicketFromCCacheTGT(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } cfg, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } cfg.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl, err := client.NewFromCCache(c, cfg) if err != nil { t.Fatalf("error generating client from ccache: %v", err) } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) //Check cache use - should get the same values back again tkt2, key2, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } assert.Equal(t, tkt.EncPart.Cipher, tkt2.EncPart.Cipher) assert.Equal(t, key.KeyValue, key2.KeyValue) url := os.Getenv("TEST_HTTP_URL") if url == "" { url = testdata.TEST_HTTP_URL } r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "status code in response to client SPNEGO request not as expected") } func TestGetServiceTicketFromCCacheWithoutKDC(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } err = getServiceTkt() if err != nil { t.Fatalf("error getting service ticket: %v", err) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } cfg, _ := config.NewFromString("...") cl, err := client.NewFromCCache(c, cfg) if err != nil { t.Fatalf("error generating client from ccache: %v", err) } url := os.Getenv("TEST_HTTP_URL") if url == "" { url = testdata.TEST_HTTP_URL } r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "status code in response to client SPNEGO request not as expected") } func TestClient_ChangePasswd(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} c.Realms[0].KPasswdServer = []string{addr + ":464"} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) ok, err := cl.ChangePasswd("newpassword") if err != nil { t.Fatalf("error changing password: %v", err) } assert.True(t, ok, "password was not changed") cl = client.NewWithPassword("testuser1", "TEST.GOKRB5", "newpassword", c) ok, err = cl.ChangePasswd(testdata.TESTUSER_PASSWORD) if err != nil { t.Fatalf("error changing password: %v", err) } assert.True(t, ok, "password was not changed back") cl = client.NewWithPassword("testuser1", "TEST.GOKRB5", testdata.TESTUSER_PASSWORD, c) err = cl.Login() if err != nil { t.Fatalf("Could not log back in after reverting password: %v", err) } } func TestClient_Destroy(t *testing.T) { test.Integration(t) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5_SHORTTICKETS} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("error on login: %v\n", err) } spn := "HTTP/host.test.gokrb5" _, _, err = cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } n := runtime.NumGoroutine() time.Sleep(time.Second * 60) cl.Destroy() time.Sleep(time.Second * 5) assert.True(t, runtime.NumGoroutine() < n, "auto-renewal goroutine was not stopped when client destroyed") is, _ := cl.IsConfigured() assert.False(t, is, "client is still configured after it was destroyed") } gokrb5-8.4.3/v8/client/client_test.go000066400000000000000000000007611427031340300173770ustar00rootroot00000000000000package client import ( "testing" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/keytab" ) func TestAssumePreauthentication(t *testing.T) { t.Parallel() cl := NewWithKeytab("username", "REALM", &keytab.Keytab{}, &config.Config{}, AssumePreAuthentication(true)) if !cl.settings.assumePreAuthentication { t.Fatal("assumePreAuthentication should be true") } if !cl.settings.AssumePreAuthentication() { t.Fatal("AssumePreAuthentication() should be true") } } gokrb5-8.4.3/v8/client/network.go000066400000000000000000000143021427031340300165470ustar00rootroot00000000000000package client import ( "encoding/binary" "fmt" "io" "net" "strings" "time" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/messages" ) // SendToKDC performs network actions to send data to the KDC. func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) { var rb []byte if cl.Config.LibDefaults.UDPPreferenceLimit == 1 { //1 means we should always use TCP rb, errtcp := cl.sendKDCTCP(realm, b) if errtcp != nil { if e, ok := errtcp.(messages.KRBError); ok { return rb, e } return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp) } return rb, nil } if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit { //Try UDP first, TCP second rb, errudp := cl.sendKDCUDP(realm, b) if errudp != nil { if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG { // Got a KRBError from KDC // If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP. return rb, e } // Try TCP r, errtcp := cl.sendKDCTCP(realm, b) if errtcp != nil { if e, ok := errtcp.(messages.KRBError); ok { // Got a KRBError return r, e } return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp) } rb = r } return rb, nil } //Try TCP first, UDP second rb, errtcp := cl.sendKDCTCP(realm, b) if errtcp != nil { if e, ok := errtcp.(messages.KRBError); ok { // Got a KRBError from KDC so returning and not trying UDP. return rb, e } rb, errudp := cl.sendKDCUDP(realm, b) if errudp != nil { if e, ok := errudp.(messages.KRBError); ok { // Got a KRBError return rb, e } return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp) } } return rb, nil } // sendKDCUDP sends bytes to the KDC via UDP. func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) { var r []byte _, kdcs, err := cl.Config.GetKDCs(realm, false) if err != nil { return r, err } r, err = dialSendUDP(kdcs, b) if err != nil { return r, err } return checkForKRBError(r) } // dialSendUDP establishes a UDP connection to a KDC. func dialSendUDP(kdcs map[int]string, b []byte) ([]byte, error) { var errs []string for i := 1; i <= len(kdcs); i++ { udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i]) if err != nil { errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err)) continue } conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second) if err != nil { errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err)) continue } if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err)) continue } // conn is guaranteed to be a UDPConn rb, err := sendUDP(conn.(*net.UDPConn), b) if err != nil { errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err)) continue } return rb, nil } return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; ")) } // sendUDP sends bytes to connection over UDP. func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) { var r []byte defer conn.Close() _, err := conn.Write(b) if err != nil { return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err) } udpbuf := make([]byte, 4096) n, _, err := conn.ReadFrom(udpbuf) r = udpbuf[:n] if err != nil { return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err) } if len(r) < 1 { return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String()) } return r, nil } // sendKDCTCP sends bytes to the KDC via TCP. func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) { var r []byte _, kdcs, err := cl.Config.GetKDCs(realm, true) if err != nil { return r, err } r, err = dialSendTCP(kdcs, b) if err != nil { return r, err } return checkForKRBError(r) } // dialKDCTCP establishes a TCP connection to a KDC. func dialSendTCP(kdcs map[int]string, b []byte) ([]byte, error) { var errs []string for i := 1; i <= len(kdcs); i++ { tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i]) if err != nil { errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err)) continue } conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second) if err != nil { errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err)) continue } if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err)) continue } // conn is guaranteed to be a TCPConn rb, err := sendTCP(conn.(*net.TCPConn), b) if err != nil { errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err)) continue } return rb, nil } return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; ")) } // sendTCP sends bytes to connection over TCP. func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) { defer conn.Close() var r []byte // RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order. hb := make([]byte, 4, 4) binary.BigEndian.PutUint32(hb, uint32(len(b))) b = append(hb, b...) _, err := conn.Write(b) if err != nil { return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err) } sh := make([]byte, 4, 4) _, err = conn.Read(sh) if err != nil { return r, fmt.Errorf("error reading response size header: %v", err) } s := binary.BigEndian.Uint32(sh) rb := make([]byte, s, s) _, err = io.ReadFull(conn, rb) if err != nil { return r, fmt.Errorf("error reading response: %v", err) } if len(rb) < 1 { return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String()) } return rb, nil } // checkForKRBError checks if the response bytes from the KDC are a KRBError. func checkForKRBError(b []byte) ([]byte, error) { var KRBErr messages.KRBError if err := KRBErr.Unmarshal(b); err == nil { return b, KRBErr } return b, nil } gokrb5-8.4.3/v8/client/passwd.go000066400000000000000000000035521427031340300163640ustar00rootroot00000000000000package client import ( "fmt" "github.com/jcmturner/gokrb5/v8/kadmin" "github.com/jcmturner/gokrb5/v8/messages" ) // Kpasswd server response codes. const ( KRB5_KPASSWD_SUCCESS = 0 KRB5_KPASSWD_MALFORMED = 1 KRB5_KPASSWD_HARDERROR = 2 KRB5_KPASSWD_AUTHERROR = 3 KRB5_KPASSWD_SOFTERROR = 4 KRB5_KPASSWD_ACCESSDENIED = 5 KRB5_KPASSWD_BAD_VERSION = 6 KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7 ) // ChangePasswd changes the password of the client to the value provided. func (cl *Client) ChangePasswd(newPasswd string) (bool, error) { ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName()) if err != nil { return false, err } ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0) if err != nil { return false, err } msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key) if err != nil { return false, err } r, err := cl.sendToKPasswd(msg) if err != nil { return false, err } err = r.Decrypt(key) if err != nil { return false, err } if r.ResultCode != KRB5_KPASSWD_SUCCESS { return false, fmt.Errorf("error response from kadmin: code: %d; result: %s; krberror: %v", r.ResultCode, r.Result, r.KRBError) } cl.Credentials.WithPassword(newPasswd) return true, nil } func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) { _, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true) if err != nil { return } b, err := msg.Marshal() if err != nil { return } var rb []byte if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit { rb, err = dialSendUDP(kps, b) if err != nil { return } } else { rb, err = dialSendTCP(kps, b) if err != nil { return } } err = r.Unmarshal(rb) return } gokrb5-8.4.3/v8/client/session.go000066400000000000000000000201121427031340300165350ustar00rootroot00000000000000package client import ( "encoding/json" "fmt" "sort" "strings" "sync" "time" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // sessions hold TGTs and are keyed on the realm name type sessions struct { Entries map[string]*session mux sync.RWMutex } // destroy erases all sessions func (s *sessions) destroy() { s.mux.Lock() defer s.mux.Unlock() for k, e := range s.Entries { e.destroy() delete(s.Entries, k) } } // update replaces a session with the one provided or adds it as a new one func (s *sessions) update(sess *session) { s.mux.Lock() defer s.mux.Unlock() // if a session already exists for this, cancel its auto renew. if i, ok := s.Entries[sess.realm]; ok { if i != sess { // Session in the sessions cache is not the same as one provided. // Cancel the one in the cache and add this one. i.mux.Lock() defer i.mux.Unlock() if i.cancel != nil { i.cancel <- true } s.Entries[sess.realm] = sess return } } // No session for this realm was found so just add it s.Entries[sess.realm] = sess } // get returns the session for the realm specified func (s *sessions) get(realm string) (*session, bool) { s.mux.RLock() defer s.mux.RUnlock() sess, ok := s.Entries[realm] return sess, ok } // session holds the TGT details for a realm type session struct { realm string authTime time.Time endTime time.Time renewTill time.Time tgt messages.Ticket sessionKey types.EncryptionKey sessionKeyExpiration time.Time cancel chan bool mux sync.RWMutex } // jsonSession is used to enable marshaling some information of a session in a JSON format type jsonSession struct { Realm string AuthTime time.Time EndTime time.Time RenewTill time.Time SessionKeyExpiration time.Time } // AddSession adds a session for a realm with a TGT to the client's session cache. // A goroutine is started to automatically renew the TGT before expiry. func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) { if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" { // Not a TGT return } realm := tgt.SName.NameString[len(tgt.SName.NameString)-1] s := &session{ realm: realm, authTime: dep.AuthTime, endTime: dep.EndTime, renewTill: dep.RenewTill, tgt: tgt, sessionKey: dep.Key, sessionKeyExpiration: dep.KeyExpiration, } cl.sessions.update(s) cl.enableAutoSessionRenewal(s) cl.Log("TGT session added for %s (EndTime: %v)", realm, dep.EndTime) } // update overwrites the session details with those from the TGT and decrypted encPart func (s *session) update(tgt messages.Ticket, dep messages.EncKDCRepPart) { s.mux.Lock() defer s.mux.Unlock() s.authTime = dep.AuthTime s.endTime = dep.EndTime s.renewTill = dep.RenewTill s.tgt = tgt s.sessionKey = dep.Key s.sessionKeyExpiration = dep.KeyExpiration } // destroy will cancel any auto renewal of the session and set the expiration times to the current time func (s *session) destroy() { s.mux.Lock() defer s.mux.Unlock() if s.cancel != nil { s.cancel <- true } s.endTime = time.Now().UTC() s.renewTill = s.endTime s.sessionKeyExpiration = s.endTime } // valid informs if the TGT is still within the valid time window func (s *session) valid() bool { s.mux.RLock() defer s.mux.RUnlock() t := time.Now().UTC() if t.Before(s.endTime) && s.authTime.Before(t) { return true } return false } // tgtDetails is a thread safe way to get the session's realm, TGT and session key values func (s *session) tgtDetails() (string, messages.Ticket, types.EncryptionKey) { s.mux.RLock() defer s.mux.RUnlock() return s.realm, s.tgt, s.sessionKey } // timeDetails is a thread safe way to get the session's validity time values func (s *session) timeDetails() (string, time.Time, time.Time, time.Time, time.Time) { s.mux.RLock() defer s.mux.RUnlock() return s.realm, s.authTime, s.endTime, s.renewTill, s.sessionKeyExpiration } // JSON return information about the held sessions in a JSON format. func (s *sessions) JSON() (string, error) { s.mux.RLock() defer s.mux.RUnlock() var js []jsonSession keys := make([]string, 0, len(s.Entries)) for k := range s.Entries { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { r, at, et, rt, kt := s.Entries[k].timeDetails() j := jsonSession{ Realm: r, AuthTime: at, EndTime: et, RenewTill: rt, SessionKeyExpiration: kt, } js = append(js, j) } b, err := json.MarshalIndent(js, "", " ") if err != nil { return "", err } return string(b), nil } // enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session. func (cl *Client) enableAutoSessionRenewal(s *session) { var timer *time.Timer s.mux.Lock() s.cancel = make(chan bool, 1) s.mux.Unlock() go func(s *session) { for { s.mux.RLock() w := (s.endTime.Sub(time.Now().UTC()) * 5) / 6 s.mux.RUnlock() if w < 0 { return } timer = time.NewTimer(w) select { case <-timer.C: renewal, err := cl.refreshSession(s) if err != nil { cl.Log("error refreshing session: %v", err) } if !renewal && err == nil { // end this goroutine as there will have been a new login and new auto renewal goroutine created. return } case <-s.cancel: // cancel has been called. Stop the timer and exit. timer.Stop() return } } }(s) } // renewTGT renews the client's TGT session. func (cl *Client) renewTGT(s *session) error { realm, tgt, skey := s.tgtDetails() spn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } _, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT for %s", realm) } s.update(tgsRep.Ticket, tgsRep.DecryptedEncPart) cl.sessions.update(s) cl.Log("TGT session renewed for %s (EndTime: %v)", realm, tgsRep.DecryptedEncPart.EndTime) return nil } // refreshSession updates either through renewal or creating a new login. // The boolean indicates if the update was a renewal. func (cl *Client) refreshSession(s *session) (bool, error) { s.mux.RLock() realm := s.realm renewTill := s.renewTill s.mux.RUnlock() cl.Log("refreshing TGT session for %s", realm) if time.Now().UTC().Before(renewTill) { err := cl.renewTGT(s) return true, err } err := cl.realmLogin(realm) return false, err } // ensureValidSession makes sure there is a valid session for the realm func (cl *Client) ensureValidSession(realm string) error { s, ok := cl.sessions.get(realm) if ok { s.mux.RLock() d := s.endTime.Sub(s.authTime) / 6 if s.endTime.Sub(time.Now().UTC()) > d { s.mux.RUnlock() return nil } s.mux.RUnlock() _, err := cl.refreshSession(s) return err } return cl.realmLogin(realm) } // sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error) { err = cl.ensureValidSession(realm) if err != nil { return } s, ok := cl.sessions.get(realm) if !ok { err = fmt.Errorf("could not find TGT session for %s", realm) return } _, tgt, sessionKey = s.tgtDetails() return } // sessionTimes provides the timing information with regards to a session for the realm specified. func (cl *Client) sessionTimes(realm string) (authTime, endTime, renewTime, sessionExp time.Time, err error) { s, ok := cl.sessions.get(realm) if !ok { err = fmt.Errorf("could not find TGT session for %s", realm) return } _, authTime, endTime, renewTime, sessionExp = s.timeDetails() return } // spnRealm resolves the realm name of a service principal name func (cl *Client) spnRealm(spn types.PrincipalName) string { return cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1]) } gokrb5-8.4.3/v8/client/session_test.go000066400000000000000000000104531427031340300176030ustar00rootroot00000000000000package client import ( "encoding/hex" "fmt" "io" "os" "runtime" "sync" "testing" "time" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestMultiThreadedClientSession(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} cl := NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Fatalf("failed to log in: %v", err) } s, ok := cl.sessions.get("TEST.GOKRB5") if !ok { t.Fatal("error initially getting session") } go func() { for { err := cl.renewTGT(s) if err != nil { t.Logf("error renewing TGT: %v", err) } time.Sleep(time.Millisecond * 100) } }() var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { go func() { defer wg.Done() tgt, _, err := cl.sessionTGT("TEST.GOKRB5") if err != nil || tgt.Realm != "TEST.GOKRB5" { t.Logf("error getting session: %v", err) } _, _, _, r, _ := cl.sessionTimes("TEST.GOKRB5") fmt.Fprintf(io.Discard, "%v", r) }() time.Sleep(time.Second) } wg.Wait() } func TestClient_AutoRenew_Goroutine(t *testing.T) { test.Integration(t) // Tests that the auto renew of client credentials is not spawning goroutines out of control. addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER2_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5_SHORTTICKETS} c.LibDefaults.PreferredPreauthTypes = []int{int(etypeID.DES3_CBC_SHA1_KD)} // a preauth etype the KDC does not support. Test this does not cause renewal to fail. cl := NewWithKeytab("testuser2", "TEST.GOKRB5", kt, c) err := cl.Login() if err != nil { t.Errorf("error on logging in: %v\n", err) } n := runtime.NumGoroutine() for i := 0; i < 24; i++ { time.Sleep(time.Second * 5) _, endTime, _, _, err := cl.sessionTimes("TEST.GOKRB5") if err != nil { t.Errorf("could not get client's session: %v", err) } if time.Now().UTC().After(endTime) { t.Fatalf("session auto update failed") } spn := "HTTP/host.test.gokrb5" tkt, key, err := cl.GetServiceTicket(spn) if err != nil { t.Fatalf("error getting service ticket: %v\n", err) } b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) skt := keytab.New() skt.Unmarshal(b) tkt.DecryptEncPart(skt, nil) assert.Equal(t, spn, tkt.SName.PrincipalNameString()) assert.Equal(t, int32(18), key.KeyType) if runtime.NumGoroutine() > n { t.Fatalf("number of goroutines is increasing: should not be more than %d, is %d", n, runtime.NumGoroutine()) } } } func TestSessions_JSON(t *testing.T) { s := &sessions{ Entries: make(map[string]*session), } for i := 0; i < 3; i++ { realm := fmt.Sprintf("test%d", i) e := &session{ realm: realm, authTime: time.Unix(int64(0+i), 0).UTC(), endTime: time.Unix(int64(10+i), 0).UTC(), renewTill: time.Unix(int64(20+i), 0).UTC(), sessionKeyExpiration: time.Unix(int64(30+i), 0).UTC(), } s.Entries[realm] = e } j, err := s.JSON() if err != nil { t.Errorf("error getting json: %v", err) } expected := `[ { "Realm": "test0", "AuthTime": "1970-01-01T00:00:00Z", "EndTime": "1970-01-01T00:00:10Z", "RenewTill": "1970-01-01T00:00:20Z", "SessionKeyExpiration": "1970-01-01T00:00:30Z" }, { "Realm": "test1", "AuthTime": "1970-01-01T00:00:01Z", "EndTime": "1970-01-01T00:00:11Z", "RenewTill": "1970-01-01T00:00:21Z", "SessionKeyExpiration": "1970-01-01T00:00:31Z" }, { "Realm": "test2", "AuthTime": "1970-01-01T00:00:02Z", "EndTime": "1970-01-01T00:00:12Z", "RenewTill": "1970-01-01T00:00:22Z", "SessionKeyExpiration": "1970-01-01T00:00:32Z" } ]` assert.Equal(t, expected, j, "json output not as expected") } gokrb5-8.4.3/v8/client/settings.go000066400000000000000000000043651427031340300167260ustar00rootroot00000000000000package client import ( "encoding/json" "fmt" "log" ) // Settings holds optional client settings. type Settings struct { disablePAFXFast bool assumePreAuthentication bool preAuthEType int32 logger *log.Logger } // jsonSettings is used when marshaling the Settings details to JSON format. type jsonSettings struct { DisablePAFXFast bool AssumePreAuthentication bool } // NewSettings creates a new client settings struct. func NewSettings(settings ...func(*Settings)) *Settings { s := new(Settings) for _, set := range settings { set(s) } return s } // DisablePAFXFAST used to configure the client to not use PA_FX_FAST. // // s := NewSettings(DisablePAFXFAST(true)) func DisablePAFXFAST(b bool) func(*Settings) { return func(s *Settings) { s.disablePAFXFast = b } } // DisablePAFXFAST indicates is the client should disable the use of PA_FX_FAST. func (s *Settings) DisablePAFXFAST() bool { return s.disablePAFXFast } // AssumePreAuthentication used to configure the client to assume pre-authentication is required. // // s := NewSettings(AssumePreAuthentication(true)) func AssumePreAuthentication(b bool) func(*Settings) { return func(s *Settings) { s.assumePreAuthentication = b } } // AssumePreAuthentication indicates if the client should proactively assume using pre-authentication. func (s *Settings) AssumePreAuthentication() bool { return s.assumePreAuthentication } // Logger used to configure client with a logger. // // s := NewSettings(kt, Logger(l)) func Logger(l *log.Logger) func(*Settings) { return func(s *Settings) { s.logger = l } } // Logger returns the client logger instance. func (s *Settings) Logger() *log.Logger { return s.logger } // Log will write to the service's logger if it is configured. func (cl *Client) Log(format string, v ...interface{}) { if cl.settings.Logger() != nil { cl.settings.Logger().Output(2, fmt.Sprintf(format, v...)) } } // JSON returns a JSON representation of the settings. func (s *Settings) JSON() (string, error) { js := jsonSettings{ DisablePAFXFast: s.disablePAFXFast, AssumePreAuthentication: s.assumePreAuthentication, } b, err := json.MarshalIndent(js, "", " ") if err != nil { return "", err } return string(b), nil } gokrb5-8.4.3/v8/config/000077500000000000000000000000001427031340300145165ustar00rootroot00000000000000gokrb5-8.4.3/v8/config/error.go000066400000000000000000000011461427031340300162000ustar00rootroot00000000000000package config import "fmt" // UnsupportedDirective error. type UnsupportedDirective struct { text string } // Error implements the error interface for unsupported directives. func (e UnsupportedDirective) Error() string { return e.text } // Invalid config error. type Invalid struct { text string } // Error implements the error interface for invalid config error. func (e Invalid) Error() string { return e.text } // InvalidErrorf creates a new Invalid error. func InvalidErrorf(format string, a ...interface{}) Invalid { return Invalid{ text: fmt.Sprintf("invalid krb5 config "+format, a...), } } gokrb5-8.4.3/v8/config/hosts.go000066400000000000000000000064671427031340300162220ustar00rootroot00000000000000package config import ( "fmt" "math/rand" "net" "strconv" "strings" "github.com/jcmturner/dnsutils/v2" ) // GetKDCs returns the count of KDCs available and a map of KDC host names keyed on preference order. func (c *Config) GetKDCs(realm string, tcp bool) (int, map[int]string, error) { if realm == "" { realm = c.LibDefaults.DefaultRealm } kdcs := make(map[int]string) var count int // Get the KDCs from the krb5.conf. var ks []string for _, r := range c.Realms { if r.Realm != realm { continue } ks = r.KDC } count = len(ks) if count > 0 { // Order the kdcs randomly for preference. kdcs = randServOrder(ks) return count, kdcs, nil } if !c.LibDefaults.DNSLookupKDC { return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm) } // Use DNS to resolve kerberos SRV records. proto := "udp" if tcp { proto = "tcp" } index, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm) if err != nil { return count, kdcs, err } if len(addrs) < 1 { return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm) } count = index for k, v := range addrs { kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port)) } return count, kdcs, nil } // GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order. // https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section func (c *Config) GetKpasswdServers(realm string, tcp bool) (int, map[int]string, error) { kdcs := make(map[int]string) var count int // Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf. if c.LibDefaults.DNSLookupKDC { proto := "udp" if tcp { proto = "tcp" } c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm) if err != nil { return count, kdcs, err } if c < 1 { c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm) if err != nil { return count, kdcs, err } } if len(addrs) < 1 { return count, kdcs, fmt.Errorf("no kpasswd or kadmin SRV records found for realm %s", realm) } count = c for k, v := range addrs { kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port)) } } else { // Get the KDCs from the krb5.conf an order them randomly for preference. var ks []string var ka []string for _, r := range c.Realms { if r.Realm == realm { ks = r.KPasswdServer ka = r.AdminServer break } } if len(ks) < 1 { for _, k := range ka { h, _, err := net.SplitHostPort(k) if err != nil { continue } ks = append(ks, h+":464") } } count = len(ks) if count < 1 { return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm) } kdcs = randServOrder(ks) } return count, kdcs, nil } func randServOrder(ks []string) map[int]string { kdcs := make(map[int]string) count := len(ks) i := 1 if count > 1 { l := len(ks) for l > 0 { ri := rand.Intn(l) kdcs[i] = ks[ri] if l > 1 { // Remove the entry from the source slice by swapping with the last entry and truncating ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1] ks = ks[:len(ks)-1] l = len(ks) } else { l = 0 } i++ } } else { kdcs[i] = ks[0] } return kdcs } gokrb5-8.4.3/v8/config/hosts_test.go000066400000000000000000000044301427031340300172450ustar00rootroot00000000000000package config import ( "testing" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestConfig_GetKDCsUsesConfiguredKDC(t *testing.T) { t.Parallel() // This test is meant to cover the fix for // https://github.com/jcmturner/gokrb5/issues/332 krb5ConfWithKDCAndDNSLookupKDC := ` [libdefaults] dns_lookup_kdc = true [realms] TEST.GOKRB5 = { kdc = kdc2b.test.gokrb5:88 } ` c, err := NewFromString(krb5ConfWithKDCAndDNSLookupKDC) if err != nil { t.Fatalf("Error loading config: %v", err) } count, kdcs, err := c.GetKDCs("TEST.GOKRB5", false) if err != nil { t.Fatal(err) } if count != 1 { t.Fatalf("expected 1 but received %d", count) } if kdcs[1] != "kdc2b.test.gokrb5:88" { t.Fatalf("expected kdc2b.test.gokrb5:88 but received %s", kdcs[1]) } } func TestResolveKDC(t *testing.T) { test.Privileged(t) c, err := NewFromString(testdata.KRB5_CONF) if err != nil { t.Fatal(err) } // Store the original value for realms since we'll use them in our // second test. originalRealms := c.Realms // For our first test, let's check that we discover the expected // KDCs when they're not provided and we should be looking them up. c.LibDefaults.DNSLookupKDC = true c.Realms = make([]Realm, 0) count, res, err := c.GetKDCs(c.LibDefaults.DefaultRealm, true) if err != nil { t.Errorf("error resolving KDC via DNS TCP: %v", err) } assert.Equal(t, 5, count, "Number of SRV records not as expected: %v", res) assert.Equal(t, count, len(res), "Map size does not match: %v", res) expected := []string{ "kdc.test.gokrb5:88", "kdc1a.test.gokrb5:88", "kdc2a.test.gokrb5:88", "kdc1b.test.gokrb5:88", "kdc2b.test.gokrb5:88", } for _, s := range expected { var found bool for _, v := range res { if s == v { found = true break } } assert.True(t, found, "Record %s not found in results", s) } // For our second check, verify that when we shouldn't be looking them up, // we get the expected value. c.LibDefaults.DNSLookupKDC = false c.Realms = originalRealms _, res, err = c.GetKDCs(c.LibDefaults.DefaultRealm, true) if err != nil { t.Errorf("error resolving KDCs from config: %v", err) } assert.Equal(t, "127.0.0.1:88", res[1], "KDC not read from config as expected") } gokrb5-8.4.3/v8/config/krb5conf.go000066400000000000000000000547641427031340300165760ustar00rootroot00000000000000// Package config implements KRB5 client and service configuration as described at https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html package config import ( "bufio" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net" "os" "os/user" "regexp" "strconv" "strings" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) // Config represents the KRB5 configuration. type Config struct { LibDefaults LibDefaults Realms []Realm DomainRealm DomainRealm //CaPaths //AppDefaults //Plugins } // WeakETypeList is a list of encryption types that have been deemed weak. const WeakETypeList = "des-cbc-crc des-cbc-md4 des-cbc-md5 des-cbc-raw des3-cbc-raw des-hmac-sha1 arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp des" // New creates a new config struct instance. func New() *Config { d := make(DomainRealm) return &Config{ LibDefaults: newLibDefaults(), DomainRealm: d, } } // LibDefaults represents the [libdefaults] section of the configuration. type LibDefaults struct { AllowWeakCrypto bool //default false // ap_req_checksum_type int //unlikely to support this Canonicalize bool //default false CCacheType int //default is 4. unlikely to implement older Clockskew time.Duration //max allowed skew in seconds, default 300 //Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab DefaultKeytabName string //default /etc/krb5.keytab DefaultRealm string DefaultTGSEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DefaultTktEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DefaultTGSEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DefaultTktEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 DNSCanonicalizeHostname bool //default true DNSLookupKDC bool //default false DNSLookupRealm bool ExtraAddresses []net.IP //Not implementing yet Forwardable bool //default false IgnoreAcceptorHostname bool //default false K5LoginAuthoritative bool //default false K5LoginDirectory string //default user's home directory. Must be owned by the user or root KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK) KDCTimeSync int //default 1 //kdc_req_checksum_type int //unlikely to implement as for very old KDCs NoAddresses bool //default true PermittedEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4 PermittedEnctypeIDs []int32 //plugin_base_dir string //not supporting plugins PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported Proxiable bool //default false RDNS bool //default true RealmTryDomains int //default -1 RenewLifetime time.Duration //default 0 SafeChecksumType int //default 8 TicketLifetime time.Duration //default 1 day UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700. VerifyAPReqNofail bool //default false } // Create a new LibDefaults struct. func newLibDefaults() LibDefaults { uid := "0" var hdir string usr, _ := user.Current() if usr != nil { uid = usr.Uid hdir = usr.HomeDir } opts := asn1.BitString{} opts.Bytes, _ = hex.DecodeString("00000010") opts.BitLength = len(opts.Bytes) * 8 l := LibDefaults{ CCacheType: 4, Clockskew: time.Duration(300) * time.Second, DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid), DefaultKeytabName: "/etc/krb5.keytab", DefaultTGSEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, DefaultTktEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, DNSCanonicalizeHostname: true, K5LoginDirectory: hdir, KDCDefaultOptions: opts, KDCTimeSync: 1, NoAddresses: true, PermittedEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, RDNS: true, RealmTryDomains: -1, SafeChecksumType: 8, TicketLifetime: time.Duration(24) * time.Hour, UDPPreferenceLimit: 1465, PreferredPreauthTypes: []int{17, 16, 15, 14}, } l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto) l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto) l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto) return l } // Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct. func (l *LibDefaults) parseLines(lines []string) error { for _, line := range lines { //Remove comments after the values if idx := strings.IndexAny(line, "#;"); idx != -1 { line = line[:idx] } line = strings.TrimSpace(line) if line == "" { continue } if !strings.Contains(line, "=") { return InvalidErrorf("libdefaults section line (%s)", line) } p := strings.Split(line, "=") key := strings.TrimSpace(strings.ToLower(p[0])) switch key { case "allow_weak_crypto": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.AllowWeakCrypto = v case "canonicalize": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Canonicalize = v case "ccache_type": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseUint(p[1], 10, 32) if err != nil || v < 0 || v > 4 { return InvalidErrorf("libdefaults section line (%s)", line) } l.CCacheType = int(v) case "clockskew": d, err := parseDuration(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Clockskew = d case "default_client_keytab_name": l.DefaultClientKeytabName = strings.TrimSpace(p[1]) case "default_keytab_name": l.DefaultKeytabName = strings.TrimSpace(p[1]) case "default_realm": l.DefaultRealm = strings.TrimSpace(p[1]) case "default_tgs_enctypes": l.DefaultTGSEnctypes = strings.Fields(p[1]) case "default_tkt_enctypes": l.DefaultTktEnctypes = strings.Fields(p[1]) case "dns_canonicalize_hostname": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.DNSCanonicalizeHostname = v case "dns_lookup_kdc": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.DNSLookupKDC = v case "dns_lookup_realm": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.DNSLookupRealm = v case "extra_addresses": ipStr := strings.TrimSpace(p[1]) for _, ip := range strings.Split(ipStr, ",") { if eip := net.ParseIP(ip); eip != nil { l.ExtraAddresses = append(l.ExtraAddresses, eip) } } case "forwardable": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Forwardable = v case "ignore_acceptor_hostname": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.IgnoreAcceptorHostname = v case "k5login_authoritative": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.K5LoginAuthoritative = v case "k5login_directory": l.K5LoginDirectory = strings.TrimSpace(p[1]) case "kdc_default_options": v := strings.TrimSpace(p[1]) v = strings.Replace(v, "0x", "", -1) b, err := hex.DecodeString(v) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.KDCDefaultOptions.Bytes = b l.KDCDefaultOptions.BitLength = len(b) * 8 case "kdc_timesync": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseInt(p[1], 10, 32) if err != nil || v < 0 { return InvalidErrorf("libdefaults section line (%s)", line) } l.KDCTimeSync = int(v) case "noaddresses": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.NoAddresses = v case "permitted_enctypes": l.PermittedEnctypes = strings.Fields(p[1]) case "preferred_preauth_types": p[1] = strings.TrimSpace(p[1]) t := strings.Split(p[1], ",") var v []int for _, s := range t { i, err := strconv.ParseInt(s, 10, 32) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } v = append(v, int(i)) } l.PreferredPreauthTypes = v case "proxiable": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.Proxiable = v case "rdns": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.RDNS = v case "realm_try_domains": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseInt(p[1], 10, 32) if err != nil || v < -1 { return InvalidErrorf("libdefaults section line (%s)", line) } l.RealmTryDomains = int(v) case "renew_lifetime": d, err := parseDuration(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.RenewLifetime = d case "safe_checksum_type": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseInt(p[1], 10, 32) if err != nil || v < 0 { return InvalidErrorf("libdefaults section line (%s)", line) } l.SafeChecksumType = int(v) case "ticket_lifetime": d, err := parseDuration(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.TicketLifetime = d case "udp_preference_limit": p[1] = strings.TrimSpace(p[1]) v, err := strconv.ParseUint(p[1], 10, 32) if err != nil || v > 32700 { return InvalidErrorf("libdefaults section line (%s)", line) } l.UDPPreferenceLimit = int(v) case "verify_ap_req_nofail": v, err := parseBoolean(p[1]) if err != nil { return InvalidErrorf("libdefaults section line (%s): %v", line, err) } l.VerifyAPReqNofail = v } } l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto) l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto) l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto) return nil } // Realm represents an entry in the [realms] section of the configuration. type Realm struct { Realm string AdminServer []string //auth_to_local //Not implementing for now //auth_to_local_names //Not implementing for now DefaultDomain string KDC []string KPasswdServer []string //default admin_server:464 MasterKDC []string } // Parse the lines of a [realms] entry into the Realm struct. func (r *Realm) parseLines(name string, lines []string) (err error) { r.Realm = name var adminServerFinal bool var KDCFinal bool var kpasswdServerFinal bool var masterKDCFinal bool var ignore bool var c int // counts the depth of blocks within brackets { } for _, line := range lines { if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") { continue } //Remove comments after the values if idx := strings.IndexAny(line, "#;"); idx != -1 { line = line[:idx] } line = strings.TrimSpace(line) if line == "" { continue } if !strings.Contains(line, "=") && !strings.Contains(line, "}") { return InvalidErrorf("realms section line (%s)", line) } if strings.Contains(line, "v4_") { ignore = true err = UnsupportedDirective{"v4 configurations are not supported"} } if strings.Contains(line, "{") { c++ if ignore { continue } } if strings.Contains(line, "}") { c-- if c < 0 { return InvalidErrorf("unpaired curly brackets") } if ignore { if c < 1 { c = 0 ignore = false } continue } } p := strings.Split(line, "=") key := strings.TrimSpace(strings.ToLower(p[0])) v := strings.TrimSpace(p[1]) switch key { case "admin_server": appendUntilFinal(&r.AdminServer, v, &adminServerFinal) case "default_domain": r.DefaultDomain = v case "kdc": if !strings.Contains(v, ":") { // No port number specified default to 88 if strings.HasSuffix(v, `*`) { v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*" } else { v = strings.TrimSpace(v) + ":88" } } appendUntilFinal(&r.KDC, v, &KDCFinal) case "kpasswd_server": appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal) case "master_kdc": appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal) } } //default for Kpasswd_server = admin_server:464 if len(r.KPasswdServer) < 1 { for _, a := range r.AdminServer { s := strings.Split(a, ":") r.KPasswdServer = append(r.KPasswdServer, s[0]+":464") } } return } // Parse the lines of the [realms] section of the configuration into an slice of Realm structs. func parseRealms(lines []string) (realms []Realm, err error) { var name string var start int var c int for i, l := range lines { //Remove comments after the values if idx := strings.IndexAny(l, "#;"); idx != -1 { l = l[:idx] } l = strings.TrimSpace(l) if l == "" { continue } //if strings.Contains(l, "v4_") { // return nil, errors.New("v4 configurations are not supported in Realms section") //} if strings.Contains(l, "{") { c++ if !strings.Contains(l, "=") { return nil, fmt.Errorf("realm configuration line invalid: %s", l) } if c == 1 { start = i p := strings.Split(l, "=") name = strings.TrimSpace(p[0]) } } if strings.Contains(l, "}") { if c < 1 { // but not started a block!!! return nil, errors.New("invalid Realms section in configuration") } c-- if c == 0 { var r Realm e := r.parseLines(name, lines[start+1:i]) if e != nil { if _, ok := e.(UnsupportedDirective); !ok { err = e return } err = e } realms = append(realms, r) } } } return } // DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration. type DomainRealm map[string]string // Parse the lines of the [domain_realm] section of the configuration and add to the mapping. func (d *DomainRealm) parseLines(lines []string) error { for _, line := range lines { //Remove comments after the values if idx := strings.IndexAny(line, "#;"); idx != -1 { line = line[:idx] } if strings.TrimSpace(line) == "" { continue } if !strings.Contains(line, "=") { return InvalidErrorf("realm line (%s)", line) } p := strings.Split(line, "=") domain := strings.TrimSpace(strings.ToLower(p[0])) realm := strings.TrimSpace(p[1]) d.addMapping(domain, realm) } return nil } // Add a domain to realm mapping. func (d *DomainRealm) addMapping(domain, realm string) { (*d)[domain] = realm } // Delete a domain to realm mapping. func (d *DomainRealm) deleteMapping(domain, realm string) { delete(*d, domain) } // ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping. // The most specific mapping is returned. func (c *Config) ResolveRealm(domainName string) string { domainName = strings.TrimSuffix(domainName, ".") // Try to match the entire hostname first if r, ok := c.DomainRealm[domainName]; ok { return r } // Try to match all DNS domain parts periods := strings.Count(domainName, ".") + 1 for i := 2; i <= periods; i++ { z := strings.SplitN(domainName, ".", i) if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok { return r } } return "" } // Load the KRB5 configuration from the specified file path. func Load(cfgPath string) (*Config, error) { fh, err := os.Open(cfgPath) if err != nil { return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error()) } defer fh.Close() scanner := bufio.NewScanner(fh) return NewFromScanner(scanner) } // NewFromString creates a new Config struct from a string. func NewFromString(s string) (*Config, error) { reader := strings.NewReader(s) return NewFromReader(reader) } // NewFromReader creates a new Config struct from an io.Reader. func NewFromReader(r io.Reader) (*Config, error) { scanner := bufio.NewScanner(r) return NewFromScanner(scanner) } // NewFromScanner creates a new Config struct from a bufio.Scanner. func NewFromScanner(scanner *bufio.Scanner) (*Config, error) { c := New() var e error sections := make(map[int]string) var sectionLineNum []int var lines []string for scanner.Scan() { // Skip comments and blank lines if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched { continue } if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched { sections[len(lines)] = "libdefaults" sectionLineNum = append(sectionLineNum, len(lines)) continue } if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched { sections[len(lines)] = "realms" sectionLineNum = append(sectionLineNum, len(lines)) continue } if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched { sections[len(lines)] = "domain_realm" sectionLineNum = append(sectionLineNum, len(lines)) continue } if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched { sections[len(lines)] = "unknown_section" sectionLineNum = append(sectionLineNum, len(lines)) continue } lines = append(lines, scanner.Text()) } for i, start := range sectionLineNum { var end int if i+1 >= len(sectionLineNum) { end = len(lines) } else { end = sectionLineNum[i+1] } switch section := sections[start]; section { case "libdefaults": err := c.LibDefaults.parseLines(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { return nil, fmt.Errorf("error processing libdefaults section: %v", err) } e = err } case "realms": realms, err := parseRealms(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { return nil, fmt.Errorf("error processing realms section: %v", err) } e = err } c.Realms = realms case "domain_realm": err := c.DomainRealm.parseLines(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { return nil, fmt.Errorf("error processing domaain_realm section: %v", err) } e = err } } } return c, e } // Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes. func parseETypes(s []string, w bool) []int32 { var eti []int32 for _, et := range s { if !w { var weak bool for _, wet := range strings.Fields(WeakETypeList) { if et == wet { weak = true break } } if weak { continue } } i := etypeID.EtypeSupported(et) if i != 0 { eti = append(eti, i) } } return eti } // Parse a time duration string in the configuration to a golang time.Duration. func parseDuration(s string) (time.Duration, error) { s = strings.Replace(strings.TrimSpace(s), " ", "", -1) // handle Nd[NmNs] if strings.Contains(s, "d") { ds := strings.SplitN(s, "d", 2) dn, err := strconv.ParseUint(ds[0], 10, 32) if err != nil { return time.Duration(0), errors.New("invalid time duration") } d := time.Duration(dn*24) * time.Hour if ds[1] != "" { dp, err := time.ParseDuration(ds[1]) if err != nil { return time.Duration(0), errors.New("invalid time duration") } d = d + dp } return d, nil } // handle Nm[Ns] d, err := time.ParseDuration(s) if err == nil { return d, nil } // handle N v, err := strconv.ParseUint(s, 10, 32) if err == nil && v > 0 { return time.Duration(v) * time.Second, nil } // handle h:m[:s] if strings.Contains(s, ":") { t := strings.Split(s, ":") if 2 > len(t) || len(t) > 3 { return time.Duration(0), errors.New("invalid time duration value") } var i []int for _, n := range t { j, err := strconv.ParseInt(n, 10, 16) if err != nil { return time.Duration(0), errors.New("invalid time duration value") } i = append(i, int(j)) } d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute if len(i) == 3 { d = d + time.Duration(i[2])*time.Second } return d, nil } return time.Duration(0), errors.New("invalid time duration value") } // Parse possible boolean values to golang bool. func parseBoolean(s string) (bool, error) { s = strings.TrimSpace(s) v, err := strconv.ParseBool(s) if err == nil { return v, nil } switch strings.ToLower(s) { case "yes": return true, nil case "y": return true, nil case "no": return false, nil case "n": return false, nil } return false, errors.New("invalid boolean value") } // Parse array of strings but stop if an asterisk is placed at the end of a line. func appendUntilFinal(s *[]string, value string, final *bool) { if *final { return } if last := len(value) - 1; last >= 0 && value[last] == '*' { *final = true value = value[:len(value)-1] } *s = append(*s, value) } // JSON return details of the config in a JSON format. func (c *Config) JSON() (string, error) { b, err := json.MarshalIndent(c, "", " ") if err != nil { return "", err } return string(b), nil } gokrb5-8.4.3/v8/config/krb5conf_test.go000066400000000000000000000555651427031340300176350ustar00rootroot00000000000000package config import ( "os" "testing" "time" "github.com/stretchr/testify/assert" ) const ( krb5Conf = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 ; comment to be ignored dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h ;comment to be ignored forwardable = yes #comment to be ignored default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 # comment to be ignored [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 #comment to be ignored kdc = assume.port.num ;comment to be ignored kdc = some.other.port:1234 # comment to be ignored kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 ; comment to be ignored default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } lowercase.org = { kdc = kerberos.lowercase.org admin_server = kerberos.lowercase.org } [domain_realm] .test.gokrb5 = TEST.GOKRB5 #comment to be ignored test.gokrb5 = TEST.GOKRB5 ;comment to be ignored .example.com = EXAMPLE.COM # comment to be ignored hostname1.example.com = EXAMPLE.COM ; comment to be ignored hostname2.example.com = TEST.GOKRB5 .testlowercase.org = lowercase.org [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } ` krb5ConfJson = `{ "LibDefaults": { "AllowWeakCrypto": false, "Canonicalize": false, "CCacheType": 4, "Clockskew": 300000000000, "DefaultClientKeytabName": "FILE:/home/gokrb5/client.keytab", "DefaultKeytabName": "FILE:/etc/krb5.keytab", "DefaultRealm": "TEST.GOKRB5", "DefaultTGSEnctypes": [ "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4" ], "DefaultTktEnctypes": [ "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96" ], "DefaultTGSEnctypeIDs": [ 18, 17, 23 ], "DefaultTktEnctypeIDs": [ 18, 17 ], "DNSCanonicalizeHostname": true, "DNSLookupKDC": false, "DNSLookupRealm": false, "ExtraAddresses": null, "Forwardable": true, "IgnoreAcceptorHostname": false, "K5LoginAuthoritative": false, "K5LoginDirectory": "/home/test", "KDCDefaultOptions": { "Bytes": "AAAAEA==", "BitLength": 32 }, "KDCTimeSync": 1, "NoAddresses": true, "PermittedEnctypes": [ "aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4" ], "PermittedEnctypeIDs": [ 18, 17, 23 ], "PreferredPreauthTypes": [ 17, 16, 15, 14 ], "Proxiable": false, "RDNS": true, "RealmTryDomains": -1, "RenewLifetime": 0, "SafeChecksumType": 8, "TicketLifetime": 36000000000000, "UDPPreferenceLimit": 1465, "VerifyAPReqNofail": false }, "Realms": [ { "Realm": "TEST.GOKRB5", "AdminServer": [ "10.80.88.88:749" ], "DefaultDomain": "test.gokrb5", "KDC": [ "10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88" ], "KPasswdServer": [ "10.80.88.88:464" ], "MasterKDC": null }, { "Realm": "EXAMPLE.COM", "AdminServer": [ "kerberos.example.com" ], "DefaultDomain": "", "KDC": [ "kerberos.example.com:88", "kerberos-1.example.com:88" ], "KPasswdServer": [ "kerberos.example.com:464" ], "MasterKDC": null }, { "Realm": "lowercase.org", "AdminServer": [ "kerberos.lowercase.org" ], "DefaultDomain": "", "KDC": [ "kerberos.lowercase.org:88" ], "KPasswdServer": [ "kerberos.lowercase.org:464" ], "MasterKDC": null } ], "DomainRealm": { ".example.com": "EXAMPLE.COM", ".test.gokrb5": "TEST.GOKRB5", ".testlowercase.org": "lowercase.org", "hostname1.example.com": "EXAMPLE.COM", "hostname2.example.com": "TEST.GOKRB5", "test.gokrb5": "TEST.GOKRB5" } }` krb5Conf2 = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] noaddresses = true default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com } ` krb5ConfNoBlankLines = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 ` krb5ConfTabs = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 .example.com = EXAMPLE.COM hostname1.example.com = EXAMPLE.COM hostname2.example.com = TEST.GOKRB5 [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false }` krb5ConfV4Lines = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log kdc = FILE:/var/log/kerberos/krb5kdc.log admin_server = FILE:/var/log/kerberos/kadmind.log [libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false #dns_lookup_kdc = true ;dns_lookup_kdc = true #dns_lookup_kdc = true ;dns_lookup_kdc = true ticket_lifetime = 10h forwardable = yes default_keytab_name = FILE:/etc/krb5.keytab default_client_keytab_name = FILE:/home/gokrb5/client.keytab default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 kdc = assume.port.num kdc = some.other.port:1234 kdc = 10.80.88.88* kdc = 10.1.2.3.4:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 v4_name_convert = { host = { rcmd = host } } } EXAMPLE.COM = { kdc = kerberos.example.com kdc = kerberos-1.example.com admin_server = kerberos.example.com auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 .example.com = EXAMPLE.COM hostname1.example.com = EXAMPLE.COM hostname2.example.com = TEST.GOKRB5 [appdefaults] pam = { debug = false ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } ` ) func TestLoad(t *testing.T) { t.Parallel() cf, _ := os.CreateTemp(os.TempDir(), "TEST-gokrb5-krb5.conf") defer os.Remove(cf.Name()) cf.WriteString(krb5Conf) c, err := Load(cf.Name()) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 3, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestLoadWithV4Lines(t *testing.T) { t.Parallel() cf, _ := os.CreateTemp(os.TempDir(), "TEST-gokrb5-krb5.conf") defer os.Remove(cf.Name()) cf.WriteString(krb5ConfV4Lines) c, err := Load(cf.Name()) if err == nil { t.Fatalf("error should not be nil for config that includes v4 lines") } if _, ok := err.(UnsupportedDirective); !ok { t.Fatalf("error should be of type UnsupportedDirective: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestLoad2(t *testing.T) { t.Parallel() c, err := NewFromString(krb5Conf2) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") assert.True(t, c.LibDefaults.NoAddresses, "No address not set as true") } func TestLoadNoBlankLines(t *testing.T) { t.Parallel() c, err := NewFromString(krb5ConfNoBlankLines) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestLoadTabs(t *testing.T) { t.Parallel() cf, _ := os.CreateTemp(os.TempDir(), "TEST-gokrb5-krb5.conf") defer os.Remove(cf.Name()) cf.WriteString(krb5ConfTabs) c, err := Load(cf.Name()) if err != nil { t.Fatalf("Error loading config: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected") assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") } func TestParseDuration(t *testing.T) { t.Parallel() // https://web.mit.edu/kerberos/krb5-1.12/doc/basic/date_format.html#duration hms, _ := time.ParseDuration("12h30m15s") hm, _ := time.ParseDuration("12h30m") h, _ := time.ParseDuration("12h") var tests = []struct { timeStr string duration time.Duration }{ {"100", time.Duration(100) * time.Second}, {"12:30", hm}, {"12:30:15", hms}, {"1d12h30m15s", time.Duration(24)*time.Hour + hms}, {"1d12h30m", time.Duration(24)*time.Hour + hm}, {"1d12h", time.Duration(24)*time.Hour + h}, {"1d", time.Duration(24) * time.Hour}, } for _, test := range tests { d, err := parseDuration(test.timeStr) if err != nil { t.Errorf("error parsing %s: %v", test.timeStr, err) } assert.Equal(t, test.duration, d, "Duration not as expected for: "+test.timeStr) } } func TestResolveRealm(t *testing.T) { t.Parallel() c, err := NewFromString(krb5Conf) if err != nil { t.Fatalf("Error loading config: %v", err) } tests := []struct { domainName string want string }{ {"unknown.com", ""}, {"hostname1.example.com", "EXAMPLE.COM"}, {"hostname2.example.com", "TEST.GOKRB5"}, {"one.two.three.example.com", "EXAMPLE.COM"}, {".test.gokrb5", "TEST.GOKRB5"}, {"foo.testlowercase.org", "lowercase.org"}, } for _, tt := range tests { t.Run(tt.domainName, func(t *testing.T) { if got := c.ResolveRealm(tt.domainName); got != tt.want { t.Errorf("config.ResolveRealm() = %v, want %v", got, tt.want) } }) } } func TestJSON(t *testing.T) { t.Parallel() c, err := NewFromString(krb5Conf) if err != nil { t.Fatalf("Error loading config: %v", err) } c.LibDefaults.K5LoginDirectory = "/home/test" j, err := c.JSON() if err != nil { t.Errorf("error marshaling krb config to JSON: %v", err) } assert.Equal(t, krb5ConfJson, j, "krb config marshaled json not as expected") t.Log(j) } gokrb5-8.4.3/v8/credentials/000077500000000000000000000000001427031340300155465ustar00rootroot00000000000000gokrb5-8.4.3/v8/credentials/ccache.go000066400000000000000000000212231427031340300173030ustar00rootroot00000000000000package credentials import ( "bytes" "encoding/binary" "errors" "os" "strings" "time" "unsafe" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/types" ) const ( headerFieldTagKDCOffset = 1 ) // CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html type CCache struct { Version uint8 Header header DefaultPrincipal principal Credentials []*Credential Path string } type header struct { length uint16 fields []headerField } type headerField struct { tag uint16 length uint16 value []byte } // Credential cache entry principal struct. type principal struct { Realm string PrincipalName types.PrincipalName } // Credential holds a Kerberos client's ccache credential information. type Credential struct { Client principal Server principal Key types.EncryptionKey AuthTime time.Time StartTime time.Time EndTime time.Time RenewTill time.Time IsSKey bool TicketFlags asn1.BitString Addresses []types.HostAddress AuthData []types.AuthorizationDataEntry Ticket []byte SecondTicket []byte } // LoadCCache loads a credential cache file into a CCache type. func LoadCCache(cpath string) (*CCache, error) { c := new(CCache) b, err := os.ReadFile(cpath) if err != nil { return c, err } err = c.Unmarshal(b) return c, err } // Unmarshal a byte slice of credential cache data into CCache type. func (c *CCache) Unmarshal(b []byte) error { p := 0 //The first byte of the file always has the value 5 if int8(b[p]) != 5 { return errors.New("Invalid credential cache data. First byte does not equal 5") } p++ //Get credential cache version //The second byte contains the version number (1 to 4) c.Version = b[p] if c.Version < 1 || c.Version > 4 { return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4") } p++ //Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order var endian binary.ByteOrder endian = binary.BigEndian if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() { endian = binary.LittleEndian } if c.Version == 4 { err := parseHeader(b, &p, c, &endian) if err != nil { return err } } c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian) for p < len(b) { cred, err := parseCredential(b, &p, c, &endian) if err != nil { return err } c.Credentials = append(c.Credentials, cred) } return nil } func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error { if c.Version != 4 { return errors.New("Credentials cache version is not 4 so there is no header to parse.") } h := header{} h.length = uint16(readInt16(b, p, e)) for *p <= int(h.length) { f := headerField{} f.tag = uint16(readInt16(b, p, e)) f.length = uint16(readInt16(b, p, e)) f.value = b[*p : *p+int(f.length)] *p += int(f.length) if !f.valid() { return errors.New("Invalid credential cache header found") } h.fields = append(h.fields, f) } c.Header = h return nil } // Parse the Keytab bytes of a principal into a Keytab entry's principal. func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ principal) { if c.Version != 1 { //Name Type is omitted in version 1 princ.PrincipalName.NameType = readInt32(b, p, e) } nc := int(readInt32(b, p, e)) if c.Version == 1 { //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2 nc-- } lenRealm := readInt32(b, p, e) princ.Realm = string(readBytes(b, p, int(lenRealm), e)) for i := 0; i < nc; i++ { l := readInt32(b, p, e) princ.PrincipalName.NameString = append(princ.PrincipalName.NameString, string(readBytes(b, p, int(l), e))) } return princ } func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) { cred = new(Credential) cred.Client = parsePrincipal(b, p, c, e) cred.Server = parsePrincipal(b, p, c, e) key := types.EncryptionKey{} key.KeyType = int32(readInt16(b, p, e)) if c.Version == 3 { //repeated twice in version 3 key.KeyType = int32(readInt16(b, p, e)) } key.KeyValue = readData(b, p, e) cred.Key = key cred.AuthTime = readTimestamp(b, p, e) cred.StartTime = readTimestamp(b, p, e) cred.EndTime = readTimestamp(b, p, e) cred.RenewTill = readTimestamp(b, p, e) if ik := readInt8(b, p, e); ik == 0 { cred.IsSKey = false } else { cred.IsSKey = true } cred.TicketFlags = types.NewKrbFlags() cred.TicketFlags.Bytes = readBytes(b, p, 4, e) l := int(readInt32(b, p, e)) cred.Addresses = make([]types.HostAddress, l, l) for i := range cred.Addresses { cred.Addresses[i] = readAddress(b, p, e) } l = int(readInt32(b, p, e)) cred.AuthData = make([]types.AuthorizationDataEntry, l, l) for i := range cred.AuthData { cred.AuthData[i] = readAuthDataEntry(b, p, e) } cred.Ticket = readData(b, p, e) cred.SecondTicket = readData(b, p, e) return } // GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for. func (c *CCache) GetClientPrincipalName() types.PrincipalName { return c.DefaultPrincipal.PrincipalName } // GetClientRealm returns the reals of the client the credentials cache is for. func (c *CCache) GetClientRealm() string { return c.DefaultPrincipal.Realm } // GetClientCredentials returns a Credentials object representing the client of the credentials cache. func (c *CCache) GetClientCredentials() *Credentials { return &Credentials{ username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(), realm: c.GetClientRealm(), cname: c.DefaultPrincipal.PrincipalName, } } // Contains tests if the cache contains a credential for the provided server PrincipalName func (c *CCache) Contains(p types.PrincipalName) bool { for _, cred := range c.Credentials { if cred.Server.PrincipalName.Equal(p) { return true } } return false } // GetEntry returns a specific credential for the PrincipalName provided. func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) { cred := new(Credential) var found bool for i := range c.Credentials { if c.Credentials[i].Server.PrincipalName.Equal(p) { cred = c.Credentials[i] found = true break } } if !found { return cred, false } return cred, true } // GetEntries filters out configuration entries an returns a slice of credentials. func (c *CCache) GetEntries() []*Credential { creds := make([]*Credential, 0) for _, cred := range c.Credentials { // Filter out configuration entries if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") { continue } creds = append(creds, cred) } return creds } func (h *headerField) valid() bool { // See https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html - Header format switch h.tag { case headerFieldTagKDCOffset: if h.length != 8 || len(h.value) != 8 { return false } return true } return false } func readData(b []byte, p *int, e *binary.ByteOrder) []byte { l := readInt32(b, p, e) return readBytes(b, p, int(l), e) } func readAddress(b []byte, p *int, e *binary.ByteOrder) types.HostAddress { a := types.HostAddress{} a.AddrType = int32(readInt16(b, p, e)) a.Address = readData(b, p, e) return a } func readAuthDataEntry(b []byte, p *int, e *binary.ByteOrder) types.AuthorizationDataEntry { a := types.AuthorizationDataEntry{} a.ADType = int32(readInt16(b, p, e)) a.ADData = readData(b, p, e) return a } // Read bytes representing a timestamp. func readTimestamp(b []byte, p *int, e *binary.ByteOrder) time.Time { return time.Unix(int64(readInt32(b, p, e)), 0) } // Read bytes representing an eight bit integer. func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8) { buf := bytes.NewBuffer(b[*p : *p+1]) binary.Read(buf, *e, &i) *p++ return } // Read bytes representing a sixteen bit integer. func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16) { buf := bytes.NewBuffer(b[*p : *p+2]) binary.Read(buf, *e, &i) *p += 2 return } // Read bytes representing a thirty two bit integer. func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32) { buf := bytes.NewBuffer(b[*p : *p+4]) binary.Read(buf, *e, &i) *p += 4 return } func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte { buf := bytes.NewBuffer(b[*p : *p+s]) r := make([]byte, s) binary.Read(buf, *e, &r) *p += s return r } func isNativeEndianLittle() bool { var x = 0x012345678 var p = unsafe.Pointer(&x) var bp = (*[4]byte)(p) var endian bool if 0x01 == bp[0] { endian = false } else if (0x78 & 0xff) == (bp[0] & 0xff) { endian = true } else { // Default to big endian endian = false } return endian } gokrb5-8.4.3/v8/credentials/ccache_integration_test.go000066400000000000000000000071021427031340300227450ustar00rootroot00000000000000package credentials import ( "bufio" "bytes" "fmt" "io" "os" "os/exec" "os/user" "sync" "testing" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) const ( kinitCmd = "kinit" kvnoCmd = "kvno" klistCmd = "klist" spn = "HTTP/host.test.gokrb5" ) type output struct { buf *bytes.Buffer lines []string *sync.Mutex } func newOutput() *output { return &output{ buf: &bytes.Buffer{}, lines: []string{}, Mutex: &sync.Mutex{}, } } func (rw *output) Write(p []byte) (int, error) { rw.Lock() defer rw.Unlock() return rw.buf.Write(p) } func (rw *output) Lines() []string { rw.Lock() defer rw.Unlock() s := bufio.NewScanner(rw.buf) for s.Scan() { rw.lines = append(rw.lines, s.Text()) } return rw.lines } func login() error { file, err := os.Create("/etc/krb5.conf") if err != nil { return fmt.Errorf("cannot open krb5.conf: %v", err) } defer file.Close() fmt.Fprintf(file, testdata.KRB5_CONF) cmd := exec.Command(kinitCmd, "testuser1@TEST.GOKRB5") stdinR, stdinW := io.Pipe() stderrR, stderrW := io.Pipe() cmd.Stdin = stdinR cmd.Stderr = stderrW err = cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kinitCmd, err) } go func() { io.WriteString(stdinW, "passwordvalue") stdinW.Close() }() errBuf := new(bytes.Buffer) go func() { io.Copy(errBuf, stderrR) stderrR.Close() }() err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v stderr: %s", kinitCmd, err, string(errBuf.Bytes())) } return nil } func getServiceTkt() error { cmd := exec.Command(kvnoCmd, spn) err := cmd.Start() if err != nil { return fmt.Errorf("could not start %s command: %v", kvnoCmd, err) } err = cmd.Wait() if err != nil { return fmt.Errorf("%s did not run successfully: %v", kvnoCmd, err) } return nil } func klist() ([]string, error) { cmd := exec.Command(klistCmd, "-Aef") stdout := newOutput() cmd.Stdout = stdout err := cmd.Start() if err != nil { return []string{}, fmt.Errorf("could not start %s command: %v", klistCmd, err) } err = cmd.Wait() if err != nil { return []string{}, fmt.Errorf("%s did not run successfully: %v", klistCmd, err) } return stdout.Lines(), nil } func loadCCache() (*CCache, error) { usr, _ := user.Current() cpath := "/tmp/krb5cc_" + usr.Uid return LoadCCache(cpath) } func TestLoadCCache(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } pn := c.GetClientPrincipalName() assert.Equal(t, "testuser1", pn.PrincipalNameString(), "principal not as expected") assert.Equal(t, "TEST.GOKRB5", c.GetClientRealm(), "realm not as expected") } func TestCCacheEntries(t *testing.T) { test.Privileged(t) err := login() if err != nil { t.Fatalf("error logging in with kinit: %v", err) } err = getServiceTkt() if err != nil { t.Fatalf("error getting service ticket: %v", err) } clist, _ := klist() t.Log("OS Creds Cache contents:") for _, l := range clist { t.Log(l) } c, err := loadCCache() if err != nil { t.Errorf("error loading CCache: %v", err) } creds := c.GetEntries() var found bool n := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) for _, cred := range creds { if cred.Server.PrincipalName.Equal(n) { found = true break } } if !found { t.Errorf("Entry for %s not found in CCache", spn) } } gokrb5-8.4.3/v8/credentials/ccache_test.go000066400000000000000000000077171427031340300203560ustar00rootroot00000000000000package credentials import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestParse(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } assert.Equal(t, uint8(4), c.Version, "Version not as expected") assert.Equal(t, 1, len(c.Header.fields), "Number of header fields not as expected") assert.Equal(t, uint16(1), c.Header.fields[0].tag, "Header tag not as expected") assert.Equal(t, uint16(8), c.Header.fields[0].length, "Length of header not as expected") assert.Equal(t, "TEST.GOKRB5", c.DefaultPrincipal.Realm, "Default client principal realm not as expected") assert.Equal(t, "testuser1", c.DefaultPrincipal.PrincipalName.PrincipalNameString(), "Default client principaal name not as expected") assert.Equal(t, 3, len(c.Credentials), "Number of credentials not as expected") tgtpn := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", "TEST.GOKRB5"}, } assert.True(t, c.Contains(tgtpn), "Cache does not contain TGT credential") httppn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"HTTP", "host.test.gokrb5"}, } assert.True(t, c.Contains(httppn), "Cache does not contain HTTP SPN credential") } func TestCCache_GetClientPrincipalName(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } pn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser1"}, } assert.Equal(t, pn, c.GetClientPrincipalName(), "Client PrincipalName not as expected") } func TestCCache_GetClientCredentials(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } pn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser1"}, } cred := c.GetClientCredentials() assert.Equal(t, "TEST.GOKRB5", cred.Domain(), "Client realm in credential not as expected") assert.Equal(t, pn, cred.CName(), "Client Principal Name not as expected") assert.Equal(t, "testuser1", cred.UserName(), "Username not as expected") } func TestCCache_GetClientRealm(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } assert.Equal(t, "TEST.GOKRB5", c.GetClientRealm(), "Client realm not as expected") } func TestCCache_GetEntry(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } httppn := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"HTTP", "host.test.gokrb5"}, } cred, ok := c.GetEntry(httppn) if !ok { t.Fatal("Could not get entry from CCache as not found") } assert.Equal(t, httppn, cred.Server.PrincipalName, "Credential does not have the right server principal name") } func TestCCache_GetEntries(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.CCACHE_TEST) if err != nil { t.Fatal("Error decoding test data") } c := new(CCache) err = c.Unmarshal(b) if err != nil { t.Fatalf("Error parsing cache: %v", err) } creds := c.GetEntries() assert.Equal(t, 2, len(creds), "Number of credentials entries not as expected") } gokrb5-8.4.3/v8/credentials/credentials.go000066400000000000000000000252701427031340300204000ustar00rootroot00000000000000// Package credentials provides credentials management for Kerberos 5 authentication. package credentials import ( "bytes" "encoding/gob" "encoding/json" "time" "github.com/hashicorp/go-uuid" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/types" ) const ( // AttributeKeyADCredentials assigned number for AD credentials. AttributeKeyADCredentials = "gokrb5AttributeKeyADCredentials" ) // Credentials struct for a user. // Contains either a keytab, password or both. // Keytabs are used over passwords if both are defined. type Credentials struct { username string displayName string realm string cname types.PrincipalName keytab *keytab.Keytab password string attributes map[string]interface{} validUntil time.Time authenticated bool human bool authTime time.Time groupMembership map[string]bool sessionID string } // marshalCredentials is used to enable marshaling and unmarshaling of credentials // without having exported fields on the Credentials struct type marshalCredentials struct { Username string DisplayName string Realm string CName types.PrincipalName `json:"-"` Keytab bool Password bool Attributes map[string]interface{} `json:"-"` ValidUntil time.Time Authenticated bool Human bool AuthTime time.Time GroupMembership map[string]bool `json:"-"` SessionID string } // ADCredentials contains information obtained from the PAC. type ADCredentials struct { EffectiveName string FullName string UserID int PrimaryGroupID int LogOnTime time.Time LogOffTime time.Time PasswordLastSet time.Time GroupMembershipSIDs []string LogonDomainName string LogonDomainID string LogonServer string } // New creates a new Credentials instance. func New(username string, realm string) *Credentials { uid, err := uuid.GenerateUUID() if err != nil { uid = "00unique-sess-ions-uuid-unavailable0" } return &Credentials{ username: username, displayName: username, realm: realm, cname: types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username), keytab: keytab.New(), attributes: make(map[string]interface{}), groupMembership: make(map[string]bool), sessionID: uid, human: true, } } // NewFromPrincipalName creates a new Credentials instance with the user details provides as a PrincipalName type. func NewFromPrincipalName(cname types.PrincipalName, realm string) *Credentials { c := New(cname.PrincipalNameString(), realm) c.cname = cname return c } // WithKeytab sets the Keytab in the Credentials struct. func (c *Credentials) WithKeytab(kt *keytab.Keytab) *Credentials { c.keytab = kt c.password = "" return c } // Keytab returns the credential's Keytab. func (c *Credentials) Keytab() *keytab.Keytab { return c.keytab } // HasKeytab queries if the Credentials has a keytab defined. func (c *Credentials) HasKeytab() bool { if c.keytab != nil && len(c.keytab.Entries) > 0 { return true } return false } // WithPassword sets the password in the Credentials struct. func (c *Credentials) WithPassword(password string) *Credentials { c.password = password c.keytab = keytab.New() // clear any keytab return c } // Password returns the credential's password. func (c *Credentials) Password() string { return c.password } // HasPassword queries if the Credentials has a password defined. func (c *Credentials) HasPassword() bool { if c.password != "" { return true } return false } // SetValidUntil sets the expiry time of the credentials func (c *Credentials) SetValidUntil(t time.Time) { c.validUntil = t } // SetADCredentials adds ADCredentials attributes to the credentials func (c *Credentials) SetADCredentials(a ADCredentials) { c.SetAttribute(AttributeKeyADCredentials, a) if a.FullName != "" { c.SetDisplayName(a.FullName) } if a.EffectiveName != "" { c.SetUserName(a.EffectiveName) } for i := range a.GroupMembershipSIDs { c.AddAuthzAttribute(a.GroupMembershipSIDs[i]) } } // GetADCredentials returns ADCredentials attributes sorted in the credential func (c *Credentials) GetADCredentials() ADCredentials { if a, ok := c.attributes[AttributeKeyADCredentials].(ADCredentials); ok { return a } return ADCredentials{} } // Methods to implement goidentity.Identity interface // UserName returns the credential's username. func (c *Credentials) UserName() string { return c.username } // SetUserName sets the username value on the credential. func (c *Credentials) SetUserName(s string) { c.username = s } // CName returns the credential's client principal name. func (c *Credentials) CName() types.PrincipalName { return c.cname } // SetCName sets the client principal name on the credential. func (c *Credentials) SetCName(pn types.PrincipalName) { c.cname = pn } // Domain returns the credential's domain. func (c *Credentials) Domain() string { return c.realm } // SetDomain sets the domain value on the credential. func (c *Credentials) SetDomain(s string) { c.realm = s } // Realm returns the credential's realm. Same as the domain. func (c *Credentials) Realm() string { return c.Domain() } // SetRealm sets the realm value on the credential. Same as the domain func (c *Credentials) SetRealm(s string) { c.SetDomain(s) } // DisplayName returns the credential's display name. func (c *Credentials) DisplayName() string { return c.displayName } // SetDisplayName sets the display name value on the credential. func (c *Credentials) SetDisplayName(s string) { c.displayName = s } // Human returns if the credential represents a human or not. func (c *Credentials) Human() bool { return c.human } // SetHuman sets the credential as human. func (c *Credentials) SetHuman(b bool) { c.human = b } // AuthTime returns the time the credential was authenticated. func (c *Credentials) AuthTime() time.Time { return c.authTime } // SetAuthTime sets the time the credential was authenticated. func (c *Credentials) SetAuthTime(t time.Time) { c.authTime = t } // AuthzAttributes returns the credentials authorizing attributes. func (c *Credentials) AuthzAttributes() []string { s := make([]string, len(c.groupMembership)) i := 0 for a := range c.groupMembership { s[i] = a i++ } return s } // Authenticated indicates if the credential has been successfully authenticated or not. func (c *Credentials) Authenticated() bool { return c.authenticated } // SetAuthenticated sets the credential as having been successfully authenticated. func (c *Credentials) SetAuthenticated(b bool) { c.authenticated = b } // AddAuthzAttribute adds an authorization attribute to the credential. func (c *Credentials) AddAuthzAttribute(a string) { c.groupMembership[a] = true } // RemoveAuthzAttribute removes an authorization attribute from the credential. func (c *Credentials) RemoveAuthzAttribute(a string) { if _, ok := c.groupMembership[a]; !ok { return } delete(c.groupMembership, a) } // EnableAuthzAttribute toggles an authorization attribute to an enabled state on the credential. func (c *Credentials) EnableAuthzAttribute(a string) { if enabled, ok := c.groupMembership[a]; ok && !enabled { c.groupMembership[a] = true } } // DisableAuthzAttribute toggles an authorization attribute to a disabled state on the credential. func (c *Credentials) DisableAuthzAttribute(a string) { if enabled, ok := c.groupMembership[a]; ok && enabled { c.groupMembership[a] = false } } // Authorized indicates if the credential has the specified authorizing attribute. func (c *Credentials) Authorized(a string) bool { if enabled, ok := c.groupMembership[a]; ok && enabled { return true } return false } // SessionID returns the credential's session ID. func (c *Credentials) SessionID() string { return c.sessionID } // Expired indicates if the credential has expired. func (c *Credentials) Expired() bool { if !c.validUntil.IsZero() && time.Now().UTC().After(c.validUntil) { return true } return false } // ValidUntil returns the credential's valid until date func (c *Credentials) ValidUntil() time.Time { return c.validUntil } // Attributes returns the Credentials' attributes map. func (c *Credentials) Attributes() map[string]interface{} { return c.attributes } // SetAttribute sets the value of an attribute. func (c *Credentials) SetAttribute(k string, v interface{}) { c.attributes[k] = v } // SetAttributes replaces the attributes map with the one provided. func (c *Credentials) SetAttributes(a map[string]interface{}) { c.attributes = a } // RemoveAttribute deletes an attribute from the attribute map that has the key provided. func (c *Credentials) RemoveAttribute(k string) { delete(c.attributes, k) } // Marshal the Credentials into a byte slice func (c *Credentials) Marshal() ([]byte, error) { gob.Register(map[string]interface{}{}) gob.Register(ADCredentials{}) buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) mc := marshalCredentials{ Username: c.username, DisplayName: c.displayName, Realm: c.realm, CName: c.cname, Keytab: c.HasKeytab(), Password: c.HasPassword(), Attributes: c.attributes, ValidUntil: c.validUntil, Authenticated: c.authenticated, Human: c.human, AuthTime: c.authTime, GroupMembership: c.groupMembership, SessionID: c.sessionID, } err := enc.Encode(&mc) if err != nil { return []byte{}, err } return buf.Bytes(), nil } // Unmarshal a byte slice into Credentials func (c *Credentials) Unmarshal(b []byte) error { gob.Register(map[string]interface{}{}) gob.Register(ADCredentials{}) mc := new(marshalCredentials) buf := bytes.NewBuffer(b) dec := gob.NewDecoder(buf) err := dec.Decode(mc) if err != nil { return err } c.username = mc.Username c.displayName = mc.DisplayName c.realm = mc.Realm c.cname = mc.CName c.attributes = mc.Attributes c.validUntil = mc.ValidUntil c.authenticated = mc.Authenticated c.human = mc.Human c.authTime = mc.AuthTime c.groupMembership = mc.GroupMembership c.sessionID = mc.SessionID return nil } // JSON return details of the Credentials in a JSON format. func (c *Credentials) JSON() (string, error) { mc := marshalCredentials{ Username: c.username, DisplayName: c.displayName, Realm: c.realm, CName: c.cname, Keytab: c.HasKeytab(), Password: c.HasPassword(), ValidUntil: c.validUntil, Authenticated: c.authenticated, Human: c.human, AuthTime: c.authTime, SessionID: c.sessionID, } b, err := json.MarshalIndent(mc, "", " ") if err != nil { return "", err } return string(b), nil } gokrb5-8.4.3/v8/credentials/credentials_test.go000066400000000000000000000011531427031340300214310ustar00rootroot00000000000000package credentials import ( "testing" "github.com/jcmturner/goidentity/v6" "github.com/stretchr/testify/assert" ) func TestImplementsInterface(t *testing.T) { t.Parallel() u := new(Credentials) i := new(goidentity.Identity) assert.Implements(t, i, u, "Credentials type does not implement the Identity interface") } func TestCredentials_Marshal(t *testing.T) { var cred Credentials b, err := cred.Marshal() if err != nil { t.Fatalf("could not marshal credetials: %v", err) } var credum Credentials err = credum.Unmarshal(b) if err != nil { t.Fatalf("could not unmarshal credetials: %v", err) } } gokrb5-8.4.3/v8/crypto/000077500000000000000000000000001427031340300145715ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/aes128-cts-hmac-sha1-96.go000066400000000000000000000105411427031340300207070ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha1" "hash" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc3961" "github.com/jcmturner/gokrb5/v8/crypto/rfc3962" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) // RFC 3962 // Aes128CtsHmacSha96 implements Kerberos encryption type aes128-cts-hmac-sha1-96 type Aes128CtsHmacSha96 struct { } // GetETypeID returns the EType ID number. func (e Aes128CtsHmacSha96) GetETypeID() int32 { return etypeID.AES128_CTS_HMAC_SHA1_96 } // GetHashID returns the checksum type ID number. func (e Aes128CtsHmacSha96) GetHashID() int32 { return chksumtype.HMAC_SHA1_96_AES128 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes128CtsHmacSha96) GetKeyByteSize() int { return 128 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes128CtsHmacSha96) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes128CtsHmacSha96) GetHashFunc() func() hash.Hash { return sha1.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes128CtsHmacSha96) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes128CtsHmacSha96) GetDefaultStringToKeyParams() string { return "00001000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes128CtsHmacSha96) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes128CtsHmacSha96) GetHMACBitLength() int { return 96 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes128CtsHmacSha96) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes128CtsHmacSha96) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { return rfc3962.StringToKey(secret, salt, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes128CtsHmacSha96) RandomToKey(b []byte) []byte { return rfc3961.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes128CtsHmacSha96) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc3962.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes128CtsHmacSha96) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc3962.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes128CtsHmacSha96) DecryptData(key, data []byte) ([]byte, error) { return rfc3962.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes128CtsHmacSha96) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc3962.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes128CtsHmacSha96) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveKey(protocolKey, usage, e) } // DeriveRandom generates data needed for key generation. func (e Aes128CtsHmacSha96) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e Aes128CtsHmacSha96) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes128CtsHmacSha96) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes128CtsHmacSha96) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/v8/crypto/aes128-cts-hmac-sha1-96_test.go000066400000000000000000000036511427031340300217520ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc3962" "github.com/stretchr/testify/assert" ) func TestAes128CtsHmacSha196_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 3962 Appendix B b, _ := hex.DecodeString("1234567878563412") s := string(b) b, _ = hex.DecodeString("f09d849e") s2 := string(b) var tests = []struct { iterations int64 phrase string salt string pbkdf2 string key string }{ {1, "password", "ATHENA.MIT.EDUraeburn", "cdedb5281bb2f801565a1122b2563515", "42263c6e89f4fc28b8df68ee09799f15"}, {2, "password", "ATHENA.MIT.EDUraeburn", "01dbee7f4a9e243e988b62c73cda935d", "c651bf29e2300ac27fa469d693bdda13"}, {1200, "password", "ATHENA.MIT.EDUraeburn", "5c08eb61fdf71e4e4ec3cf6ba1f5512b", "4c01cd46d632d01e6dbe230a01ed642a"}, {5, "password", s, "d1daa78615f287e6a1c8b120d7062a49", "e9b23d52273747dd5c35cb55be619d8e"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase equals block size", "139c30c0966bc32ba55fdbf212530ac9", "59d1bb789a828b1aa54ef9c2883f69ed"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase exceeds block size", "9ccad6d468770cd51b10e6a68721be61", "cb8005dc5f90179a7f02104c0018751d"}, {50, s2, "EXAMPLE.COMpianist", "6b9cf26d45455a43a5b8bb276a403b39", "f149c1f2e154a73452d43e7fe62a56e5"}, } var e Aes128CtsHmacSha96 for i, test := range tests { assert.Equal(t, test.pbkdf2, hex.EncodeToString(rfc3962.StringToPBKDF2(test.phrase, test.salt, test.iterations, e)), "PBKDF2 not as expected") k, err := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(uint32(test.iterations))) if err != nil { t.Errorf("error in processing string to key for test %d: %v", i, err) } assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } gokrb5-8.4.3/v8/crypto/aes128-cts-hmac-sha256-128.go000066400000000000000000000113261427031340300211410ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha256" "hash" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc8009" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) // RFC https://tools.ietf.org/html/rfc8009 // Aes128CtsHmacSha256128 implements Kerberos encryption type aes128-cts-hmac-sha256-128 type Aes128CtsHmacSha256128 struct { } // GetETypeID returns the EType ID number. func (e Aes128CtsHmacSha256128) GetETypeID() int32 { return etypeID.AES128_CTS_HMAC_SHA256_128 } // GetHashID returns the checksum type ID number. func (e Aes128CtsHmacSha256128) GetHashID() int32 { return chksumtype.HMAC_SHA256_128_AES128 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes128CtsHmacSha256128) GetKeyByteSize() int { return 128 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes128CtsHmacSha256128) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes128CtsHmacSha256128) GetHashFunc() func() hash.Hash { return sha256.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes128CtsHmacSha256128) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes128CtsHmacSha256128) GetDefaultStringToKeyParams() string { return "00008000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes128CtsHmacSha256128) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes128CtsHmacSha256128) GetHMACBitLength() int { return 128 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes128CtsHmacSha256128) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes128CtsHmacSha256128) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { saltp := rfc8009.GetSaltP(salt, "aes128-cts-hmac-sha256-128") return rfc8009.StringToKey(secret, saltp, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes128CtsHmacSha256128) RandomToKey(b []byte) []byte { return rfc8009.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes128CtsHmacSha256128) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc8009.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes128CtsHmacSha256128) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc8009.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes128CtsHmacSha256128) DecryptData(key, data []byte) ([]byte, error) { return rfc8009.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes128CtsHmacSha256128) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc8009.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes128CtsHmacSha256128) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveKey(protocolKey, usage, e), nil } // DeriveRandom generates data needed for key generation. func (e Aes128CtsHmacSha256128) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the ciphertext message. // As the hash is calculated over the iv concatenated with the AES cipher output not the plaintext the pt value to this // interface method is not use. Pass any []byte. func (e Aes128CtsHmacSha256128) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { // We don't need ib just there for the interface return rfc8009.VerifyIntegrity(protocolKey, ct, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes128CtsHmacSha256128) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes128CtsHmacSha256128) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/v8/crypto/aes128-cts-hmac-sha256-128_test.go000066400000000000000000000146231427031340300222030ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc8009" "github.com/stretchr/testify/assert" ) func TestAes128CtsHmacSha256128_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A // Random 16bytes in test vector as string r, _ := hex.DecodeString("10DF9DD783E5BC8ACEA1730E74355F61") s := string(r) var tests = []struct { iterations uint32 phrase string salt string saltp string key string }{ {32768, "password", s + "ATHENA.MIT.EDUraeburn", "6165733132382d6374732d686d61632d7368613235362d3132380010df9dd783e5bc8acea1730e74355f61415448454e412e4d49542e4544557261656275726e", "089bca48b105ea6ea77ca5d2f39dc5e7"}, } var e Aes128CtsHmacSha256128 for _, test := range tests { saltp := rfc8009.GetSaltP(test.salt, "aes128-cts-hmac-sha256-128") assert.Equal(t, test.saltp, hex.EncodeToString([]byte(saltp)), "SaltP not as expected") k, _ := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(test.iterations)) assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } func TestAes128CtsHmacSha256128_DeriveKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A protocolBaseKey, _ := hex.DecodeString("3705d96080c17728a0e800eab6e0d23c") testUsage := uint32(2) var e Aes128CtsHmacSha256128 k, err := e.DeriveKey(protocolBaseKey, common.GetUsageKc(testUsage)) if err != nil { t.Fatalf("Error deriving checksum key: %v", err) } assert.Equal(t, "b31a018a48f54776f403e9a396325dc3", hex.EncodeToString(k), "Checksum derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKe(testUsage)) if err != nil { t.Fatalf("Error deriving encryption key: %v", err) } assert.Equal(t, "9b197dd1e8c5609d6e67c3e37c62c72e", hex.EncodeToString(k), "Encryption derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKi(testUsage)) if err != nil { t.Fatalf("Error deriving integrity key: %v", err) } assert.Equal(t, "9fda0e56ab2d85e1569a688696c26a6c", hex.EncodeToString(k), "Integrity derived key not as epxected") } func TestAes128CtsHmacSha256128_VerifyIntegrity(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 protocolBaseKey, _ := hex.DecodeString("3705d96080c17728a0e800eab6e0d23c") testUsage := uint32(2) var e Aes128CtsHmacSha256128 var tests = []struct { kc string pt string chksum string }{ {"b31a018a48f54776f403e9a396325dc3", "000102030405060708090a0b0c0d0e0f1011121314", "d78367186643d67b411cba9139fc1dee"}, } for _, test := range tests { p, _ := hex.DecodeString(test.pt) b, err := e.GetChecksumHash(protocolBaseKey, p, testUsage) if err != nil { t.Errorf("error generating checksum: %v", err) } assert.Equal(t, test.chksum, hex.EncodeToString(b), "Checksum not as expected") } } func TestAes128CtsHmacSha256128_Cypto(t *testing.T) { t.Parallel() protocolBaseKey, _ := hex.DecodeString("3705d96080c17728a0e800eab6e0d23c") testUsage := uint32(2) var tests = []struct { plain string confounder string ke string ki string encrypted string // AESOutput hash string // TruncatedHMACOutput cipher string // Ciphertext(AESOutput|HMACOutput) }{ // Test vectors from RFC 8009 Appendix A {"", "7e5895eaf2672435bad817f545a37148", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "ef85fb890bb8472f4dab20394dca781d", "ad877eda39d50c870c0d5a0a8e48c718", "ef85fb890bb8472f4dab20394dca781dad877eda39d50c870c0d5a0a8e48c718"}, {"000102030405", "7bca285e2fd4130fb55b1a5c83bc5b24", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "84d7f30754ed987bab0bf3506beb09cfb55402cef7e6", "877ce99e247e52d16ed4421dfdf8976c", "84d7f30754ed987bab0bf3506beb09cfb55402cef7e6877ce99e247e52d16ed4421dfdf8976c"}, {"000102030405060708090a0b0c0d0e0f", "56ab21713ff62c0a1457200f6fa9948f", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "3517d640f50ddc8ad3628722b3569d2ae07493fa8263254080ea65c1008e8fc2", "95fb4852e7d83e1e7c48c37eebe6b0d3", "3517d640f50ddc8ad3628722b3569d2ae07493fa8263254080ea65c1008e8fc295fb4852e7d83e1e7c48c37eebe6b0d3"}, {"000102030405060708090a0b0c0d0e0f1011121314", "a7a4e29a4728ce10664fb64e49ad3fac", "9b197dd1e8c5609d6e67c3e37c62c72e", "9fda0e56ab2d85e1569a688696c26a6c", "720f73b18d9859cd6ccb4346115cd336c70f58edc0c4437c5573544c31c813bce1e6d072c1", "86b39a413c2f92ca9b8334a287ffcbfc", "720f73b18d9859cd6ccb4346115cd336c70f58edc0c4437c5573544c31c813bce1e6d072c186b39a413c2f92ca9b8334a287ffcbfc"}, } var e Aes128CtsHmacSha256128 for i, test := range tests { m, _ := hex.DecodeString(test.plain) b, _ := hex.DecodeString(test.encrypted) ke, _ := hex.DecodeString(test.ke) cf, _ := hex.DecodeString(test.confounder) ct, _ := hex.DecodeString(test.cipher) cfm := append(cf, m...) // Test encryption to raw encrypted bytes _, c, err := e.EncryptData(ke, cfm) if err != nil { t.Errorf("encryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.encrypted, hex.EncodeToString(c), "Encrypted result not as expected - test %v", i) // Test decryption of raw encrypted bytes p, err := e.DecryptData(ke, b) //Remove the confounder bytes p = p[e.GetConfounderByteSize():] if err != nil { t.Errorf("decryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.plain, hex.EncodeToString(p), "Decrypted result not as expected - test %v", i) // Test integrity check of complete ciphertext message assert.True(t, e.VerifyIntegrity(protocolBaseKey, ct, ct, testUsage), "Integrity check of cipher text failed") // Test encrypting and decrypting a complete cipertext message (with confounder, integrity hash) _, cm, err := e.EncryptMessage(protocolBaseKey, m, testUsage) if err != nil { t.Errorf("encryption to message failed for test %v: %v", i+1, err) } dm, err := e.DecryptMessage(protocolBaseKey, cm, testUsage) if err != nil { t.Errorf("decrypting complete encrypted message failed for test %v: %v", i+1, err) } assert.Equal(t, m, dm, "Message not as expected after encrypting and decrypting for test %v: %v", i+1, err) // Test the integrity hash ivz := make([]byte, e.GetConfounderByteSize()) hm := append(ivz, b...) mac, _ := common.GetIntegrityHash(hm, protocolBaseKey, testUsage, e) assert.Equal(t, test.hash, hex.EncodeToString(mac), "HMAC result not as expected - test %v", i) } } gokrb5-8.4.3/v8/crypto/aes256-cts-hmac-sha1-96.go000066400000000000000000000105411427031340300207110ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha1" "hash" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc3961" "github.com/jcmturner/gokrb5/v8/crypto/rfc3962" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) // RFC 3962 // Aes256CtsHmacSha96 implements Kerberos encryption type aes256-cts-hmac-sha1-96 type Aes256CtsHmacSha96 struct { } // GetETypeID returns the EType ID number. func (e Aes256CtsHmacSha96) GetETypeID() int32 { return etypeID.AES256_CTS_HMAC_SHA1_96 } // GetHashID returns the checksum type ID number. func (e Aes256CtsHmacSha96) GetHashID() int32 { return chksumtype.HMAC_SHA1_96_AES256 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes256CtsHmacSha96) GetKeyByteSize() int { return 256 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes256CtsHmacSha96) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes256CtsHmacSha96) GetHashFunc() func() hash.Hash { return sha1.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes256CtsHmacSha96) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes256CtsHmacSha96) GetDefaultStringToKeyParams() string { return "00001000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes256CtsHmacSha96) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes256CtsHmacSha96) GetHMACBitLength() int { return 96 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes256CtsHmacSha96) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes256CtsHmacSha96) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { return rfc3962.StringToKey(secret, salt, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes256CtsHmacSha96) RandomToKey(b []byte) []byte { return rfc3961.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes256CtsHmacSha96) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc3962.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes256CtsHmacSha96) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc3962.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes256CtsHmacSha96) DecryptData(key, data []byte) ([]byte, error) { return rfc3962.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes256CtsHmacSha96) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc3962.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes256CtsHmacSha96) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveKey(protocolKey, usage, e) } // DeriveRandom generates data needed for key generation. func (e Aes256CtsHmacSha96) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e Aes256CtsHmacSha96) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes256CtsHmacSha96) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes256CtsHmacSha96) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/v8/crypto/aes256-cts-hmac-sha1-96_test.go000066400000000000000000000045511427031340300217540ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc3962" "github.com/stretchr/testify/assert" ) func TestAes256CtsHmacSha196_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 3962 Appendix B b, _ := hex.DecodeString("1234567878563412") s := string(b) b, _ = hex.DecodeString("f09d849e") s2 := string(b) var tests = []struct { iterations int64 phrase string salt string pbkdf2 string key string }{ {1, "password", "ATHENA.MIT.EDUraeburn", "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837", "fe697b52bc0d3ce14432ba036a92e65bbb52280990a2fa27883998d72af30161"}, {2, "password", "ATHENA.MIT.EDUraeburn", "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86", "a2e16d16b36069c135d5e9d2e25f896102685618b95914b467c67622225824ff"}, {1200, "password", "ATHENA.MIT.EDUraeburn", "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13", "55a6ac740ad17b4846941051e1e8b0a7548d93b0ab30a8bc3ff16280382b8c2a"}, {5, "password", s, "d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee", "97a4e786be20d81a382d5ebc96d5909cabcdadc87ca48f574504159f16c36e31"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase equals block size", "139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1", "89adee3608db8bc71f1bfbfe459486b05618b70cbae22092534e56c553ba4b34"}, {1200, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "pass phrase exceeds block size", "9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a", "d78c5c9cb872a8c9dad4697f0bb5b2d21496c82beb2caeda2112fceea057401b"}, {50, s2, "EXAMPLE.COMpianist", "6b9cf26d45455a43a5b8bb276a403b39e7fe37a0c41e02c281ff3069e1e94f52", "4b6d9839f84406df1f09cc166db4b83c571848b784a3d6bdc346589a3e393f9e"}, } var e Aes256CtsHmacSha96 for i, test := range tests { assert.Equal(t, test.pbkdf2, hex.EncodeToString(rfc3962.StringToPBKDF2(test.phrase, test.salt, test.iterations, e)), "PBKDF2 not as expected") k, err := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(uint32(test.iterations))) if err != nil { t.Errorf("error in processing string to key for test %d: %v", i, err) } assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } gokrb5-8.4.3/v8/crypto/aes256-cts-hmac-sha384-192.go000066400000000000000000000113311427031340300211420ustar00rootroot00000000000000package crypto import ( "crypto/aes" "crypto/hmac" "crypto/sha512" "hash" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc8009" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) // RFC https://tools.ietf.org/html/rfc8009 // Aes256CtsHmacSha384192 implements Kerberos encryption type aes256-cts-hmac-sha384-192 type Aes256CtsHmacSha384192 struct { } // GetETypeID returns the EType ID number. func (e Aes256CtsHmacSha384192) GetETypeID() int32 { return etypeID.AES256_CTS_HMAC_SHA384_192 } // GetHashID returns the checksum type ID number. func (e Aes256CtsHmacSha384192) GetHashID() int32 { return chksumtype.HMAC_SHA384_192_AES256 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Aes256CtsHmacSha384192) GetKeyByteSize() int { return 192 / 8 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Aes256CtsHmacSha384192) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e Aes256CtsHmacSha384192) GetHashFunc() func() hash.Hash { return sha512.New384 } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Aes256CtsHmacSha384192) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Aes256CtsHmacSha384192) GetDefaultStringToKeyParams() string { return "00008000" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Aes256CtsHmacSha384192) GetConfounderByteSize() int { return aes.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Aes256CtsHmacSha384192) GetHMACBitLength() int { return 192 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Aes256CtsHmacSha384192) GetCypherBlockBitLength() int { return aes.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Aes256CtsHmacSha384192) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { saltp := rfc8009.GetSaltP(salt, "aes256-cts-hmac-sha384-192") return rfc8009.StringToKey(secret, saltp, s2kparams, e) } // RandomToKey returns a key from the bytes provided. func (e Aes256CtsHmacSha384192) RandomToKey(b []byte) []byte { return rfc8009.RandomToKey(b) } // EncryptData encrypts the data provided. func (e Aes256CtsHmacSha384192) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc8009.EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Aes256CtsHmacSha384192) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc8009.EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Aes256CtsHmacSha384192) DecryptData(key, data []byte) ([]byte, error) { return rfc8009.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Aes256CtsHmacSha384192) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc8009.DecryptMessage(key, ciphertext, usage, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e Aes256CtsHmacSha384192) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveKey(protocolKey, usage, e), nil } // DeriveRandom generates data needed for key generation. func (e Aes256CtsHmacSha384192) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc8009.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the ciphertext message. // As the hash is calculated over the iv concatenated with the AES cipher output not the plaintext the pt value to this // interface method is not use. Pass any []byte. func (e Aes256CtsHmacSha384192) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { // We don't need ib just there for the interface return rfc8009.VerifyIntegrity(protocolKey, ct, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Aes256CtsHmacSha384192) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Aes256CtsHmacSha384192) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/v8/crypto/aes256-cts-hmac-sha384-192_test.go000066400000000000000000000156621427031340300222140ustar00rootroot00000000000000package crypto import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc8009" "github.com/stretchr/testify/assert" ) func TestAes256CtsHmacSha384192_StringToKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A // Random 16bytes in test vector as string r, _ := hex.DecodeString("10DF9DD783E5BC8ACEA1730E74355F61") s := string(r) var tests = []struct { iterations uint32 phrase string salt string saltp string key string }{ {32768, "password", s + "ATHENA.MIT.EDUraeburn", "6165733235362d6374732d686d61632d7368613338342d3139320010df9dd783e5bc8acea1730e74355f61415448454e412e4d49542e4544557261656275726e", "45bd806dbf6a833a9cffc1c94589a222367a79bc21c413718906e9f578a78467"}, } var e Aes256CtsHmacSha384192 for _, test := range tests { saltp := rfc8009.GetSaltP(test.salt, "aes256-cts-hmac-sha384-192") assert.Equal(t, test.saltp, hex.EncodeToString([]byte(saltp)), "SaltP not as expected") k, _ := e.StringToKey(test.phrase, test.salt, common.IterationsToS2Kparams(test.iterations)) assert.Equal(t, test.key, hex.EncodeToString(k), "String to Key not as expected") } } func TestAes256CtsHmacSha384192_DeriveKey(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 Appendix A protocolBaseKey, _ := hex.DecodeString("6d404d37faf79f9df0d33568d320669800eb4836472ea8a026d16b7182460c52") testUsage := uint32(2) var e Aes256CtsHmacSha384192 k, err := e.DeriveKey(protocolBaseKey, common.GetUsageKc(testUsage)) if err != nil { t.Fatalf("Error deriving checksum key: %v", err) } assert.Equal(t, "ef5718be86cc84963d8bbb5031e9f5c4ba41f28faf69e73d", hex.EncodeToString(k), "Checksum derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKe(testUsage)) if err != nil { t.Fatalf("Error deriving encryption key: %v", err) } assert.Equal(t, "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", hex.EncodeToString(k), "Encryption derived key not as epxected") k, err = e.DeriveKey(protocolBaseKey, common.GetUsageKi(testUsage)) if err != nil { t.Fatalf("Error deriving integrity key: %v", err) } assert.Equal(t, "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", hex.EncodeToString(k), "Integrity derived key not as epxected") } func TestAes256CtsHmacSha384192_Cypto(t *testing.T) { t.Parallel() protocolBaseKey, _ := hex.DecodeString("6d404d37faf79f9df0d33568d320669800eb4836472ea8a026d16b7182460c52") testUsage := uint32(2) var tests = []struct { plain string confounder string ke string ki string encrypted string // AESOutput hash string // TruncatedHMACOutput cipher string // Ciphertext(AESOutput|HMACOutput) }{ // Test vectors from RFC 8009 Appendix A {"", "f764e9fa15c276478b2c7d0c4e5f58e4", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "41f53fa5bfe7026d91faf9be959195a0", "58707273a96a40f0a01960621ac612748b9bbfbe7eb4ce3c", "41f53fa5bfe7026d91faf9be959195a058707273a96a40f0a01960621ac612748b9bbfbe7eb4ce3c"}, {"000102030405", "b80d3251c1f6471494256ffe712d0b9a", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "4ed7b37c2bcac8f74f23c1cf07e62bc7b75fb3f637b9", "f559c7f664f69eab7b6092237526ea0d1f61cb20d69d10f2", "4ed7b37c2bcac8f74f23c1cf07e62bc7b75fb3f637b9f559c7f664f69eab7b6092237526ea0d1f61cb20d69d10f2"}, {"000102030405060708090a0b0c0d0e0f", "53bf8a0d105265d4e276428624ce5e63", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "bc47ffec7998eb91e8115cf8d19dac4bbbe2e163e87dd37f49beca92027764f6", "8cf51f14d798c2273f35df574d1f932e40c4ff255b36a266", "bc47ffec7998eb91e8115cf8d19dac4bbbe2e163e87dd37f49beca92027764f68cf51f14d798c2273f35df574d1f932e40c4ff255b36a266"}, {"000102030405060708090a0b0c0d0e0f1011121314", "763e65367e864f02f55153c7e3b58af1", "56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49", "69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f", "40013e2df58e8751957d2878bcd2d6fe101ccfd556cb1eae79db3c3ee86429f2b2a602ac86", "fef6ecb647d6295fae077a1feb517508d2c16b4192e01f62", "40013e2df58e8751957d2878bcd2d6fe101ccfd556cb1eae79db3c3ee86429f2b2a602ac86fef6ecb647d6295fae077a1feb517508d2c16b4192e01f62"}, } var e Aes256CtsHmacSha384192 for i, test := range tests { m, _ := hex.DecodeString(test.plain) b, _ := hex.DecodeString(test.encrypted) ke, _ := hex.DecodeString(test.ke) cf, _ := hex.DecodeString(test.confounder) ct, _ := hex.DecodeString(test.cipher) cfm := append(cf, m...) // Test encryption to raw encrypted bytes _, c, err := e.EncryptData(ke, cfm) if err != nil { t.Errorf("encryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.encrypted, hex.EncodeToString(c), "Encrypted result not as expected - test %v", i) // Test decryption of raw encrypted bytes p, err := e.DecryptData(ke, b) //Remove the confounder bytes p = p[e.GetConfounderByteSize():] if err != nil { t.Errorf("decryption failed for test %v: %v", i+1, err) } assert.Equal(t, test.plain, hex.EncodeToString(p), "Decrypted result not as expected - test %v", i) // Test integrity check of complete ciphertext message assert.True(t, e.VerifyIntegrity(protocolBaseKey, ct, ct, testUsage), "Integrity check of cipher text failed") // Test encrypting and decrypting a complete cipertext message (with confounder, integrity hash) _, cm, err := e.EncryptMessage(protocolBaseKey, m, testUsage) if err != nil { t.Errorf("encryption to message failed for test %v: %v", i+1, err) } dm, err := e.DecryptMessage(protocolBaseKey, cm, testUsage) if err != nil { t.Errorf("decrypting complete encrypted message failed for test %v: %v", i+1, err) } assert.Equal(t, m, dm, "Message not as expected after encrypting and decrypting for test %v: %v", i+1, err) // Test the integrity hash ivz := make([]byte, e.GetConfounderByteSize()) hm := append(ivz, b...) mac, _ := common.GetIntegrityHash(hm, protocolBaseKey, testUsage, e) assert.Equal(t, test.hash, hex.EncodeToString(mac), "HMAC result not as expected - test %v", i) } } func TestAes256CtsHmacSha384192_VerifyIntegrity(t *testing.T) { t.Parallel() // Test vectors from RFC 8009 protocolBaseKey, _ := hex.DecodeString("6d404d37faf79f9df0d33568d320669800eb4836472ea8a026d16b7182460c52") testUsage := uint32(2) var e Aes256CtsHmacSha384192 var tests = []struct { kc string pt string chksum string }{ {"ef5718be86cc84963d8bbb5031e9f5c4ba41f28faf69e73d", "000102030405060708090a0b0c0d0e0f1011121314", "45ee791567eefca37f4ac1e0222de80d43c3bfa06699672a"}, } for _, test := range tests { p, _ := hex.DecodeString(test.pt) b, err := e.GetChecksumHash(protocolBaseKey, p, testUsage) if err != nil { t.Errorf("error generating checksum: %v", err) } assert.Equal(t, test.chksum, hex.EncodeToString(b), "Checksum not as expected") } } gokrb5-8.4.3/v8/crypto/common/000077500000000000000000000000001427031340300160615ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/common/common.go000066400000000000000000000076441427031340300177130ustar00rootroot00000000000000// Package common provides encryption methods common across encryption types package common import ( "bytes" "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) // ZeroPad pads bytes with zeros to nearest multiple of message size m. func ZeroPad(b []byte, m int) ([]byte, error) { if m <= 0 { return nil, errors.New("Invalid message block size when padding") } if b == nil || len(b) == 0 { return nil, errors.New("Data not valid to pad: Zero size") } if l := len(b) % m; l != 0 { n := m - l z := make([]byte, n) b = append(b, z...) } return b, nil } // PKCS7Pad pads bytes according to RFC 2315 to nearest multiple of message size m. func PKCS7Pad(b []byte, m int) ([]byte, error) { if m <= 0 { return nil, errors.New("Invalid message block size when padding") } if b == nil || len(b) == 0 { return nil, errors.New("Data not valid to pad: Zero size") } n := m - (len(b) % m) pb := make([]byte, len(b)+n) copy(pb, b) copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) return pb, nil } // PKCS7Unpad removes RFC 2315 padding from byes where message size is m. func PKCS7Unpad(b []byte, m int) ([]byte, error) { if m <= 0 { return nil, errors.New("invalid message block size when unpadding") } if b == nil || len(b) == 0 { return nil, errors.New("padded data not valid: Zero size") } if len(b)%m != 0 { return nil, errors.New("padded data not valid: Not multiple of message block size") } c := b[len(b)-1] n := int(c) if n == 0 || n > len(b) { return nil, errors.New("padded data not valid: Data may not have been padded") } for i := 0; i < n; i++ { if b[len(b)-n+i] != c { return nil, errors.New("padded data not valid") } } return b[:len(b)-n], nil } // GetHash generates the keyed hash value according to the etype's hash function. func GetHash(pt, key []byte, usage []byte, etype etype.EType) ([]byte, error) { k, err := etype.DeriveKey(key, usage) if err != nil { return nil, fmt.Errorf("unable to derive key for checksum: %v", err) } mac := hmac.New(etype.GetHashFunc(), k) p := make([]byte, len(pt)) copy(p, pt) mac.Write(p) return mac.Sum(nil)[:etype.GetHMACBitLength()/8], nil } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func GetChecksumHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) { return GetHash(b, key, GetUsageKc(usage), etype) } // GetIntegrityHash returns a keyed integrity hash of the bytes provided. func GetIntegrityHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) { return GetHash(b, key, GetUsageKi(usage), etype) } // VerifyChecksum compares the checksum of the msg bytes is the same as the checksum provided. func VerifyChecksum(key, chksum, msg []byte, usage uint32, etype etype.EType) bool { //The encrypted message is a concatenation of the encrypted output and the hash HMAC. expectedMAC, _ := GetChecksumHash(msg, key, usage, etype) return hmac.Equal(chksum, expectedMAC) } // GetUsageKc returns the checksum key usage value for the usage number un. // // See RFC 3961 5.3 key-derivation function definition. func GetUsageKc(un uint32) []byte { return getUsage(un, 0x99) } // GetUsageKe returns the encryption key usage value for the usage number un // // See RFC 3961 5.3 key-derivation function definition. func GetUsageKe(un uint32) []byte { return getUsage(un, 0xAA) } // GetUsageKi returns the integrity key usage value for the usage number un // // See RFC 3961 5.3 key-derivation function definition. func GetUsageKi(un uint32) []byte { return getUsage(un, 0x55) } func getUsage(un uint32, o byte) []byte { var buf bytes.Buffer binary.Write(&buf, binary.BigEndian, un) return append(buf.Bytes(), o) } // IterationsToS2Kparams converts the number of iterations as an integer to a string representation. func IterationsToS2Kparams(i uint32) string { b := make([]byte, 4, 4) binary.BigEndian.PutUint32(b, i) return hex.EncodeToString(b) } gokrb5-8.4.3/v8/crypto/crypto.go000066400000000000000000000117231427031340300164440ustar00rootroot00000000000000// Package crypto implements cryptographic functions for Kerberos 5 implementation. package crypto import ( "encoding/hex" "fmt" "github.com/jcmturner/gokrb5/v8/crypto/etype" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/types" ) // GetEtype returns an instances of the required etype struct for the etype ID. func GetEtype(id int32) (etype.EType, error) { switch id { case etypeID.AES128_CTS_HMAC_SHA1_96: var et Aes128CtsHmacSha96 return et, nil case etypeID.AES256_CTS_HMAC_SHA1_96: var et Aes256CtsHmacSha96 return et, nil case etypeID.AES128_CTS_HMAC_SHA256_128: var et Aes128CtsHmacSha256128 return et, nil case etypeID.AES256_CTS_HMAC_SHA384_192: var et Aes256CtsHmacSha384192 return et, nil case etypeID.DES3_CBC_SHA1_KD: var et Des3CbcSha1Kd return et, nil case etypeID.RC4_HMAC: var et RC4HMAC return et, nil default: return nil, fmt.Errorf("unknown or unsupported EType: %d", id) } } // GetChksumEtype returns an instances of the required etype struct for the checksum ID. func GetChksumEtype(id int32) (etype.EType, error) { switch id { case chksumtype.HMAC_SHA1_96_AES128: var et Aes128CtsHmacSha96 return et, nil case chksumtype.HMAC_SHA1_96_AES256: var et Aes256CtsHmacSha96 return et, nil case chksumtype.HMAC_SHA256_128_AES128: var et Aes128CtsHmacSha256128 return et, nil case chksumtype.HMAC_SHA384_192_AES256: var et Aes256CtsHmacSha384192 return et, nil case chksumtype.HMAC_SHA1_DES3_KD: var et Des3CbcSha1Kd return et, nil case chksumtype.KERB_CHECKSUM_HMAC_MD5: var et RC4HMAC return et, nil //case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED: // var et RC4HMAC // return et, nil default: return nil, fmt.Errorf("unknown or unsupported checksum type: %d", id) } } // GetKeyFromPassword generates an encryption key from the principal's password. func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) { var key types.EncryptionKey et, err := GetEtype(etypeID) if err != nil { return key, et, fmt.Errorf("error getting encryption type: %v", err) } sk2p := et.GetDefaultStringToKeyParams() var salt string var paID int32 for _, pa := range pas { switch pa.PADataType { case patype.PA_PW_SALT: if paID > pa.PADataType { continue } salt = string(pa.PADataValue) case patype.PA_ETYPE_INFO: if paID > pa.PADataType { continue } var eti types.ETypeInfo err := eti.Unmarshal(pa.PADataValue) if err != nil { return key, et, fmt.Errorf("error unmashaling PA Data to PA-ETYPE-INFO2: %v", err) } if etypeID != eti[0].EType { et, err = GetEtype(eti[0].EType) if err != nil { return key, et, fmt.Errorf("error getting encryption type: %v", err) } } salt = string(eti[0].Salt) case patype.PA_ETYPE_INFO2: if paID > pa.PADataType { continue } var et2 types.ETypeInfo2 err := et2.Unmarshal(pa.PADataValue) if err != nil { return key, et, fmt.Errorf("error unmashalling PA Data to PA-ETYPE-INFO2: %v", err) } if etypeID != et2[0].EType { et, err = GetEtype(et2[0].EType) if err != nil { return key, et, fmt.Errorf("error getting encryption type: %v", err) } } if len(et2[0].S2KParams) == 4 { sk2p = hex.EncodeToString(et2[0].S2KParams) } salt = et2[0].Salt } } if salt == "" { salt = cname.GetSalt(realm) } k, err := et.StringToKey(passwd, salt, sk2p) if err != nil { return key, et, fmt.Errorf("error deriving key from string: %+v", err) } key = types.EncryptionKey{ KeyType: etypeID, KeyValue: k, } return key, et, nil } // GetEncryptedData encrypts the data provided and returns and EncryptedData type. // Pass a usage value of zero to use the key provided directly rather than deriving one. func GetEncryptedData(plainBytes []byte, key types.EncryptionKey, usage uint32, kvno int) (types.EncryptedData, error) { var ed types.EncryptedData et, err := GetEtype(key.KeyType) if err != nil { return ed, fmt.Errorf("error getting etype: %v", err) } _, b, err := et.EncryptMessage(key.KeyValue, plainBytes, usage) if err != nil { return ed, err } ed = types.EncryptedData{ EType: key.KeyType, Cipher: b, KVNO: kvno, } return ed, nil } // DecryptEncPart decrypts the EncryptedData. func DecryptEncPart(ed types.EncryptedData, key types.EncryptionKey, usage uint32) ([]byte, error) { return DecryptMessage(ed.Cipher, key, usage) } // DecryptMessage decrypts the ciphertext and verifies the integrity. func DecryptMessage(ciphertext []byte, key types.EncryptionKey, usage uint32) ([]byte, error) { et, err := GetEtype(key.KeyType) if err != nil { return []byte{}, fmt.Errorf("error decrypting: %v", err) } b, err := et.DecryptMessage(key.KeyValue, ciphertext, usage) if err != nil { return nil, fmt.Errorf("error decrypting: %v", err) } return b, nil } gokrb5-8.4.3/v8/crypto/des3-cbc-sha1-kd.go000066400000000000000000000107631427031340300177400ustar00rootroot00000000000000package crypto import ( "crypto/des" "crypto/hmac" "crypto/sha1" "errors" "hash" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/rfc3961" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) //RFC: 3961 Section 6.3 // Des3CbcSha1Kd implements Kerberos encryption type des3-cbc-hmac-sha1-kd type Des3CbcSha1Kd struct { } // GetETypeID returns the EType ID number. func (e Des3CbcSha1Kd) GetETypeID() int32 { return etypeID.DES3_CBC_SHA1_KD } // GetHashID returns the checksum type ID number. func (e Des3CbcSha1Kd) GetHashID() int32 { return chksumtype.HMAC_SHA1_DES3_KD } // GetKeyByteSize returns the number of bytes for key of this etype. func (e Des3CbcSha1Kd) GetKeyByteSize() int { return 24 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e Des3CbcSha1Kd) GetKeySeedBitLength() int { return 21 * 8 } // GetHashFunc returns the hash function for this etype. func (e Des3CbcSha1Kd) GetHashFunc() func() hash.Hash { return sha1.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e Des3CbcSha1Kd) GetMessageBlockByteSize() int { //For traditional CBC mode with padding, it would be the underlying cipher's block size return des.BlockSize } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e Des3CbcSha1Kd) GetDefaultStringToKeyParams() string { var s string return s } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e Des3CbcSha1Kd) GetConfounderByteSize() int { return des.BlockSize } // GetHMACBitLength returns the bit count size of the integrity hash. func (e Des3CbcSha1Kd) GetHMACBitLength() int { return e.GetHashFunc()().Size() * 8 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e Des3CbcSha1Kd) GetCypherBlockBitLength() int { return des.BlockSize * 8 } // StringToKey returns a key derived from the string provided. func (e Des3CbcSha1Kd) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { if s2kparams != "" { return []byte{}, errors.New("s2kparams must be an empty string") } return rfc3961.DES3StringToKey(secret, salt, e) } // RandomToKey returns a key from the bytes provided. func (e Des3CbcSha1Kd) RandomToKey(b []byte) []byte { return rfc3961.DES3RandomToKey(b) } // DeriveRandom generates data needed for key generation. func (e Des3CbcSha1Kd) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { r, err := rfc3961.DeriveRandom(protocolKey, usage, e) return r, err } // DeriveKey derives a key from the protocol key based on the usage value. func (e Des3CbcSha1Kd) DeriveKey(protocolKey, usage []byte) ([]byte, error) { r, err := e.DeriveRandom(protocolKey, usage) if err != nil { return nil, err } return e.RandomToKey(r), nil } // EncryptData encrypts the data provided. func (e Des3CbcSha1Kd) EncryptData(key, data []byte) ([]byte, []byte, error) { return rfc3961.DES3EncryptData(key, data, e) } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e Des3CbcSha1Kd) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { return rfc3961.DES3EncryptMessage(key, message, usage, e) } // DecryptData decrypts the data provided. func (e Des3CbcSha1Kd) DecryptData(key, data []byte) ([]byte, error) { return rfc3961.DES3DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e Des3CbcSha1Kd) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc3961.DES3DecryptMessage(key, ciphertext, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e Des3CbcSha1Kd) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e Des3CbcSha1Kd) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e Des3CbcSha1Kd) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { c, err := e.GetChecksumHash(protocolKey, data, usage) if err != nil { return false } return hmac.Equal(chksum, c) } gokrb5-8.4.3/v8/crypto/des3-cbc-sha1-kd_test.go000066400000000000000000000062621427031340300207760ustar00rootroot00000000000000package crypto import ( "encoding/hex" "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestDes3CbcSha1Kd_DR_DK(t *testing.T) { t.Parallel() // Test vectors from RFC 3961 Appendix A3 var tests = []struct { key string usage string dr string dk string }{ {"dce06b1f64c857a11c3db57c51899b2cc1791008ce973b92", "0000000155", "935079d14490a75c3093c4a6e8c3b049c71e6ee705", "925179d04591a79b5d3192c4a7e9c289b049c71f6ee604cd"}, {"5e13d31c70ef765746578531cb51c15bf11ca82c97cee9f2", "00000001aa", "9f58e5a047d894101c469845d67ae3c5249ed812f2", "9e58e5a146d9942a101c469845d67a20e3c4259ed913f207"}, {"98e6fd8a04a4b6859b75a176540b9752bad3ecd610a252bc", "0000000155", "12fff90c773f956d13fc2ca0d0840349dbd39908eb", "13fef80d763e94ec6d13fd2ca1d085070249dad39808eabf"}, {"622aec25a2fe2cad7094680b7c64940280084c1a7cec92b5", "00000001aa", "f8debf05b097e7dc0603686aca35d91fd9a5516a70", "f8dfbf04b097e6d9dc0702686bcb3489d91fd9a4516b703e"}, {"d3f8298ccb166438dcb9b93ee5a7629286a491f838f802fb", "6b65726265726f73", "2270db565d2a3d64cfbfdc5305d4f778a6de42d9da", "2370da575d2a3da864cebfdc5204d56df779a7df43d9da43"}, {"c1081649ada74362e6a1459d01dfd30d67c2234c940704da", "0000000155", "348056ec98fcc517171d2b4d7a9493af482d999175", "348057ec98fdc48016161c2a4c7a943e92ae492c989175f7"}, {"5d154af238f46713155719d55e2f1f790dd661f279a7917c", "00000001aa", "a8818bc367dadacbe9a6c84627fb60c294b01215e5", "a8808ac267dada3dcbe9a7c84626fbc761c294b01315e5c1"}, {"798562e049852f57dc8c343ba17f2ca1d97394efc8adc443", "0000000155", "c813f88b3be2b2f75424ce9175fbc8483b88c8713a", "c813f88a3be3b334f75425ce9175fbe3c8493b89c8703b49"}, {"26dce334b545292f2feab9a8701a89a4b99eb9942cecd016", "00000001aa", "f58efc6f83f93e55e695fd252cf8fe59f7d5ba37ec", "f48ffd6e83f83e7354e694fd252cf83bfe58f7d5ba37ec5d"}, } for _, test := range tests { var e Des3CbcSha1Kd key, _ := hex.DecodeString(test.key) usage, _ := hex.DecodeString(test.usage) derivedRandom, err := e.DeriveRandom(key, usage) if err != nil { t.Fatal(fmt.Sprintf("Error in deriveRandom: %v", err)) } assert.Equal(t, test.dr, hex.EncodeToString(derivedRandom), "DR not as expected") derivedKey, err := e.DeriveKey(key, usage) if err != nil { t.Fatal(fmt.Sprintf("Error in deriveKey: %v", err)) } assert.Equal(t, test.dk, hex.EncodeToString(derivedKey), "DK not as expected") } } func TestDes3CbcSha1Kd_StringToKey(t *testing.T) { t.Parallel() var tests = []struct { salt string secret string key string }{ {"ATHENA.MIT.EDUraeburn", "password", "850bb51358548cd05e86768c313e3bfef7511937dcf72c3e"}, {"WHITEHOUSE.GOVdanny", "potatoe", "dfcd233dd0a43204ea6dc437fb15e061b02979c1f74f377a"}, {"EXAMPLE.COMbuckaroo", "penny", "6d2fcdf2d6fbbc3ddcadb5da5710a23489b0d3b69d5d9d4a"}, {"ATHENA.MIT.EDUJuri" + "\u0161" + "i" + "\u0107", "\u00DF", "16d5a40e1ce3bacb61b9dce00470324c831973a7b952feb0"}, {"EXAMPLE.COMpianist", "𝄞", "85763726585dbc1cce6ec43e1f751f07f1c4cbb098f40b19"}, } var e Des3CbcSha1Kd for _, test := range tests { key, err := e.StringToKey(test.secret, test.salt, "") if err != nil { t.Errorf("error in StringToKey: %v", err) } assert.Equal(t, test.key, hex.EncodeToString(key), "StringToKey not as expected") } } gokrb5-8.4.3/v8/crypto/etype/000077500000000000000000000000001427031340300157175ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/etype/etype.go000066400000000000000000000021201427031340300173670ustar00rootroot00000000000000// Package etype provides the Kerberos Encryption Type interface package etype import "hash" // EType is the interface defining the Encryption Type. type EType interface { GetETypeID() int32 GetHashID() int32 GetKeyByteSize() int GetKeySeedBitLength() int GetDefaultStringToKeyParams() string StringToKey(string, salt, s2kparams string) ([]byte, error) RandomToKey(b []byte) []byte GetHMACBitLength() int GetMessageBlockByteSize() int EncryptData(key, data []byte) ([]byte, []byte, error) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) DecryptData(key, data []byte) ([]byte, error) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) GetCypherBlockBitLength() int GetConfounderByteSize() int DeriveKey(protocolKey, usage []byte) ([]byte, error) DeriveRandom(protocolKey, usage []byte) ([]byte, error) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool GetHashFunc() func() hash.Hash } gokrb5-8.4.3/v8/crypto/rc4-hmac.go000066400000000000000000000101541427031340300165170ustar00rootroot00000000000000package crypto import ( "bytes" "crypto/hmac" "crypto/md5" "hash" "io" "github.com/jcmturner/gokrb5/v8/crypto/rfc3961" "github.com/jcmturner/gokrb5/v8/crypto/rfc4757" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "golang.org/x/crypto/md4" ) // RC4HMAC implements Kerberos encryption type rc4-hmac type RC4HMAC struct { } // GetETypeID returns the EType ID number. func (e RC4HMAC) GetETypeID() int32 { return etypeID.RC4_HMAC } // GetHashID returns the checksum type ID number. func (e RC4HMAC) GetHashID() int32 { return chksumtype.KERB_CHECKSUM_HMAC_MD5 } // GetKeyByteSize returns the number of bytes for key of this etype. func (e RC4HMAC) GetKeyByteSize() int { return 16 } // GetKeySeedBitLength returns the number of bits for the seed for key generation. func (e RC4HMAC) GetKeySeedBitLength() int { return e.GetKeyByteSize() * 8 } // GetHashFunc returns the hash function for this etype. func (e RC4HMAC) GetHashFunc() func() hash.Hash { return md5.New } // GetMessageBlockByteSize returns the block size for the etype's messages. func (e RC4HMAC) GetMessageBlockByteSize() int { return 1 } // GetDefaultStringToKeyParams returns the default key derivation parameters in string form. func (e RC4HMAC) GetDefaultStringToKeyParams() string { return "" } // GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations. func (e RC4HMAC) GetConfounderByteSize() int { return 8 } // GetHMACBitLength returns the bit count size of the integrity hash. func (e RC4HMAC) GetHMACBitLength() int { return md5.Size * 8 } // GetCypherBlockBitLength returns the bit count size of the cypher block. func (e RC4HMAC) GetCypherBlockBitLength() int { return 8 // doesn't really apply } // StringToKey returns a key derived from the string provided. func (e RC4HMAC) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) { return rfc4757.StringToKey(secret) } // RandomToKey returns a key from the bytes provided. func (e RC4HMAC) RandomToKey(b []byte) []byte { r := bytes.NewReader(b) h := md4.New() io.Copy(h, r) return h.Sum(nil) } // EncryptData encrypts the data provided. func (e RC4HMAC) EncryptData(key, data []byte) ([]byte, []byte, error) { b, err := rfc4757.EncryptData(key, data, e) return []byte{}, b, err } // EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message. func (e RC4HMAC) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) { b, err := rfc4757.EncryptMessage(key, message, usage, false, e) return []byte{}, b, err } // DecryptData decrypts the data provided. func (e RC4HMAC) DecryptData(key, data []byte) ([]byte, error) { return rfc4757.DecryptData(key, data, e) } // DecryptMessage decrypts the message provided and verifies the integrity of the message. func (e RC4HMAC) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) { return rfc4757.DecryptMessage(key, ciphertext, usage, false, e) } // DeriveKey derives a key from the protocol key based on the usage value. func (e RC4HMAC) DeriveKey(protocolKey, usage []byte) ([]byte, error) { return rfc4757.HMAC(protocolKey, usage), nil } // DeriveRandom generates data needed for key generation. func (e RC4HMAC) DeriveRandom(protocolKey, usage []byte) ([]byte, error) { return rfc3961.DeriveRandom(protocolKey, usage, e) } // VerifyIntegrity checks the integrity of the plaintext message. func (e RC4HMAC) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool { return rfc4757.VerifyIntegrity(protocolKey, pt, ct, e) } // GetChecksumHash returns a keyed checksum hash of the bytes provided. func (e RC4HMAC) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) { return rfc4757.Checksum(protocolKey, usage, data) } // VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided. func (e RC4HMAC) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool { checksum, err := rfc4757.Checksum(protocolKey, usage, data) if err != nil { return false } return hmac.Equal(checksum, chksum) } gokrb5-8.4.3/v8/crypto/rfc3961/000077500000000000000000000000001427031340300156665ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/rfc3961/encryption.go000066400000000000000000000101201427031340300204010ustar00rootroot00000000000000// Package rfc3961 provides encryption and checksum methods as specified in RFC 3961 package rfc3961 import ( "crypto/cipher" "crypto/des" "crypto/hmac" "crypto/rand" "errors" "fmt" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) // DES3EncryptData encrypts the data provided using DES3 and methods specific to the etype provided. func DES3EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) { if len(key) != e.GetKeyByteSize() { return nil, nil, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } data, _ = common.ZeroPad(data, e.GetMessageBlockByteSize()) block, err := des.NewTripleDESCipher(key) if err != nil { return nil, nil, fmt.Errorf("error creating cipher: %v", err) } //RFC 3961: initial cipher state All bits zero ivz := make([]byte, des.BlockSize) ct := make([]byte, len(data)) mode := cipher.NewCBCEncrypter(block, ivz) mode.CryptBlocks(ct, data) return ct[len(ct)-e.GetMessageBlockByteSize():], ct, nil } // DES3EncryptMessage encrypts the message provided using DES3 and methods specific to the etype provided. // The encrypted data is concatenated with its integrity hash to create an encrypted message. func DES3EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) { //confounder c := make([]byte, e.GetConfounderByteSize()) _, err := rand.Read(c) if err != nil { return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err) } plainBytes := append(c, message...) plainBytes, _ = common.ZeroPad(plainBytes, e.GetMessageBlockByteSize()) // Derive key for encryption from usage var k []byte if usage != 0 { k, err = e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err) } } iv, b, err := e.EncryptData(k, plainBytes) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } // Generate and append integrity hash ih, err := common.GetIntegrityHash(plainBytes, key, usage, e) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } b = append(b, ih...) return iv, b, nil } // DES3DecryptData decrypts the data provided using DES3 and methods specific to the etype provided. func DES3DecryptData(key, data []byte, e etype.EType) ([]byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } if len(data) < des.BlockSize || len(data)%des.BlockSize != 0 { return []byte{}, errors.New("ciphertext is not a multiple of the block size") } block, err := des.NewTripleDESCipher(key) if err != nil { return []byte{}, fmt.Errorf("error creating cipher: %v", err) } pt := make([]byte, len(data)) ivz := make([]byte, des.BlockSize) mode := cipher.NewCBCDecrypter(block, ivz) mode.CryptBlocks(pt, data) return pt, nil } // DES3DecryptMessage decrypts the message provided using DES3 and methods specific to the etype provided. // The integrity of the message is also verified. func DES3DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) { //Derive the key k, err := e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return nil, fmt.Errorf("error deriving key: %v", err) } // Strip off the checksum from the end b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8]) if err != nil { return nil, fmt.Errorf("error decrypting: %v", err) } //Verify checksum if !e.VerifyIntegrity(key, ciphertext, b, usage) { return nil, errors.New("error decrypting: integrity verification failed") } //Remove the confounder bytes return b[e.GetConfounderByteSize():], nil } // VerifyIntegrity verifies the integrity of cipertext bytes ct. func VerifyIntegrity(key, ct, pt []byte, usage uint32, etype etype.EType) bool { h := make([]byte, etype.GetHMACBitLength()/8) copy(h, ct[len(ct)-etype.GetHMACBitLength()/8:]) expectedMAC, _ := common.GetIntegrityHash(pt, key, usage, etype) return hmac.Equal(h, expectedMAC) } gokrb5-8.4.3/v8/crypto/rfc3961/keyDerivation.go000066400000000000000000000113651427031340300210400ustar00rootroot00000000000000package rfc3961 import ( "bytes" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) const ( prfconstant = "prf" ) // DeriveRandom implements the RFC 3961 defined function: DR(Key, Constant) = k-truncate(E(Key, Constant, initial-cipher-state)). // // key: base key or protocol key. Likely to be a key from a keytab file. // // usage: a constant. // // n: block size in bits (not bytes) - note if you use something like aes.BlockSize this is in bytes. // // k: key length / key seed length in bits. Eg. for AES256 this value is 256. // // e: the encryption etype function to use. func DeriveRandom(key, usage []byte, e etype.EType) ([]byte, error) { n := e.GetCypherBlockBitLength() k := e.GetKeySeedBitLength() //Ensure the usage constant is at least the size of the cypher block size. Pass it through the nfold algorithm that will "stretch" it if needs be. nFoldUsage := Nfold(usage, n) //k-truncate implemented by creating a byte array the size of k (k is in bits hence /8) out := make([]byte, k/8) // Keep feeding the output back into the encryption function until it is no longer short than k. _, K, err := e.EncryptData(key, nFoldUsage) if err != nil { return out, err } for i := copy(out, K); i < len(out); { _, K, _ = e.EncryptData(key, K) i = i + copy(out[i:], K) } return out, nil } // DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods. func DeriveKey(protocolKey, usage []byte, e etype.EType) ([]byte, error) { r, err := e.DeriveRandom(protocolKey, usage) if err != nil { return nil, err } return e.RandomToKey(r), nil } // RandomToKey returns a key from the bytes provided according to the definition in RFC 3961. func RandomToKey(b []byte) []byte { return b } // DES3RandomToKey returns a key from the bytes provided according to the definition in RFC 3961 for DES3 etypes. func DES3RandomToKey(b []byte) []byte { r := fixWeakKey(stretch56Bits(b[:7])) r2 := fixWeakKey(stretch56Bits(b[7:14])) r = append(r, r2...) r3 := fixWeakKey(stretch56Bits(b[14:21])) r = append(r, r3...) return r } // DES3StringToKey returns a key derived from the string provided according to the definition in RFC 3961 for DES3 etypes. func DES3StringToKey(secret, salt string, e etype.EType) ([]byte, error) { s := secret + salt tkey := e.RandomToKey(Nfold([]byte(s), e.GetKeySeedBitLength())) return e.DeriveKey(tkey, []byte("kerberos")) } // PseudoRandom function as defined in RFC 3961 func PseudoRandom(key, b []byte, e etype.EType) ([]byte, error) { h := e.GetHashFunc()() h.Write(b) tmp := h.Sum(nil)[:e.GetMessageBlockByteSize()] k, err := e.DeriveKey(key, []byte(prfconstant)) if err != nil { return []byte{}, err } _, prf, err := e.EncryptData(k, tmp) if err != nil { return []byte{}, err } return prf, nil } func stretch56Bits(b []byte) []byte { d := make([]byte, len(b), len(b)) copy(d, b) var lb byte for i, v := range d { bv, nb := calcEvenParity(v) d[i] = nb if bv != 0 { lb = lb | (1 << uint(i+1)) } else { lb = lb &^ (1 << uint(i+1)) } } _, lb = calcEvenParity(lb) d = append(d, lb) return d } func calcEvenParity(b byte) (uint8, uint8) { lowestbit := b & 0x01 // c counter of 1s in the first 7 bits of the byte var c int // Iterate over the highest 7 bits (hence p starts at 1 not zero) and count the 1s. for p := 1; p < 8; p++ { v := b & (1 << uint(p)) if v != 0 { c++ } } if c%2 == 0 { //Even number of 1s so set parity to 1 b = b | 1 } else { //Odd number of 1s so set parity to 0 b = b &^ 1 } return lowestbit, b } func fixWeakKey(b []byte) []byte { if weak(b) { b[7] ^= 0xF0 } return b } func weak(b []byte) bool { // weak keys from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-67r1.pdf weakKeys := [4][]byte{ {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, {0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE}, {0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1}, {0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E}, } semiWeakKeys := [12][]byte{ {0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E}, {0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01}, {0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1}, {0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01}, {0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE}, {0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01}, {0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1}, {0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E}, {0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE}, {0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E}, {0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE}, {0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1}, } for _, k := range weakKeys { if bytes.Equal(b, k) { return true } } for _, k := range semiWeakKeys { if bytes.Equal(b, k) { return true } } return false } gokrb5-8.4.3/v8/crypto/rfc3961/keyDerivation_test.go000066400000000000000000000027071427031340300220770ustar00rootroot00000000000000package rfc3961 import "testing" func TestFixWeakKey(t *testing.T) { var weakKeys = []struct { key []byte lastbyte byte }{ {[]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, byte(0xF1)}, {[]byte{0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE}, byte(0x0E)}, {[]byte{0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1}, byte(0x01)}, {[]byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E}, byte(0xFE)}, {[]byte{0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E}, byte(0xFE)}, {[]byte{0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01}, byte(0xF1)}, {[]byte{0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1}, byte(0x01)}, {[]byte{0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01}, byte(0xF1)}, {[]byte{0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE}, byte(0x0E)}, {[]byte{0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01}, byte(0xF1)}, {[]byte{0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1}, byte(0x01)}, {[]byte{0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E}, byte(0xFE)}, {[]byte{0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE}, byte(0x0E)}, {[]byte{0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E}, byte(0xFE)}, {[]byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE}, byte(0x0E)}, {[]byte{0xFE, 0x2F, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1}, byte(0xF1)}, // Non weak key } for i, k := range weakKeys { b := fixWeakKey(k.key) if b[7] != weakKeys[i].lastbyte { t.Errorf("key not fixed correctly %X - %X", b, weakKeys[i].lastbyte) } } } gokrb5-8.4.3/v8/crypto/rfc3961/nfold.go000066400000000000000000000052021427031340300173160ustar00rootroot00000000000000package rfc3961 // Implementation of the n-fold algorithm as defined in RFC 3961. /* Credits This golang implementation of nfold used the following project for help with implementation detail. Although their source is in java it was helpful as a reference implementation of the RFC. You can find the source code of their open source project along with license information below. We acknowledge and are grateful to these developers for their contributions to open source Project: Apache Directory (http://http://directory.apache.org/) https://svn.apache.org/repos/asf/directory/apacheds/tags/1.5.1/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/crypto/encryption/NFold.java License: http://www.apache.org/licenses/LICENSE-2.0 */ // Nfold expands the key to ensure it is not smaller than one cipher block. // Defined in RFC 3961. // // m input bytes that will be "stretched" to the least common multiple of n bits and the bit length of m. func Nfold(m []byte, n int) []byte { k := len(m) * 8 //Get the lowest common multiple of the two bit sizes lcm := lcm(n, k) relicate := lcm / k var sumBytes []byte for i := 0; i < relicate; i++ { rotation := 13 * i sumBytes = append(sumBytes, rotateRight(m, rotation)...) } nfold := make([]byte, n/8) sum := make([]byte, n/8) for i := 0; i < lcm/n; i++ { for j := 0; j < n/8; j++ { sum[j] = sumBytes[j+(i*len(sum))] } nfold = onesComplementAddition(nfold, sum) } return nfold } func onesComplementAddition(n1, n2 []byte) []byte { numBits := len(n1) * 8 out := make([]byte, numBits/8) carry := 0 for i := numBits - 1; i > -1; i-- { n1b := getBit(&n1, i) n2b := getBit(&n2, i) s := n1b + n2b + carry if s == 0 || s == 1 { setBit(&out, i, s) carry = 0 } else if s == 2 { carry = 1 } else if s == 3 { setBit(&out, i, 1) carry = 1 } } if carry == 1 { carryArray := make([]byte, len(n1)) carryArray[len(carryArray)-1] = 1 out = onesComplementAddition(out, carryArray) } return out } func rotateRight(b []byte, step int) []byte { out := make([]byte, len(b)) bitLen := len(b) * 8 for i := 0; i < bitLen; i++ { v := getBit(&b, i) setBit(&out, (i+step)%bitLen, v) } return out } func lcm(x, y int) int { return (x * y) / gcd(x, y) } func gcd(x, y int) int { for y != 0 { x, y = y, x%y } return x } func getBit(b *[]byte, p int) int { pByte := p / 8 pBit := uint(p % 8) vByte := (*b)[pByte] vInt := int(vByte >> (8 - (pBit + 1)) & 0x0001) return vInt } func setBit(b *[]byte, p, v int) { pByte := p / 8 pBit := uint(p % 8) oldByte := (*b)[pByte] var newByte byte newByte = byte(v<<(8-(pBit+1))) | oldByte (*b)[pByte] = newByte } gokrb5-8.4.3/v8/crypto/rfc3961/nfold_test.go000066400000000000000000000015071427031340300203610ustar00rootroot00000000000000package rfc3961 import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" ) func Test_nfold(t *testing.T) { t.Parallel() var tests = []struct { n int b []byte folded string }{ {64, []byte("012345"), "be072631276b1955"}, {56, []byte("password"), "78a07b6caf85fa"}, {64, []byte("Rough Consensus, and Running Code"), "bb6ed30870b7f0e0"}, {168, []byte("password"), "59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e"}, {192, []byte("MASSACHVSETTS INSTITVTE OF TECHNOLOGY"), "db3b0d8f0b061e603282b308a50841229ad798fab9540c1b"}, {168, []byte("Q"), "518a54a215a8452a518a54a215a8452a518a54a215"}, {168, []byte("ba"), "fb25d531ae8974499f52fd92ea9857c4ba24cf297e"}, } for _, test := range tests { assert.Equal(t, test.folded, hex.EncodeToString(Nfold(test.b, test.n)), "Folded not as expected") } } gokrb5-8.4.3/v8/crypto/rfc3962/000077500000000000000000000000001427031340300156675ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/rfc3962/encryption.go000066400000000000000000000062301427031340300204110ustar00rootroot00000000000000// Package rfc3962 provides encryption and checksum methods as specified in RFC 3962 package rfc3962 import ( "crypto/rand" "errors" "fmt" "github.com/jcmturner/aescts/v2" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 3962. func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } ivz := make([]byte, e.GetCypherBlockBitLength()/8) return aescts.Encrypt(key, ivz, data) } // EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 3962. // The encrypted data is concatenated with its integrity hash to create an encrypted message. func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } //confounder c := make([]byte, e.GetConfounderByteSize()) _, err := rand.Read(c) if err != nil { return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err) } plainBytes := append(c, message...) // Derive key for encryption from usage var k []byte if usage != 0 { k, err = e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err) } } // Encrypt the data iv, b, err := e.EncryptData(k, plainBytes) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } // Generate and append integrity hash ih, err := common.GetIntegrityHash(plainBytes, key, usage, e) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } b = append(b, ih...) return iv, b, nil } // DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 3962. func DecryptData(key, data []byte, e etype.EType) ([]byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } ivz := make([]byte, e.GetCypherBlockBitLength()/8) return aescts.Decrypt(key, ivz, data) } // DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 3962. // The integrity of the message is also verified. func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) { //Derive the key k, err := e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return nil, fmt.Errorf("error deriving key: %v", err) } // Strip off the checksum from the end b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8]) if err != nil { return nil, err } //Verify checksum if !e.VerifyIntegrity(key, ciphertext, b, usage) { return nil, errors.New("integrity verification failed") } //Remove the confounder bytes return b[e.GetConfounderByteSize():], nil } gokrb5-8.4.3/v8/crypto/rfc3962/keyDerivation.go000066400000000000000000000034471427031340300210430ustar00rootroot00000000000000package rfc3962 import ( "encoding/binary" "encoding/hex" "errors" "github.com/jcmturner/gofork/x/crypto/pbkdf2" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) const ( s2kParamsZero = 4294967296 ) // StringToKey returns a key derived from the string provided according to the definition in RFC 3961. func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) { i, err := S2KparamsToItertions(s2kparams) if err != nil { return nil, err } return StringToKeyIter(secret, salt, i, e) } // StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0 func StringToPBKDF2(secret, salt string, iterations int64, e etype.EType) []byte { return pbkdf2.Key64([]byte(secret), []byte(salt), iterations, int64(e.GetKeyByteSize()), e.GetHashFunc()) } // StringToKeyIter returns a key derived from the string provided according to the definition in RFC 3961. func StringToKeyIter(secret, salt string, iterations int64, e etype.EType) ([]byte, error) { tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e)) return e.DeriveKey(tkey, []byte("kerberos")) } // S2KparamsToItertions converts the string representation of iterations to an integer func S2KparamsToItertions(s2kparams string) (int64, error) { //The s2kparams string should be hex string representing 4 bytes //The 4 bytes represent a number in big endian order //If the value is zero then the number of iterations should be 4,294,967,296 (2^32) var i uint32 if len(s2kparams) != 8 { return int64(s2kParamsZero), errors.New("invalid s2kparams length") } b, err := hex.DecodeString(s2kparams) if err != nil { return int64(s2kParamsZero), errors.New("invalid s2kparams, cannot decode string to bytes") } i = binary.BigEndian.Uint32(b) return int64(i), nil } gokrb5-8.4.3/v8/crypto/rfc4757/000077500000000000000000000000001427031340300156725ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/rfc4757/checksum.go000066400000000000000000000015141427031340300200240ustar00rootroot00000000000000package rfc4757 import ( "bytes" "crypto/hmac" "crypto/md5" "io" ) // Checksum returns a hash of the data in accordance with RFC 4757 func Checksum(key []byte, usage uint32, data []byte) ([]byte, error) { // Create hashing key s := append([]byte(`signaturekey`), byte(0x00)) //includes zero octet at end mac := hmac.New(md5.New, key) mac.Write(s) Ksign := mac.Sum(nil) // Format data tb := UsageToMSMsgType(usage) p := append(tb, data...) h := md5.New() rb := bytes.NewReader(p) _, err := io.Copy(h, rb) if err != nil { return []byte{}, err } tmp := h.Sum(nil) // Generate HMAC mac = hmac.New(md5.New, Ksign) mac.Write(tmp) return mac.Sum(nil), nil } // HMAC returns a keyed MD5 checksum of the data func HMAC(key []byte, data []byte) []byte { mac := hmac.New(md5.New, key) mac.Write(data) return mac.Sum(nil) } gokrb5-8.4.3/v8/crypto/rfc4757/encryption.go000066400000000000000000000053671427031340300204260ustar00rootroot00000000000000// Package rfc4757 provides encryption and checksum methods as specified in RFC 4757 package rfc4757 import ( "crypto/hmac" "crypto/rand" "crypto/rc4" "errors" "fmt" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 4757. func EncryptData(key, data []byte, e etype.EType) ([]byte, error) { if len(key) != e.GetKeyByteSize() { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } rc4Cipher, err := rc4.NewCipher(key) if err != nil { return []byte{}, fmt.Errorf("error creating RC4 cipher: %v", err) } ed := make([]byte, len(data)) copy(ed, data) rc4Cipher.XORKeyStream(ed, ed) rc4Cipher.Reset() return ed, nil } // DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 4757. func DecryptData(key, data []byte, e etype.EType) ([]byte, error) { return EncryptData(key, data, e) } // EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 4757. // The encrypted data is concatenated with its RC4 header containing integrity checksum and confounder to create an encrypted message. func EncryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) { confounder := make([]byte, e.GetConfounderByteSize()) // size = 8 _, err := rand.Read(confounder) if err != nil { return []byte{}, fmt.Errorf("error generating confounder: %v", err) } k1 := key k2 := HMAC(k1, UsageToMSMsgType(usage)) toenc := append(confounder, data...) chksum := HMAC(k2, toenc) k3 := HMAC(k2, chksum) ed, err := EncryptData(k3, toenc, e) if err != nil { return []byte{}, fmt.Errorf("error encrypting data: %v", err) } msg := append(chksum, ed...) return msg, nil } // DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 4757. // The integrity of the message is also verified. func DecryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) { checksum := data[:e.GetHMACBitLength()/8] ct := data[e.GetHMACBitLength()/8:] _, k2, k3 := deriveKeys(key, checksum, usage, export) pt, err := DecryptData(k3, ct, e) if err != nil { return []byte{}, fmt.Errorf("error decrypting data: %v", err) } if !VerifyIntegrity(k2, pt, data, e) { return []byte{}, errors.New("integrity checksum incorrect") } return pt[e.GetConfounderByteSize():], nil } // VerifyIntegrity checks the integrity checksum of the data matches that calculated from the decrypted data. func VerifyIntegrity(key, pt, data []byte, e etype.EType) bool { chksum := HMAC(key, pt) return hmac.Equal(chksum, data[:e.GetHMACBitLength()/8]) } gokrb5-8.4.3/v8/crypto/rfc4757/keyDerivation.go000066400000000000000000000016131427031340300210370ustar00rootroot00000000000000package rfc4757 import ( "bytes" "encoding/hex" "errors" "fmt" "io" "golang.org/x/crypto/md4" ) // StringToKey returns a key derived from the string provided according to the definition in RFC 4757. func StringToKey(secret string) ([]byte, error) { b := make([]byte, len(secret)*2, len(secret)*2) for i, r := range secret { u := fmt.Sprintf("%04x", r) c, err := hex.DecodeString(u) if err != nil { return []byte{}, errors.New("character could not be encoded") } // Swap round the two bytes to make little endian as we put into byte slice b[2*i] = c[1] b[2*i+1] = c[0] } r := bytes.NewReader(b) h := md4.New() _, err := io.Copy(h, r) if err != nil { return []byte{}, err } return h.Sum(nil), nil } func deriveKeys(key, checksum []byte, usage uint32, export bool) (k1, k2, k3 []byte) { k1 = key k2 = HMAC(k1, UsageToMSMsgType(usage)) k3 = HMAC(k2, checksum) return } gokrb5-8.4.3/v8/crypto/rfc4757/keyDerivation_test.go000066400000000000000000000006511427031340300220770ustar00rootroot00000000000000package rfc4757 import ( "encoding/hex" "testing" "github.com/stretchr/testify/assert" ) const ( testPassword = "foo" testKey = "ac8e657f83df82beea5d43bdaf7800cc" ) func TestStringToKey(t *testing.T) { t.Parallel() kb, err := StringToKey(testPassword) if err != nil { t.Fatalf("Error deriving key from string: %v", err) } k := hex.EncodeToString(kb) assert.Equal(t, testKey, k, "Key not as expected") } gokrb5-8.4.3/v8/crypto/rfc4757/msgtype.go000066400000000000000000000007731427031340300177200ustar00rootroot00000000000000package rfc4757 import "encoding/binary" // UsageToMSMsgType converts Kerberos key usage numbers to Microsoft message type encoded as a little-endian four byte slice. func UsageToMSMsgType(usage uint32) []byte { // Translate usage numbers to the Microsoft T numbers switch usage { case 3: usage = 8 case 9: usage = 8 case 23: usage = 13 } // Now convert to bytes tb := make([]byte, 4) // We force an int32 input so we can't go over 4 bytes binary.PutUvarint(tb, uint64(usage)) return tb } gokrb5-8.4.3/v8/crypto/rfc8009/000077500000000000000000000000001427031340300156645ustar00rootroot00000000000000gokrb5-8.4.3/v8/crypto/rfc8009/encryption.go000066400000000000000000000105071427031340300204100ustar00rootroot00000000000000// Package rfc8009 provides encryption and checksum methods as specified in RFC 8009 package rfc8009 import ( "crypto/aes" "crypto/hmac" "crypto/rand" "errors" "fmt" "github.com/jcmturner/aescts/v2" "github.com/jcmturner/gokrb5/v8/crypto/common" "github.com/jcmturner/gokrb5/v8/crypto/etype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 8009. func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } if len(key) != kl { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key)) } ivz := make([]byte, aes.BlockSize) return aescts.Encrypt(key, ivz, data) } // EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 8009. // The encrypted data is concatenated with its integrity hash to create an encrypted message. func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } if len(key) != kl { return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key)) } if len(key) != e.GetKeyByteSize() { } //confounder c := make([]byte, e.GetConfounderByteSize()) _, err := rand.Read(c) if err != nil { return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err) } plainBytes := append(c, message...) // Derive key for encryption from usage var k []byte if usage != 0 { k, err = e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err) } } // Encrypt the data iv, b, err := e.EncryptData(k, plainBytes) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } ivz := make([]byte, e.GetConfounderByteSize()) ih, err := GetIntegityHash(ivz, b, key, usage, e) if err != nil { return iv, b, fmt.Errorf("error encrypting data: %v", err) } b = append(b, ih...) return iv, b, nil } // DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 8009. func DecryptData(key, data []byte, e etype.EType) ([]byte, error) { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } if len(key) != kl { return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key)) } ivz := make([]byte, aes.BlockSize) return aescts.Decrypt(key, ivz, data) } // DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 8009. // The integrity of the message is also verified. func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) { //Derive the key k, err := e.DeriveKey(key, common.GetUsageKe(usage)) if err != nil { return nil, fmt.Errorf("error deriving key: %v", err) } // Strip off the checksum from the end b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8]) if err != nil { return nil, err } //Verify checksum if !e.VerifyIntegrity(key, ciphertext, b, usage) { return nil, errors.New("integrity verification failed") } //Remove the confounder bytes return b[e.GetConfounderByteSize():], nil } // GetIntegityHash returns a keyed integrity hash of the bytes provided as defined in RFC 8009 func GetIntegityHash(iv, c, key []byte, usage uint32, e etype.EType) ([]byte, error) { // Generate and append integrity hash // Rather than calculating the hash over the confounder and plaintext // it is calculated over the iv concatenated with the AES cipher output. ib := append(iv, c...) return common.GetIntegrityHash(ib, key, usage, e) } // VerifyIntegrity verifies the integrity of cipertext bytes ct. func VerifyIntegrity(key, ct []byte, usage uint32, etype etype.EType) bool { h := make([]byte, etype.GetHMACBitLength()/8) copy(h, ct[len(ct)-etype.GetHMACBitLength()/8:]) ivz := make([]byte, etype.GetConfounderByteSize()) ib := append(ivz, ct[:len(ct)-(etype.GetHMACBitLength()/8)]...) expectedMAC, _ := common.GetIntegrityHash(ib, key, usage, etype) return hmac.Equal(h, expectedMAC) } gokrb5-8.4.3/v8/crypto/rfc8009/keyDerivation.go000066400000000000000000000101541427031340300210310ustar00rootroot00000000000000package rfc8009 import ( "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "github.com/jcmturner/gokrb5/v8/crypto/etype" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "golang.org/x/crypto/pbkdf2" ) const ( s2kParamsZero = 32768 ) // DeriveRandom for key derivation as defined in RFC 8009 func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) { h := e.GetHashFunc()() return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil } // DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods. // // https://tools.ietf.org/html/rfc8009#section-5 func DeriveKey(protocolKey, label []byte, e etype.EType) []byte { var context []byte var kl int // Key length is longer for aes256-cts-hmac-sha384-192 is it is a Ke or from StringToKey (where label is "kerberos") if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { Swtch: switch label[len(label)-1] { case 0x73: // 0x73 is "s" so label could be kerberos meaning StringToKey so now check if the label is "kerberos" kerblabel := []byte("kerberos") if len(label) != len(kerblabel) { break } for i, b := range label { if b != kerblabel[i] { kl = e.GetKeySeedBitLength() break Swtch } } if kl == 0 { // This is StringToKey kl = 256 } case 0xAA: // This is a Ke kl = 256 } } if kl == 0 { kl = e.GetKeySeedBitLength() } return e.RandomToKey(KDF_HMAC_SHA2(protocolKey, label, context, kl, e)) } // RandomToKey returns a key from the bytes provided according to the definition in RFC 8009. func RandomToKey(b []byte) []byte { return b } // StringToKey returns a key derived from the string provided according to the definition in RFC 8009. func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) { i, err := S2KparamsToItertions(s2kparams) if err != nil { return nil, err } return StringToKeyIter(secret, salt, i, e) } // StringToKeyIter returns a key derived from the string provided according to the definition in RFC 8009. func StringToKeyIter(secret, salt string, iterations int, e etype.EType) ([]byte, error) { tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e)) return e.DeriveKey(tkey, []byte("kerberos")) } // StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0 func StringToPBKDF2(secret, salt string, iterations int, e etype.EType) []byte { kl := e.GetKeyByteSize() if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 { kl = 32 } return pbkdf2.Key([]byte(secret), []byte(salt), iterations, kl, e.GetHashFunc()) } // KDF_HMAC_SHA2 key derivation: https://tools.ietf.org/html/rfc8009#section-3 func KDF_HMAC_SHA2(protocolKey, label, context []byte, kl int, e etype.EType) []byte { //k: Length in bits of the key to be outputted, expressed in big-endian binary representation in 4 bytes. k := make([]byte, 4, 4) binary.BigEndian.PutUint32(k, uint32(kl)) c := make([]byte, 4, 4) binary.BigEndian.PutUint32(c, uint32(1)) c = append(c, label...) c = append(c, byte(0)) if len(context) > 0 { c = append(c, context...) } c = append(c, k...) mac := hmac.New(e.GetHashFunc(), protocolKey) mac.Write(c) return mac.Sum(nil)[:(kl / 8)] } // GetSaltP returns the salt value based on the etype name: https://tools.ietf.org/html/rfc8009#section-4 func GetSaltP(salt, ename string) string { b := []byte(ename) b = append(b, byte(0)) b = append(b, []byte(salt)...) return string(b) } // S2KparamsToItertions converts the string representation of iterations to an integer for RFC 8009. func S2KparamsToItertions(s2kparams string) (int, error) { var i uint32 if len(s2kparams) != 8 { return s2kParamsZero, errors.New("Invalid s2kparams length") } b, err := hex.DecodeString(s2kparams) if err != nil { return s2kParamsZero, errors.New("Invalid s2kparams, cannot decode string to bytes") } i = binary.BigEndian.Uint32(b) //buf := bytes.NewBuffer(b) //err = binary.Read(buf, binary.BigEndian, &i) if err != nil { return s2kParamsZero, errors.New("Invalid s2kparams, cannot convert to big endian int32") } return int(i), nil } gokrb5-8.4.3/v8/examples/000077500000000000000000000000001427031340300150675ustar00rootroot00000000000000gokrb5-8.4.3/v8/examples/example-AD.go000066400000000000000000000125161427031340300173400ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "errors" "fmt" "io" "log" "net/http" "net/http/httptest" "os" "github.com/gorilla/sessions" "github.com/jcmturner/goidentity/v6" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/test/testdata" ) func main() { s := httpServer() defer s.Close() fmt.Printf("Listening on %s\n", s.URL) l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_USER_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) cl := client.NewWithKeytab("testuser1", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true), client.Logger(l)) httpRequest(s.URL, cl) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_USER_GOKRB5) kt = keytab.New() kt.Unmarshal(b) c, _ = config.NewFromString(testdata.KRB5_CONF) cl = client.NewWithKeytab("testuser2", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true), client.Logger(l)) httpRequest(s.URL, cl) } func httpRequest(url string, cl *client.Client) { l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) err := cl.Login() if err != nil { l.Fatalf("Error on AS_REQ: %v\n", err) } spnegoCl := spnego.NewClient(cl, nil, "HTTP/host.res.gokrb5") // Make the request for the first time with no session r, _ := http.NewRequest("GET", url, nil) httpResp, err := spnegoCl.Do(r) if err != nil { l.Fatalf("error making request: %v", err) } fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode) content, _ := io.ReadAll(httpResp.Body) fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content) // Make the request again which should use the session httpResp, err = spnegoCl.Do(r) if err != nil { l.Fatalf("error making request: %v", err) } fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode) content, _ = io.ReadAll(httpResp.Body) fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content) } func httpServer() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile) b, _ := hex.DecodeString(testdata.KEYTAB_SYSHTTP_RES_GOKRB5) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l), service.KeytabPrincipal("sysHTTP"), service.SessionManager(NewSessionMgr("gokrb5")))) return s } func testAppHandler(w http.ResponseWriter, r *http.Request) { creds := goidentity.FromHTTPRequestContext(r) fmt.Fprint(w, "\n

      GOKRB5 Handler

      \n") if creds != nil && creds.Authenticated() { fmt.Fprintf(w, "
      • Authenticed user: %s
      • \n", creds.UserName()) fmt.Fprintf(w, "
      • User's realm: %s
      • \n", creds.Domain()) fmt.Fprint(w, "
      • Authz Attributes (Group Memberships):
        • \n") for _, s := range creds.AuthzAttributes() { fmt.Fprintf(w, "
        • %v
        • \n", s) } fmt.Fprint(w, "
        \n") if ADCredsJSON, ok := creds.Attributes()[credentials.AttributeKeyADCredentials]; ok { //ADCreds := new(credentials.ADCredentials) ADCreds := ADCredsJSON.(credentials.ADCredentials) //err := json.Unmarshal(aj, ADCreds) //if err == nil { // Now access the fields of the ADCredentials struct. For example: fmt.Fprintf(w, "
      • EffectiveName: %v
      • \n", ADCreds.EffectiveName) fmt.Fprintf(w, "
      • FullName: %v
      • \n", ADCreds.FullName) fmt.Fprintf(w, "
      • UserID: %v
      • \n", ADCreds.UserID) fmt.Fprintf(w, "
      • PrimaryGroupID: %v
      • \n", ADCreds.PrimaryGroupID) fmt.Fprintf(w, "
      • Group SIDs: %v
      • \n", ADCreds.GroupMembershipSIDs) fmt.Fprintf(w, "
      • LogOnTime: %v
      • \n", ADCreds.LogOnTime) fmt.Fprintf(w, "
      • LogOffTime: %v
      • \n", ADCreds.LogOffTime) fmt.Fprintf(w, "
      • PasswordLastSet: %v
      • \n", ADCreds.PasswordLastSet) fmt.Fprintf(w, "
      • LogonServer: %v
      • \n", ADCreds.LogonServer) fmt.Fprintf(w, "
      • LogonDomainName: %v
      • \n", ADCreds.LogonDomainName) fmt.Fprintf(w, "
      • LogonDomainID: %v
      • \n", ADCreds.LogonDomainID) //} } fmt.Fprint(w, "
      ") } else { w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Authentication failed") } fmt.Fprint(w, "") return } type SessionMgr struct { skey []byte store sessions.Store cookieName string } func NewSessionMgr(cookieName string) SessionMgr { skey := []byte("thisistestsecret") // Best practice is to load this key from a secure location. return SessionMgr{ skey: skey, store: sessions.NewCookieStore(skey), cookieName: cookieName, } } func (smgr SessionMgr) Get(r *http.Request, k string) ([]byte, error) { s, err := smgr.store.Get(r, smgr.cookieName) if err != nil { return nil, err } if s == nil { return nil, errors.New("nil session") } b, ok := s.Values[k].([]byte) if !ok { return nil, fmt.Errorf("could not get bytes held in session at %s", k) } return b, nil } func (smgr SessionMgr) New(w http.ResponseWriter, r *http.Request, k string, v []byte) error { s, err := smgr.store.New(r, smgr.cookieName) if err != nil { return fmt.Errorf("could not get new session from session manager: %v", err) } s.Values[k] = v return s.Save(r, w) } gokrb5-8.4.3/v8/examples/example.go000066400000000000000000000050371427031340300170560ustar00rootroot00000000000000// Package examples provides simple examples of gokrb5 use. //go:build examples // +build examples package main import ( "encoding/hex" "fmt" "io" "log" "net/http" "net/http/httptest" "os" "github.com/jcmturner/goidentity/v6" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/test/testdata" ) func main() { s := httpServer() defer s.Close() b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) c.LibDefaults.NoAddresses = true cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) httpRequest(s.URL, cl) b, _ = hex.DecodeString(testdata.KEYTAB_TESTUSER2_TEST_GOKRB5) kt = keytab.New() kt.Unmarshal(b) c, _ = config.NewFromString(testdata.KRB5_CONF) c.LibDefaults.NoAddresses = true cl = client.NewWithKeytab("testuser2", "TEST.GOKRB5", kt, c) httpRequest(s.URL, cl) } func httpRequest(url string, cl *client.Client) { l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) err := cl.Login() if err != nil { l.Printf("Error on AS_REQ: %v\n", err) } r, _ := http.NewRequest("GET", url, nil) err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { l.Printf("Error setting client SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { l.Printf("Request error: %v\n", err) } fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode) content, _ := io.ReadAll(httpResp.Body) fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content) } func httpServer() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile) b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l))) return s } func testAppHandler(w http.ResponseWriter, r *http.Request) { creds := goidentity.FromHTTPRequestContext(r) fmt.Fprint(w, "\n

      TEST.GOKRB5 Handler

      \n") if creds != nil && creds.Authenticated() { fmt.Fprintf(w, "
      • Authenticed user: %s
      • \n", creds.UserName()) fmt.Fprintf(w, "
      • User's realm: %s
      \n", creds.Domain()) } else { w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Authentication failed") } fmt.Fprint(w, "") return } gokrb5-8.4.3/v8/examples/httpClient.go000066400000000000000000000041011427031340300175300ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "fmt" "io" "log" "net/http" "os" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/test/testdata" ) const ( port = ":9080" kRB5CONF = `[libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 127.0.0.1:88 admin_server = 127.0.0.1:749 default_domain = test.gokrb5 } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 ` ) func main() { l := log.New(os.Stderr, "GOKRB5 Client: ", log.LstdFlags) //defer profile.Start(profile.TraceProfile).Stop() // Load the keytab kb, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER2_TEST_GOKRB5) kt := keytab.New() err := kt.Unmarshal(kb) if err != nil { l.Fatalf("could not load client keytab: %v", err) } // Load the client krb5 config conf, err := config.NewFromString(kRB5CONF) if err != nil { l.Fatalf("could not load krb5.conf: %v", err) } addr := os.Getenv("TEST_KDC_ADDR") if addr != "" { conf.Realms[0].KDC = []string{addr + ":88"} } // Create the client with the keytab cl := client.NewWithKeytab("testuser2", "TEST.GOKRB5", kt, conf, client.Logger(l), client.DisablePAFXFAST(true)) // Log in the client err = cl.Login() if err != nil { l.Fatalf("could not login client: %v", err) } // Form the request url := "http://localhost" + port r, err := http.NewRequest("GET", url, nil) if err != nil { l.Fatalf("could create request: %v", err) } spnegoCl := spnego.NewClient(cl, nil, "HTTP/host.test.gokrb5") // Make the request resp, err := spnegoCl.Do(r) if err != nil { l.Fatalf("error making request: %v", err) } b, err := io.ReadAll(resp.Body) if err != nil { l.Fatalf("error reading response body: %v", err) } fmt.Println(string(b)) } gokrb5-8.4.3/v8/examples/httpServer.go000066400000000000000000000047071427031340300175740ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "errors" "fmt" "log" "net/http" "os" "github.com/gorilla/sessions" "github.com/jcmturner/goidentity/v6" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/test/testdata" ) const ( port = ":9080" ) func main() { //defer profile.Start(profile.TraceProfile).Stop() // Create logger l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile) // Load the service's keytab b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) // Create the application's specific handler th := http.HandlerFunc(testAppHandler) // Set up handler mappings wrapping in the SPNEGOKRB5Authenticate handler wrapper mux := http.NewServeMux() mux.Handle("/", spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l), service.SessionManager(NewSessionMgr("gokrb5")))) // Start up the web server log.Fatal(http.ListenAndServe(port, mux)) } // Simple application specific handler func testAppHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) creds := goidentity.FromHTTPRequestContext(r) fmt.Fprintf(w, `

      GOKRB5 Handler

      • Authenticed user: %s
      • User's realm: %s
      • Authn time: %v
      • Session ID: %s
        • `, creds.UserName(), creds.Domain(), creds.AuthTime(), creds.SessionID(), ) return } type SessionMgr struct { skey []byte store sessions.Store cookieName string } func NewSessionMgr(cookieName string) SessionMgr { skey := []byte("thisistestsecret") // Best practice is to load this key from a secure location. return SessionMgr{ skey: skey, store: sessions.NewCookieStore(skey), cookieName: cookieName, } } func (smgr SessionMgr) Get(r *http.Request, k string) ([]byte, error) { s, err := smgr.store.Get(r, smgr.cookieName) if err != nil { return nil, err } if s == nil { return nil, errors.New("nil session") } b, ok := s.Values[k].([]byte) if !ok { return nil, fmt.Errorf("could not get bytes held in session at %s", k) } return b, nil } func (smgr SessionMgr) New(w http.ResponseWriter, r *http.Request, k string, v []byte) error { s, err := smgr.store.New(r, smgr.cookieName) if err != nil { return fmt.Errorf("could not get new session from session manager: %v", err) } s.Values[k] = v return s.Save(r, w) } gokrb5-8.4.3/v8/examples/longRunningClient.go000066400000000000000000000033211427031340300210540ustar00rootroot00000000000000//go:build examples // +build examples package main import ( "encoding/hex" "log" "os" "time" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test/testdata" ) const ( kRB5CONF = `[libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 [realms] TEST.GOKRB5 = { kdc = 10.80.88.88:88 admin_server = 10.80.88.88:749 default_domain = test.gokrb5 } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 ` ) func main() { l := log.New(os.Stderr, "GOKRB5 Client: ", log.LstdFlags) //defer profile.Start(profile.TraceProfile).Stop() // Load the keytab kb, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER2_TEST_GOKRB5) kt := keytab.New() err := kt.Unmarshal(kb) if err != nil { l.Fatalf("could not load client keytab: %v", err) } // Load the client krb5 config conf, err := config.NewFromString(kRB5CONF) if err != nil { l.Fatalf("could not load krb5.conf: %v", err) } addr := os.Getenv("TEST_KDC_ADDR") if addr != "" { conf.Realms[0].KDC = []string{addr + ":88"} } // Create the client with the keytab cl := client.NewWithKeytab("testuser2", "TEST.GOKRB5", kt, conf, client.Logger(l), client.DisablePAFXFAST(true)) // Log in the client err = cl.Login() if err != nil { l.Fatalf("could not login client: %v", err) } for { _, _, err := cl.GetServiceTicket("HTTP/host.test.gokrb5") if err != nil { l.Printf("failed to get service ticket: %v\n", err) } time.Sleep(time.Minute * 5) } } gokrb5-8.4.3/v8/go.mod000066400000000000000000000007451427031340300143650ustar00rootroot00000000000000module github.com/jcmturner/gokrb5/v8 go 1.16 require ( github.com/gorilla/sessions v1.2.1 github.com/hashicorp/go-uuid v1.0.3 github.com/jcmturner/aescts/v2 v2.0.0 github.com/jcmturner/dnsutils/v2 v2.0.0 github.com/jcmturner/gofork v1.7.6 github.com/jcmturner/goidentity/v6 v6.0.1 github.com/jcmturner/rpc/v2 v2.0.3 github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/net v0.0.0-20220725212005-46097bf591d3 // indirect ) gokrb5-8.4.3/v8/go.sum000066400000000000000000000137341427031340300144140ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg= golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gokrb5-8.4.3/v8/gokrb5.go000066400000000000000000000010621427031340300147700ustar00rootroot00000000000000/* Package gokrb5 provides a Kerberos 5 implementation for Go. This is a pure Go implementation and does not have dependencies on native libraries. Feature include: Server Side HTTP handler wrapper implements SPNEGO Kerberos authentication. HTTP handler wrapper decodes Microsoft AD PAC authorization data. Client Side Client that can authenticate to an SPNEGO Kerberos authenticated web service. Ability to change client's password. General Kerberos libraries for custom integration. Parsing Keytab files. Parsing krb5.conf files. */ package gokrb5 gokrb5-8.4.3/v8/gssapi/000077500000000000000000000000001427031340300145375ustar00rootroot00000000000000gokrb5-8.4.3/v8/gssapi/MICToken.go000066400000000000000000000135731427031340300165100ustar00rootroot00000000000000package gssapi import ( "bytes" "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" ) // RFC 4121, section 4.2.6.1 const ( // MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor. When not set, it indicates the sender is the context initiator MICTokenFlagSentByAcceptor = 1 << iota // MICTokenFlagSealed - this flag indicates confidentiality is provided for. It SHALL NOT be set in MIC tokens MICTokenFlagSealed // MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message MICTokenFlagAcceptorSubkey ) const ( micHdrLen = 16 // Length of the MIC Token's header ) // MICToken represents a GSS API MIC token, as defined in RFC 4121. // It contains the header fields, the payload (this is not transmitted) and // the checksum, and provides the logic for converting to/from bytes plus // computing and verifying checksums type MICToken struct { // const GSS Token ID: 0x0404 Flags byte // contains three flags: acceptor, sealed, acceptor subkey // const Filler: 0xFF 0xFF 0xFF 0xFF 0xFF SndSeqNum uint64 // sender's sequence number. big-endian Payload []byte // your data! :) Checksum []byte // checksum of { payload | header } } // Return the 2 bytes identifying a GSS API MIC token func getGSSMICTokenID() *[2]byte { return &[2]byte{0x04, 0x04} } // Return the filler bytes used in header func fillerBytes() *[5]byte { return &[5]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF} } // Marshal the MICToken into a byte slice. // The payload should have been set and the checksum computed, otherwise an error is returned. func (mt *MICToken) Marshal() ([]byte, error) { if mt.Checksum == nil { return nil, errors.New("checksum has not been set") } bytes := make([]byte, micHdrLen+len(mt.Checksum)) copy(bytes[0:micHdrLen], mt.getMICChecksumHeader()[:]) copy(bytes[micHdrLen:], mt.Checksum) return bytes, nil } // SetChecksum uses the passed encryption key and key usage to compute the checksum over the payload and // the header, and sets the Checksum field of this MICToken. // If the payload has not been set or the checksum has already been set, an error is returned. func (mt *MICToken) SetChecksum(key types.EncryptionKey, keyUsage uint32) error { if mt.Checksum != nil { return errors.New("checksum has already been computed") } checksum, err := mt.checksum(key, keyUsage) if err != nil { return err } mt.Checksum = checksum return nil } // Compute and return the checksum of this token, computed using the passed key and key usage. // Note: This will NOT update the struct's Checksum field. func (mt *MICToken) checksum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) { if mt.Payload == nil { return nil, errors.New("cannot compute checksum with uninitialized payload") } d := make([]byte, micHdrLen+len(mt.Payload)) copy(d[0:], mt.Payload) copy(d[len(mt.Payload):], mt.getMICChecksumHeader()) encType, err := crypto.GetEtype(key.KeyType) if err != nil { return nil, err } return encType.GetChecksumHash(key.KeyValue, d, keyUsage) } // Build a header suitable for a checksum computation func (mt *MICToken) getMICChecksumHeader() []byte { header := make([]byte, micHdrLen) copy(header[0:2], getGSSMICTokenID()[:]) header[2] = mt.Flags copy(header[3:8], fillerBytes()[:]) binary.BigEndian.PutUint64(header[8:16], mt.SndSeqNum) return header } // Verify computes the token's checksum with the provided key and usage, // and compares it to the checksum present in the token. // In case of any failure, (false, err) is returned, with err an explanatory error. func (mt *MICToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) { computed, err := mt.checksum(key, keyUsage) if err != nil { return false, err } if !hmac.Equal(computed, mt.Checksum) { return false, fmt.Errorf( "checksum mismatch. Computed: %s, Contained in token: %s", hex.EncodeToString(computed), hex.EncodeToString(mt.Checksum)) } return true, nil } // Unmarshal bytes into the corresponding MICToken. // If expectFromAcceptor is true we expect the token to have been emitted by the gss acceptor, // and will check the according flag, returning an error if the token does not match the expectation. func (mt *MICToken) Unmarshal(b []byte, expectFromAcceptor bool) error { if len(b) < micHdrLen { return errors.New("bytes shorter than header length") } if !bytes.Equal(getGSSMICTokenID()[:], b[0:2]) { return fmt.Errorf("wrong Token ID, Expected %s, was %s", hex.EncodeToString(getGSSMICTokenID()[:]), hex.EncodeToString(b[0:2])) } flags := b[2] isFromAcceptor := flags&MICTokenFlagSentByAcceptor != 0 if isFromAcceptor && !expectFromAcceptor { return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor") } if !isFromAcceptor && expectFromAcceptor { return errors.New("unexpected acceptor flag is not set: expecting a token from the acceptor, not in the initiator") } if !bytes.Equal(b[3:8], fillerBytes()[:]) { return fmt.Errorf("unexpected filler bytes: expecting %s, was %s", hex.EncodeToString(fillerBytes()[:]), hex.EncodeToString(b[3:8])) } mt.Flags = flags mt.SndSeqNum = binary.BigEndian.Uint64(b[8:16]) mt.Checksum = b[micHdrLen:] return nil } // NewInitiatorMICToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum. // Other flags are set to 0. // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier. // This is currently not supported. func NewInitiatorMICToken(payload []byte, key types.EncryptionKey) (*MICToken, error) { token := MICToken{ Flags: 0x00, SndSeqNum: 0, Payload: payload, } if err := token.SetChecksum(key, keyusage.GSSAPI_INITIATOR_SIGN); err != nil { return nil, err } return &token, nil } gokrb5-8.4.3/v8/gssapi/MICToken_test.go000066400000000000000000000140231427031340300175360ustar00rootroot00000000000000package gssapi import ( "encoding/binary" "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) const ( testMICPayload = "deadbeef" // What a kerberized server might send testMICChallengeFromAcceptor = "040401ffffffffff00000000575e85d6c34d12ba3e5b1b1310cd9cb3" // What an initiator client could reply testMICChallengeReplyFromInitiator = "040400ffffffffff00000000000000009649ca09d2f1bc51ff6e5ca3" acceptorSign = keyusage.GSSAPI_ACCEPTOR_SIGN initiatorSign = keyusage.GSSAPI_INITIATOR_SIGN ) func getMICChallengeReference() *MICToken { challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) return &MICToken{ Flags: MICTokenFlagSentByAcceptor, SndSeqNum: binary.BigEndian.Uint64(challenge[8:16]), Payload: nil, Checksum: challenge[16:], } } func getMICChallengeReferenceNoChksum() *MICToken { c := getMICChallengeReference() c.Checksum = nil return c } func getMICResponseReference() *MICToken { response, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) return &MICToken{ Flags: 0x00, SndSeqNum: 0, Payload: nil, Checksum: response[16:], } } func getMICResponseReferenceNoChkSum() *MICToken { r := getMICResponseReference() r.Checksum = nil return r } func TestUnmarshal_MICChallenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken err := mt.Unmarshal(challenge, true) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getMICChallengeReference(), &mt, "Token not decoded as expected.") } func TestUnmarshalFailure_MICChallenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken err := mt.Unmarshal(challenge, false) assert.NotNil(t, err, "Expected error did not occur: a message from the acceptor cannot be expected to be sent from the initiator.") assert.Nil(t, mt.Payload, "Token fields should not have been initialised") assert.Nil(t, mt.Checksum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), mt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint64(0), mt.SndSeqNum, "Token fields should not have been initialised") } func TestUnmarshal_MICChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) var mt MICToken err := mt.Unmarshal(response, false) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getMICResponseReference(), &mt, "Token not decoded as expected.") } func TestUnmarshalFailure_MICChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) var mt MICToken err := mt.Unmarshal(response, true) assert.NotNil(t, err, "Expected error did not occur: a message from the initiator cannot be expected to be sent from the acceptor.") assert.Nil(t, mt.Payload, "Token fields should not have been initialised") assert.Nil(t, mt.Checksum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), mt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint64(0), mt.SndSeqNum, "Token fields should not have been initialised") } func TestMICChallengeChecksumVerification(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken mt.Unmarshal(challenge, true) mt.Payload, _ = hex.DecodeString(testMICPayload) challengeOk, cErr := mt.Verify(getSessionKey(), acceptorSign) assert.Nil(t, cErr, "Error occurred during checksum verification.") assert.True(t, challengeOk, "Checksum verification failed.") } func TestMICResponseChecksumVerification(t *testing.T) { t.Parallel() reply, _ := hex.DecodeString(testMICChallengeReplyFromInitiator) var mt MICToken mt.Unmarshal(reply, false) mt.Payload, _ = hex.DecodeString(testMICPayload) replyOk, rErr := mt.Verify(getSessionKey(), initiatorSign) assert.Nil(t, rErr, "Error occurred during checksum verification.") assert.True(t, replyOk, "Checksum verification failed.") } func TestMICChecksumVerificationFailure(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testMICChallengeFromAcceptor) var mt MICToken mt.Unmarshal(challenge, true) // Test a failure with the correct key but wrong keyusage: challengeOk, cErr := mt.Verify(getSessionKey(), initiatorSign) assert.NotNil(t, cErr, "Expected error did not occur.") assert.False(t, challengeOk, "Checksum verification succeeded when it should have failed.") wrongKeyVal, _ := hex.DecodeString("14f9bde6b50ec508201a97f74c4effff") badKey := types.EncryptionKey{ KeyType: sessionKeyType, KeyValue: wrongKeyVal, } // Test a failure with the wrong key but correct keyusage: wrongKeyOk, wkErr := mt.Verify(badKey, acceptorSign) assert.NotNil(t, wkErr, "Expected error did not occur.") assert.False(t, wrongKeyOk, "Checksum verification succeeded when it should have failed.") } func TestMarshal_MICChallenge(t *testing.T) { t.Parallel() bytes, _ := getMICChallengeReference().Marshal() assert.Equal(t, testMICChallengeFromAcceptor, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_MICChallengeReply(t *testing.T) { t.Parallel() bytes, _ := getMICResponseReference().Marshal() assert.Equal(t, testMICChallengeReplyFromInitiator, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_MICFailures(t *testing.T) { t.Parallel() noChkSum := getMICResponseReferenceNoChkSum() chkBytes, chkErr := noChkSum.Marshal() assert.Nil(t, chkBytes, "No bytes should be returned.") assert.NotNil(t, chkErr, "Expected an error as no checksum was set") } func TestNewInitiatorMICTokenSignatureAndMarshalling(t *testing.T) { t.Parallel() bytes, _ := hex.DecodeString(testMICPayload) token, tErr := NewInitiatorMICToken(bytes, getSessionKey()) token.Payload = nil assert.Nil(t, tErr, "Unexpected error.") assert.Equal(t, getMICResponseReference(), token, "Token failed to be marshalled to the expected bytes.") } gokrb5-8.4.3/v8/gssapi/README.md000066400000000000000000000024461427031340300160240ustar00rootroot00000000000000# Notes on GSS-API Negotiation Mechanism https://tools.ietf.org/html/rfc4178 Client sends an initial negotiation message to the server which specifies the list of mechanisms the client can support in order of decreasing preference. This message is generated with the ``NewNegTokenInitKrb5`` method. The message generated by this function specifies only a kerberos v5 mechanism is supported. The RFC states that this message can optionally contain the initial mechanism token for the preferred mechanism (KRB5 in this case) of the client. The ``NewNegTokenInitKrb5`` includes this in the message. The server side responds to this message with a one of four messages: | Message Type/State | Description | |--------------------|-------------| | accept-completed | indicates that the initiator-selected mechanism was acceptable to the target, and that the security mechanism token embedded in the first negotiation message was sufficient to complete the authentication | | accept-incomplete | At least one more message is needed from the client to establish security context. | | reject | Negotiation is being terminated. | | request-mic | (this state can only be present in the first reply message from the target) indicates that the MIC token exchange is REQUIRED if per-message integrity services are available |gokrb5-8.4.3/v8/gssapi/contextFlags.go000066400000000000000000000011171427031340300175270ustar00rootroot00000000000000package gssapi import "github.com/jcmturner/gofork/encoding/asn1" // GSS-API context flags assigned numbers. const ( ContextFlagDeleg = 1 ContextFlagMutual = 2 ContextFlagReplay = 4 ContextFlagSequence = 8 ContextFlagConf = 16 ContextFlagInteg = 32 ContextFlagAnon = 64 ) // ContextFlags flags for GSSAPI // DEPRECATED - do not use type ContextFlags asn1.BitString // NewContextFlags creates a new ContextFlags instance // DEPRECATED - do not use func NewContextFlags() ContextFlags { var c ContextFlags c.BitLength = 32 c.Bytes = make([]byte, 4) return c } gokrb5-8.4.3/v8/gssapi/gssapi.go000066400000000000000000000162051427031340300163600ustar00rootroot00000000000000// Package gssapi implements Generic Security Services Application Program Interface required for SPNEGO kerberos authentication. package gssapi import ( "context" "fmt" "github.com/jcmturner/gofork/encoding/asn1" ) // GSS-API OID names const ( // GSS-API OID names OIDKRB5 OIDName = "KRB5" // MechType OID for Kerberos 5 OIDMSLegacyKRB5 OIDName = "MSLegacyKRB5" // MechType OID for Kerberos 5 OIDSPNEGO OIDName = "SPNEGO" OIDGSSIAKerb OIDName = "GSSIAKerb" // Indicates the client cannot get a service ticket and asks the server to serve as an intermediate to the target KDC. http://k5wiki.kerberos.org/wiki/Projects/IAKERB#IAKERB_mech ) // GSS-API status values const ( StatusBadBindings = 1 << iota StatusBadMech StatusBadName StatusBadNameType StatusBadStatus StatusBadSig StatusBadMIC StatusContextExpired StatusCredentialsExpired StatusDefectiveCredential StatusDefectiveToken StatusFailure StatusNoContext StatusNoCred StatusBadQOP StatusUnauthorized StatusUnavailable StatusDuplicateElement StatusNameNotMN StatusComplete StatusContinueNeeded StatusDuplicateToken StatusOldToken StatusUnseqToken StatusGapToken ) // ContextToken is an interface for a GSS-API context token. type ContextToken interface { Marshal() ([]byte, error) Unmarshal(b []byte) error Verify() (bool, Status) Context() context.Context } /* CREDENTIAL MANAGEMENT GSS_Acquire_cred acquire credentials for use GSS_Release_cred release credentials after use GSS_Inquire_cred display information about credentials GSS_Add_cred construct credentials incrementally GSS_Inquire_cred_by_mech display per-mechanism credential information CONTEXT-LEVEL CALLS GSS_Init_sec_context initiate outbound security context GSS_Accept_sec_context accept inbound security context GSS_Delete_sec_context flush context when no longer needed GSS_Process_context_token process received control token on context GSS_Context_time indicate validity time remaining on context GSS_Inquire_context display information about context GSS_Wrap_size_limit determine GSS_Wrap token size limit GSS_Export_sec_context transfer context to other process GSS_Import_sec_context import transferred context PER-MESSAGE CALLS GSS_GetMIC apply integrity check, receive as token separate from message GSS_VerifyMIC validate integrity check token along with message GSS_Wrap sign, optionally encrypt, encapsulate GSS_Unwrap decapsulate, decrypt if needed, validate integrity check SUPPORT CALLS GSS_Display_status translate status codes to printable form GSS_Indicate_mechs indicate mech_types supported on local system GSS_Compare_name compare two names for equality GSS_Display_name translate name to printable form GSS_Import_name convert printable name to normalized form GSS_Release_name free storage of normalized-form name GSS_Release_buffer free storage of general GSS-allocated object GSS_Release_OID_set free storage of OID set object GSS_Create_empty_OID_set create empty OID set GSS_Add_OID_set_member add member to OID set GSS_Test_OID_set_member test if OID is member of OID set GSS_Inquire_names_for_mech indicate name types supported by mechanism GSS_Inquire_mechs_for_name indicates mechanisms supporting name type GSS_Canonicalize_name translate name to per-mechanism form GSS_Export_name externalize per-mechanism name GSS_Duplicate_name duplicate name object */ // Mechanism is the GSS-API interface for authentication mechanisms. type Mechanism interface { OID() asn1.ObjectIdentifier AcquireCred() error // acquire credentials for use (eg. AS exchange for KRB5) InitSecContext() (ContextToken, error) // initiate outbound security context (eg TGS exchange builds AP_REQ to go into ContextToken to send to service) AcceptSecContext(ct ContextToken) (bool, context.Context, Status) // service verifies the token server side to establish a context MIC() MICToken // apply integrity check, receive as token separate from message VerifyMIC(mt MICToken) (bool, error) // validate integrity check token along with message Wrap(msg []byte) WrapToken // sign, optionally encrypt, encapsulate Unwrap(wt WrapToken) []byte // decapsulate, decrypt if needed, validate integrity check } // OIDName is the type for defined GSS-API OIDs. type OIDName string // OID returns the OID for the provided OID name. func (o OIDName) OID() asn1.ObjectIdentifier { switch o { case OIDSPNEGO: return asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2} case OIDKRB5: return asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2} case OIDMSLegacyKRB5: return asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2} case OIDGSSIAKerb: return asn1.ObjectIdentifier{1, 3, 6, 1, 5, 2, 5} } return asn1.ObjectIdentifier{} } // Status is the GSS-API status and implements the error interface. type Status struct { Code int Message string } // Error returns the Status description. func (s Status) Error() string { var str string switch s.Code { case StatusBadBindings: str = "channel binding mismatch" case StatusBadMech: str = "unsupported mechanism requested" case StatusBadName: str = "invalid name provided" case StatusBadNameType: str = "name of unsupported type provided" case StatusBadStatus: str = "invalid input status selector" case StatusBadSig: str = "token had invalid integrity check" case StatusBadMIC: str = "preferred alias for GSS_S_BAD_SIG" case StatusContextExpired: str = "specified security context expired" case StatusCredentialsExpired: str = "expired credentials detected" case StatusDefectiveCredential: str = "defective credential detected" case StatusDefectiveToken: str = "defective token detected" case StatusFailure: str = "failure, unspecified at GSS-API level" case StatusNoContext: str = "no valid security context specified" case StatusNoCred: str = "no valid credentials provided" case StatusBadQOP: str = "unsupported QOP valu" case StatusUnauthorized: str = "operation unauthorized" case StatusUnavailable: str = "operation unavailable" case StatusDuplicateElement: str = "duplicate credential element requested" case StatusNameNotMN: str = "name contains multi-mechanism elements" case StatusComplete: str = "normal completion" case StatusContinueNeeded: str = "continuation call to routine required" case StatusDuplicateToken: str = "duplicate per-message token detected" case StatusOldToken: str = "timed-out per-message token detected" case StatusUnseqToken: str = "reordered (early) per-message token detected" case StatusGapToken: str = "skipped predecessor token(s) detected" default: str = "unknown GSS-API error status" } if s.Message != "" { return fmt.Sprintf("%s: %s", str, s.Message) } return str } gokrb5-8.4.3/v8/gssapi/gssapi_test.go000066400000000000000000000011011427031340300174040ustar00rootroot00000000000000package gssapi import ( "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" ) func TestOID(t *testing.T) { var tests = []struct { name OIDName oid []int }{ {OIDMSLegacyKRB5, []int{1, 2, 840, 48018, 1, 2, 2}}, {OIDKRB5, []int{1, 2, 840, 113554, 1, 2, 2}}, {OIDSPNEGO, []int{1, 3, 6, 1, 5, 5, 2}}, {OIDGSSIAKerb, []int{1, 3, 6, 1, 5, 2, 5}}, } for _, tst := range tests { oid := asn1.ObjectIdentifier(tst.oid) assert.True(t, oid.Equal(OIDName(tst.name).OID()), "OID value not as expected for %s", tst.name) } } gokrb5-8.4.3/v8/gssapi/wrapToken.go000066400000000000000000000154511427031340300170460ustar00rootroot00000000000000package gssapi import ( "bytes" "crypto/hmac" "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" ) // RFC 4121, section 4.2.6.2 const ( // HdrLen is the length of the Wrap Token's header HdrLen = 16 // FillerByte is a filler in the WrapToken structure FillerByte byte = 0xFF ) // WrapToken represents a GSS API Wrap token, as defined in RFC 4121. // It contains the header fields, the payload and the checksum, and provides // the logic for converting to/from bytes plus computing and verifying checksums type WrapToken struct { // const GSS Token ID: 0x0504 Flags byte // contains three flags: acceptor, sealed, acceptor subkey // const Filler: 0xFF EC uint16 // checksum length. big-endian RRC uint16 // right rotation count. big-endian SndSeqNum uint64 // sender's sequence number. big-endian Payload []byte // your data! :) CheckSum []byte // authenticated checksum of { payload | header } } // Return the 2 bytes identifying a GSS API Wrap token func getGssWrapTokenId() *[2]byte { return &[2]byte{0x05, 0x04} } // Marshal the WrapToken into a byte slice. // The payload should have been set and the checksum computed, otherwise an error is returned. func (wt *WrapToken) Marshal() ([]byte, error) { if wt.CheckSum == nil { return nil, errors.New("checksum has not been set") } if wt.Payload == nil { return nil, errors.New("payload has not been set") } pldOffset := HdrLen // Offset of the payload in the token chkSOffset := HdrLen + len(wt.Payload) // Offset of the checksum in the token bytes := make([]byte, chkSOffset+int(wt.EC)) copy(bytes[0:], getGssWrapTokenId()[:]) bytes[2] = wt.Flags bytes[3] = FillerByte binary.BigEndian.PutUint16(bytes[4:6], wt.EC) binary.BigEndian.PutUint16(bytes[6:8], wt.RRC) binary.BigEndian.PutUint64(bytes[8:16], wt.SndSeqNum) copy(bytes[pldOffset:], wt.Payload) copy(bytes[chkSOffset:], wt.CheckSum) return bytes, nil } // SetCheckSum uses the passed encryption key and key usage to compute the checksum over the payload and // the header, and sets the CheckSum field of this WrapToken. // If the payload has not been set or the checksum has already been set, an error is returned. func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error { if wt.Payload == nil { return errors.New("payload has not been set") } if wt.CheckSum != nil { return errors.New("checksum has already been computed") } chkSum, cErr := wt.computeCheckSum(key, keyUsage) if cErr != nil { return cErr } wt.CheckSum = chkSum return nil } // ComputeCheckSum computes and returns the checksum of this token, computed using the passed key and key usage. // Note: This will NOT update the struct's Checksum field. func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) { if wt.Payload == nil { return nil, errors.New("cannot compute checksum with uninitialized payload") } // Build a slice containing { payload | header } checksumMe := make([]byte, HdrLen+len(wt.Payload)) copy(checksumMe[0:], wt.Payload) copy(checksumMe[len(wt.Payload):], getChecksumHeader(wt.Flags, wt.SndSeqNum)) encType, err := crypto.GetEtype(key.KeyType) if err != nil { return nil, err } return encType.GetChecksumHash(key.KeyValue, checksumMe, keyUsage) } // Build a header suitable for a checksum computation func getChecksumHeader(flags byte, senderSeqNum uint64) []byte { header := make([]byte, 16) copy(header[0:], []byte{0x05, 0x04, flags, 0xFF, 0x00, 0x00, 0x00, 0x00}) binary.BigEndian.PutUint64(header[8:], senderSeqNum) return header } // Verify computes the token's checksum with the provided key and usage, // and compares it to the checksum present in the token. // In case of any failure, (false, Err) is returned, with Err an explanatory error. func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) { computed, cErr := wt.computeCheckSum(key, keyUsage) if cErr != nil { return false, cErr } if !hmac.Equal(computed, wt.CheckSum) { return false, fmt.Errorf( "checksum mismatch. Computed: %s, Contained in token: %s", hex.EncodeToString(computed), hex.EncodeToString(wt.CheckSum)) } return true, nil } // Unmarshal bytes into the corresponding WrapToken. // If expectFromAcceptor is true, we expect the token to have been emitted by the gss acceptor, // and will check the according flag, returning an error if the token does not match the expectation. func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error { // Check if we can read a whole header if len(b) < 16 { return errors.New("bytes shorter than header length") } // Is the Token ID correct? if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) { return fmt.Errorf("wrong Token ID. Expected %s, was %s", hex.EncodeToString(getGssWrapTokenId()[:]), hex.EncodeToString(b[0:2])) } // Check the acceptor flag flags := b[2] isFromAcceptor := flags&0x01 == 1 if isFromAcceptor && !expectFromAcceptor { return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor") } if !isFromAcceptor && expectFromAcceptor { return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator") } // Check the filler byte if b[3] != FillerByte { return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4])) } checksumL := binary.BigEndian.Uint16(b[4:6]) // Sanity check on the checksum length if int(checksumL) > len(b)-HdrLen { return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL) } wt.Flags = flags wt.EC = checksumL wt.RRC = binary.BigEndian.Uint16(b[6:8]) wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16]) wt.Payload = b[16 : len(b)-int(checksumL)] wt.CheckSum = b[len(b)-int(checksumL):] return nil } // NewInitiatorWrapToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum. // Other flags are set to 0, and the RRC and sequence number are initialized to 0. // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier. // This is currently not supported. func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) { encType, err := crypto.GetEtype(key.KeyType) if err != nil { return nil, err } token := WrapToken{ Flags: 0x00, // all zeroed out (this is a token sent by the initiator) // Checksum size: length of output of the HMAC function, in bytes. EC: uint16(encType.GetHMACBitLength() / 8), RRC: 0, SndSeqNum: 0, Payload: payload, } if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil { return nil, err } return &token, nil } gokrb5-8.4.3/v8/gssapi/wrapToken_test.go000066400000000000000000000153241427031340300201040ustar00rootroot00000000000000package gssapi import ( "encoding/binary" "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) const ( // What a kerberized server might send testChallengeFromAcceptor = "050401ff000c000000000000575e85d601010000853b728d5268525a1386c19f" // What an initiator client could reply testChallengeReplyFromInitiator = "050400ff000c000000000000000000000101000079a033510b6f127212242b97" // session key used to sign the tokens above sessionKey = "14f9bde6b50ec508201a97f74c4e5bd3" sessionKeyType = 17 acceptorSeal = keyusage.GSSAPI_ACCEPTOR_SEAL initiatorSeal = keyusage.GSSAPI_INITIATOR_SEAL ) func getSessionKey() types.EncryptionKey { key, _ := hex.DecodeString(sessionKey) return types.EncryptionKey{ KeyType: sessionKeyType, KeyValue: key, } } func getChallengeReference() *WrapToken { challenge, _ := hex.DecodeString(testChallengeFromAcceptor) return &WrapToken{ Flags: 0x01, EC: 12, RRC: 0, SndSeqNum: binary.BigEndian.Uint64(challenge[8:16]), Payload: []byte{0x01, 0x01, 0x00, 0x00}, CheckSum: challenge[20:32], } } func getChallengeReferenceNoChksum() *WrapToken { c := getChallengeReference() c.CheckSum = nil return c } func getResponseReference() *WrapToken { response, _ := hex.DecodeString(testChallengeReplyFromInitiator) return &WrapToken{ Flags: 0x00, EC: 12, RRC: 0, SndSeqNum: 0, Payload: []byte{0x01, 0x01, 0x00, 0x00}, CheckSum: response[20:32], } } func getResponseReferenceNoChkSum() *WrapToken { r := getResponseReference() r.CheckSum = nil return r } func TestUnmarshal_Challenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken err := wt.Unmarshal(challenge, true) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getChallengeReference(), &wt, "Token not decoded as expected.") } func TestUnmarshalFailure_Challenge(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken err := wt.Unmarshal(challenge, false) assert.NotNil(t, err, "Expected error did not occur: a message from the acceptor cannot be expected to be sent from the initiator.") assert.Nil(t, wt.Payload, "Token fields should not have been initialised") assert.Nil(t, wt.CheckSum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), wt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.EC, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.RRC, "Token fields should not have been initialised") assert.Equal(t, uint64(0), wt.SndSeqNum, "Token fields should not have been initialised") } func TestUnmarshal_ChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testChallengeReplyFromInitiator) var wt WrapToken err := wt.Unmarshal(response, false) assert.Nil(t, err, "Unexpected error occurred.") assert.Equal(t, getResponseReference(), &wt, "Token not decoded as expected.") } func TestUnmarshalFailure_ChallengeReply(t *testing.T) { t.Parallel() response, _ := hex.DecodeString(testChallengeReplyFromInitiator) var wt WrapToken err := wt.Unmarshal(response, true) assert.NotNil(t, err, "Expected error did not occur: a message from the initiator cannot be expected to be sent from the acceptor.") assert.Nil(t, wt.Payload, "Token fields should not have been initialised") assert.Nil(t, wt.CheckSum, "Token fields should not have been initialised") assert.Equal(t, byte(0x00), wt.Flags, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.EC, "Token fields should not have been initialised") assert.Equal(t, uint16(0), wt.RRC, "Token fields should not have been initialised") assert.Equal(t, uint64(0), wt.SndSeqNum, "Token fields should not have been initialised") } func TestChallengeChecksumVerification(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken wt.Unmarshal(challenge, true) challengeOk, cErr := wt.Verify(getSessionKey(), acceptorSeal) assert.Nil(t, cErr, "Error occurred during checksum verification.") assert.True(t, challengeOk, "Checksum verification failed.") } func TestResponseChecksumVerification(t *testing.T) { t.Parallel() reply, _ := hex.DecodeString(testChallengeReplyFromInitiator) var wt WrapToken wt.Unmarshal(reply, false) replyOk, rErr := wt.Verify(getSessionKey(), initiatorSeal) assert.Nil(t, rErr, "Error occurred during checksum verification.") assert.True(t, replyOk, "Checksum verification failed.") } func TestChecksumVerificationFailure(t *testing.T) { t.Parallel() challenge, _ := hex.DecodeString(testChallengeFromAcceptor) var wt WrapToken wt.Unmarshal(challenge, true) // Test a failure with the correct key but wrong keyusage: challengeOk, cErr := wt.Verify(getSessionKey(), initiatorSeal) assert.NotNil(t, cErr, "Expected error did not occur.") assert.False(t, challengeOk, "Checksum verification succeeded when it should have failed.") wrongKeyVal, _ := hex.DecodeString("14f9bde6b50ec508201a97f74c4effff") badKey := types.EncryptionKey{ KeyType: sessionKeyType, KeyValue: wrongKeyVal, } // Test a failure with the wrong key but correct keyusage: wrongKeyOk, wkErr := wt.Verify(badKey, acceptorSeal) assert.NotNil(t, wkErr, "Expected error did not occur.") assert.False(t, wrongKeyOk, "Checksum verification succeeded when it should have failed.") } func TestMarshal_Challenge(t *testing.T) { t.Parallel() bytes, _ := getChallengeReference().Marshal() assert.Equal(t, testChallengeFromAcceptor, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_ChallengeReply(t *testing.T) { t.Parallel() bytes, _ := getResponseReference().Marshal() assert.Equal(t, testChallengeReplyFromInitiator, hex.EncodeToString(bytes), "Marshalling did not yield the expected result.") } func TestMarshal_Failures(t *testing.T) { t.Parallel() noChkSum := getResponseReferenceNoChkSum() chkBytes, chkErr := noChkSum.Marshal() assert.Nil(t, chkBytes, "No bytes should be returned.") assert.NotNil(t, chkErr, "Expected an error as no checksum was set") noPayload := getResponseReference() noPayload.Payload = nil pldBytes, pldErr := noPayload.Marshal() assert.Nil(t, pldBytes, "No bytes should be returned.") assert.NotNil(t, pldErr, "Expected an error as no checksum was set") } func TestNewInitiatorTokenSignatureAndMarshalling(t *testing.T) { t.Parallel() token, tErr := NewInitiatorWrapToken([]byte{0x01, 0x01, 0x00, 0x00}, getSessionKey()) assert.Nil(t, tErr, "Unexpected error.") assert.Equal(t, getResponseReference(), token, "Token failed to be marshalled to the expected bytes.") } gokrb5-8.4.3/v8/iana/000077500000000000000000000000001427031340300141615ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/addrtype/000077500000000000000000000000001427031340300157755ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/addrtype/constants.go000066400000000000000000000005221427031340300203370ustar00rootroot00000000000000// Package addrtype provides Address type assigned numbers. package addrtype // Address type IDs. const ( IPv4 int32 = 2 Directional int32 = 3 ChaosNet int32 = 5 XNS int32 = 6 ISO int32 = 7 DECNETPhaseIV int32 = 12 AppleTalkDDP int32 = 16 NetBios int32 = 20 IPv6 int32 = 24 ) gokrb5-8.4.3/v8/iana/adtype/000077500000000000000000000000001427031340300154475ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/adtype/constants.go000066400000000000000000000014711427031340300200150ustar00rootroot00000000000000// Package adtype provides Authenticator type assigned numbers. package adtype // Authenticator type IDs. const ( ADIfRelevant int32 = 1 ADIntendedForServer int32 = 2 ADIntendedForApplicationClass int32 = 3 ADKDCIssued int32 = 4 ADAndOr int32 = 5 ADMandatoryTicketExtensions int32 = 6 ADInTicketExtensions int32 = 7 ADMandatoryForKDC int32 = 8 OSFDCE int32 = 64 SESAME int32 = 65 ADOSFDCEPKICertID int32 = 66 ADAuthenticationStrength int32 = 70 ADFXFastArmor int32 = 71 ADFXFastUsed int32 = 72 ADWin2KPAC int32 = 128 ADEtypeNegotiation int32 = 129 //Reserved values 9-63 ) gokrb5-8.4.3/v8/iana/asnAppTag/000077500000000000000000000000001427031340300160375ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/asnAppTag/constants.go000066400000000000000000000007611427031340300204060ustar00rootroot00000000000000// Package asnAppTag provides ASN1 application tag numbers. package asnAppTag // ASN1 application tag numbers. const ( Ticket = 1 Authenticator = 2 EncTicketPart = 3 ASREQ = 10 TGSREQ = 12 ASREP = 11 TGSREP = 13 APREQ = 14 APREP = 15 KRBSafe = 20 KRBPriv = 21 KRBCred = 22 EncASRepPart = 25 EncTGSRepPart = 26 EncAPRepPart = 27 EncKrbPrivPart = 28 EncKrbCredPart = 29 KRBError = 30 ) gokrb5-8.4.3/v8/iana/chksumtype/000077500000000000000000000000001427031340300163555ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/chksumtype/constants.go000066400000000000000000000017431427031340300207250ustar00rootroot00000000000000// Package chksumtype provides Kerberos 5 checksum type assigned numbers. package chksumtype // Checksum type IDs. const ( //RESERVED : 0 CRC32 int32 = 1 RSA_MD4 int32 = 2 RSA_MD4_DES int32 = 3 DES_MAC int32 = 4 DES_MAC_K int32 = 5 RSA_MD4_DES_K int32 = 6 RSA_MD5 int32 = 7 RSA_MD5_DES int32 = 8 RSA_MD5_DES3 int32 = 9 SHA1_ID10 int32 = 10 //UNASSIGNED : 11 HMAC_SHA1_DES3_KD int32 = 12 HMAC_SHA1_DES3 int32 = 13 SHA1_ID14 int32 = 14 HMAC_SHA1_96_AES128 int32 = 15 HMAC_SHA1_96_AES256 int32 = 16 CMAC_CAMELLIA128 int32 = 17 CMAC_CAMELLIA256 int32 = 18 HMAC_SHA256_128_AES128 int32 = 19 HMAC_SHA384_192_AES256 int32 = 20 //UNASSIGNED : 21-32770 GSSAPI int32 = 32771 //UNASSIGNED : 32772-2147483647 KERB_CHECKSUM_HMAC_MD5_UNSIGNED uint32 = 4294967158 // 0xFFFFFF76 documentation says this is -138 but in an unsigned int this is 4294967158 KERB_CHECKSUM_HMAC_MD5 int32 = -138 ) gokrb5-8.4.3/v8/iana/constants.go000066400000000000000000000001731427031340300165250ustar00rootroot00000000000000// Package iana provides Kerberos 5 assigned numbers. package iana // PVNO is the Protocol Version Number. const PVNO = 5 gokrb5-8.4.3/v8/iana/errorcode/000077500000000000000000000000001427031340300161455ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/errorcode/constants.go000066400000000000000000000305641427031340300205200ustar00rootroot00000000000000// Package errorcode provides Kerberos 5 assigned error codes. package errorcode import "fmt" // Kerberos error codes. const ( KDC_ERR_NONE int32 = 0 //No error KDC_ERR_NAME_EXP int32 = 1 //Client's entry in database has expired KDC_ERR_SERVICE_EXP int32 = 2 //Server's entry in database has expired KDC_ERR_BAD_PVNO int32 = 3 //Requested protocol version number not supported KDC_ERR_C_OLD_MAST_KVNO int32 = 4 //Client's key encrypted in old master key KDC_ERR_S_OLD_MAST_KVNO int32 = 5 //Server's key encrypted in old master key KDC_ERR_C_PRINCIPAL_UNKNOWN int32 = 6 //Client not found in Kerberos database KDC_ERR_S_PRINCIPAL_UNKNOWN int32 = 7 //Server not found in Kerberos database KDC_ERR_PRINCIPAL_NOT_UNIQUE int32 = 8 //Multiple principal entries in database KDC_ERR_NULL_KEY int32 = 9 //The client or server has a null key KDC_ERR_CANNOT_POSTDATE int32 = 10 //Ticket not eligible for postdating KDC_ERR_NEVER_VALID int32 = 11 //Requested starttime is later than end time KDC_ERR_POLICY int32 = 12 //KDC policy rejects request KDC_ERR_BADOPTION int32 = 13 //KDC cannot accommodate requested option KDC_ERR_ETYPE_NOSUPP int32 = 14 //KDC has no support for encryption type KDC_ERR_SUMTYPE_NOSUPP int32 = 15 //KDC has no support for checksum type KDC_ERR_PADATA_TYPE_NOSUPP int32 = 16 //KDC has no support for padata type KDC_ERR_TRTYPE_NOSUPP int32 = 17 //KDC has no support for transited type KDC_ERR_CLIENT_REVOKED int32 = 18 //Clients credentials have been revoked KDC_ERR_SERVICE_REVOKED int32 = 19 //Credentials for server have been revoked KDC_ERR_TGT_REVOKED int32 = 20 //TGT has been revoked KDC_ERR_CLIENT_NOTYET int32 = 21 //Client not yet valid; try again later KDC_ERR_SERVICE_NOTYET int32 = 22 //Server not yet valid; try again later KDC_ERR_KEY_EXPIRED int32 = 23 //Password has expired; change password to reset KDC_ERR_PREAUTH_FAILED int32 = 24 //Pre-authentication information was invalid KDC_ERR_PREAUTH_REQUIRED int32 = 25 //Additional pre-authentication required KDC_ERR_SERVER_NOMATCH int32 = 26 //Requested server and ticket don't match KDC_ERR_MUST_USE_USER2USER int32 = 27 //Server principal valid for user2user only KDC_ERR_PATH_NOT_ACCEPTED int32 = 28 //KDC Policy rejects transited path KDC_ERR_SVC_UNAVAILABLE int32 = 29 //A service is not available KRB_AP_ERR_BAD_INTEGRITY int32 = 31 //Integrity check on decrypted field failed KRB_AP_ERR_TKT_EXPIRED int32 = 32 //Ticket expired KRB_AP_ERR_TKT_NYV int32 = 33 //Ticket not yet valid KRB_AP_ERR_REPEAT int32 = 34 //Request is a replay KRB_AP_ERR_NOT_US int32 = 35 //The ticket isn't for us KRB_AP_ERR_BADMATCH int32 = 36 //Ticket and authenticator don't match KRB_AP_ERR_SKEW int32 = 37 //Clock skew too great KRB_AP_ERR_BADADDR int32 = 38 //Incorrect net address KRB_AP_ERR_BADVERSION int32 = 39 //Protocol version mismatch KRB_AP_ERR_MSG_TYPE int32 = 40 //Invalid msg type KRB_AP_ERR_MODIFIED int32 = 41 //Message stream modified KRB_AP_ERR_BADORDER int32 = 42 //Message out of order KRB_AP_ERR_BADKEYVER int32 = 44 //Specified version of key is not available KRB_AP_ERR_NOKEY int32 = 45 //Service key not available KRB_AP_ERR_MUT_FAIL int32 = 46 //Mutual authentication failed KRB_AP_ERR_BADDIRECTION int32 = 47 //Incorrect message direction KRB_AP_ERR_METHOD int32 = 48 //Alternative authentication method required KRB_AP_ERR_BADSEQ int32 = 49 //Incorrect sequence number in message KRB_AP_ERR_INAPP_CKSUM int32 = 50 //Inappropriate type of checksum in message KRB_AP_PATH_NOT_ACCEPTED int32 = 51 //Policy rejects transited path KRB_ERR_RESPONSE_TOO_BIG int32 = 52 //Response too big for UDP; retry with TCP KRB_ERR_GENERIC int32 = 60 //Generic error (description in e-text) KRB_ERR_FIELD_TOOLONG int32 = 61 //Field is too long for this implementation KDC_ERROR_CLIENT_NOT_TRUSTED int32 = 62 //Reserved for PKINIT KDC_ERROR_KDC_NOT_TRUSTED int32 = 63 //Reserved for PKINIT KDC_ERROR_INVALID_SIG int32 = 64 //Reserved for PKINIT KDC_ERR_KEY_TOO_WEAK int32 = 65 //Reserved for PKINIT KDC_ERR_CERTIFICATE_MISMATCH int32 = 66 //Reserved for PKINIT KRB_AP_ERR_NO_TGT int32 = 67 //No TGT available to validate USER-TO-USER KDC_ERR_WRONG_REALM int32 = 68 //Reserved for future use KRB_AP_ERR_USER_TO_USER_REQUIRED int32 = 69 //Ticket must be for USER-TO-USER KDC_ERR_CANT_VERIFY_CERTIFICATE int32 = 70 //Reserved for PKINIT KDC_ERR_INVALID_CERTIFICATE int32 = 71 //Reserved for PKINIT KDC_ERR_REVOKED_CERTIFICATE int32 = 72 //Reserved for PKINIT KDC_ERR_REVOCATION_STATUS_UNKNOWN int32 = 73 //Reserved for PKINIT KDC_ERR_REVOCATION_STATUS_UNAVAILABLE int32 = 74 //Reserved for PKINIT KDC_ERR_CLIENT_NAME_MISMATCH int32 = 75 //Reserved for PKINIT KDC_ERR_KDC_NAME_MISMATCH int32 = 76 //Reserved for PKINIT ) // Lookup an error code description. func Lookup(i int32) string { if s, ok := errorcodeLookup[i]; ok { return fmt.Sprintf("(%d) %s", i, s) } return fmt.Sprintf("Unknown ErrorCode %d", i) } var errorcodeLookup = map[int32]string{ KDC_ERR_NONE: "KDC_ERR_NONE No error", KDC_ERR_NAME_EXP: "KDC_ERR_NAME_EXP Client's entry in database has expired", KDC_ERR_SERVICE_EXP: "KDC_ERR_SERVICE_EXP Server's entry in database has expired", KDC_ERR_BAD_PVNO: "KDC_ERR_BAD_PVNO Requested protocol version number not supported", KDC_ERR_C_OLD_MAST_KVNO: "KDC_ERR_C_OLD_MAST_KVNO Client's key encrypted in old master key", KDC_ERR_S_OLD_MAST_KVNO: "KDC_ERR_S_OLD_MAST_KVNO Server's key encrypted in old master key", KDC_ERR_C_PRINCIPAL_UNKNOWN: "KDC_ERR_C_PRINCIPAL_UNKNOWN Client not found in Kerberos database", KDC_ERR_S_PRINCIPAL_UNKNOWN: "KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database", KDC_ERR_PRINCIPAL_NOT_UNIQUE: "KDC_ERR_PRINCIPAL_NOT_UNIQUE Multiple principal entries in database", KDC_ERR_NULL_KEY: "KDC_ERR_NULL_KEY The client or server has a null key", KDC_ERR_CANNOT_POSTDATE: "KDC_ERR_CANNOT_POSTDATE Ticket not eligible for postdating", KDC_ERR_NEVER_VALID: "KDC_ERR_NEVER_VALID Requested starttime is later than end time", KDC_ERR_POLICY: "KDC_ERR_POLICY KDC policy rejects request", KDC_ERR_BADOPTION: "KDC_ERR_BADOPTION KDC cannot accommodate requested option", KDC_ERR_ETYPE_NOSUPP: "KDC_ERR_ETYPE_NOSUPP KDC has no support for encryption type", KDC_ERR_SUMTYPE_NOSUPP: "KDC_ERR_SUMTYPE_NOSUPP KDC has no support for checksum type", KDC_ERR_PADATA_TYPE_NOSUPP: "KDC_ERR_PADATA_TYPE_NOSUPP KDC has no support for padata type", KDC_ERR_TRTYPE_NOSUPP: "KDC_ERR_TRTYPE_NOSUPP KDC has no support for transited type", KDC_ERR_CLIENT_REVOKED: "KDC_ERR_CLIENT_REVOKED Clients credentials have been revoked", KDC_ERR_SERVICE_REVOKED: "KDC_ERR_SERVICE_REVOKED Credentials for server have been revoked", KDC_ERR_TGT_REVOKED: "KDC_ERR_TGT_REVOKED TGT has been revoked", KDC_ERR_CLIENT_NOTYET: "KDC_ERR_CLIENT_NOTYET Client not yet valid; try again later", KDC_ERR_SERVICE_NOTYET: "KDC_ERR_SERVICE_NOTYET Server not yet valid; try again later", KDC_ERR_KEY_EXPIRED: "KDC_ERR_KEY_EXPIRED Password has expired; change password to reset", KDC_ERR_PREAUTH_FAILED: "KDC_ERR_PREAUTH_FAILED Pre-authentication information was invalid", KDC_ERR_PREAUTH_REQUIRED: "KDC_ERR_PREAUTH_REQUIRED Additional pre-authentication required", KDC_ERR_SERVER_NOMATCH: "KDC_ERR_SERVER_NOMATCH Requested server and ticket don't match", KDC_ERR_MUST_USE_USER2USER: "KDC_ERR_MUST_USE_USER2USER Server principal valid for user2user only", KDC_ERR_PATH_NOT_ACCEPTED: "KDC_ERR_PATH_NOT_ACCEPTED KDC Policy rejects transited path", KDC_ERR_SVC_UNAVAILABLE: "KDC_ERR_SVC_UNAVAILABLE A service is not available", KRB_AP_ERR_BAD_INTEGRITY: "KRB_AP_ERR_BAD_INTEGRITY Integrity check on decrypted field failed", KRB_AP_ERR_TKT_EXPIRED: "KRB_AP_ERR_TKT_EXPIRED Ticket expired", KRB_AP_ERR_TKT_NYV: "KRB_AP_ERR_TKT_NYV Ticket not yet valid", KRB_AP_ERR_REPEAT: "KRB_AP_ERR_REPEAT Request is a replay", KRB_AP_ERR_NOT_US: "KRB_AP_ERR_NOT_US The ticket isn't for us", KRB_AP_ERR_BADMATCH: "KRB_AP_ERR_BADMATCH Ticket and authenticator don't match", KRB_AP_ERR_SKEW: "KRB_AP_ERR_SKEW Clock skew too great", KRB_AP_ERR_BADADDR: "KRB_AP_ERR_BADADDR Incorrect net address", KRB_AP_ERR_BADVERSION: "KRB_AP_ERR_BADVERSION Protocol version mismatch", KRB_AP_ERR_MSG_TYPE: "KRB_AP_ERR_MSG_TYPE Invalid msg type", KRB_AP_ERR_MODIFIED: "KRB_AP_ERR_MODIFIED Message stream modified", KRB_AP_ERR_BADORDER: "KRB_AP_ERR_BADORDER Message out of order", KRB_AP_ERR_BADKEYVER: "KRB_AP_ERR_BADKEYVER Specified version of key is not available", KRB_AP_ERR_NOKEY: "KRB_AP_ERR_NOKEY Service key not available", KRB_AP_ERR_MUT_FAIL: "KRB_AP_ERR_MUT_FAIL Mutual authentication failed", KRB_AP_ERR_BADDIRECTION: "KRB_AP_ERR_BADDIRECTION Incorrect message direction", KRB_AP_ERR_METHOD: "KRB_AP_ERR_METHOD Alternative authentication method required", KRB_AP_ERR_BADSEQ: "KRB_AP_ERR_BADSEQ Incorrect sequence number in message", KRB_AP_ERR_INAPP_CKSUM: "KRB_AP_ERR_INAPP_CKSUM Inappropriate type of checksum in message", KRB_AP_PATH_NOT_ACCEPTED: "KRB_AP_PATH_NOT_ACCEPTED Policy rejects transited path", KRB_ERR_RESPONSE_TOO_BIG: "KRB_ERR_RESPONSE_TOO_BIG Response too big for UDP; retry with TCP", KRB_ERR_GENERIC: "KRB_ERR_GENERIC Generic error (description in e-text)", KRB_ERR_FIELD_TOOLONG: "KRB_ERR_FIELD_TOOLONG Field is too long for this implementation", KDC_ERROR_CLIENT_NOT_TRUSTED: "KDC_ERROR_CLIENT_NOT_TRUSTED Reserved for PKINIT", KDC_ERROR_KDC_NOT_TRUSTED: "KDC_ERROR_KDC_NOT_TRUSTED Reserved for PKINIT", KDC_ERROR_INVALID_SIG: "KDC_ERROR_INVALID_SIG Reserved for PKINIT", KDC_ERR_KEY_TOO_WEAK: "KDC_ERR_KEY_TOO_WEAK Reserved for PKINIT", KDC_ERR_CERTIFICATE_MISMATCH: "KDC_ERR_CERTIFICATE_MISMATCH Reserved for PKINIT", KRB_AP_ERR_NO_TGT: "KRB_AP_ERR_NO_TGT No TGT available to validate USER-TO-USER", KDC_ERR_WRONG_REALM: "KDC_ERR_WRONG_REALM Reserved for future use", KRB_AP_ERR_USER_TO_USER_REQUIRED: "KRB_AP_ERR_USER_TO_USER_REQUIRED Ticket must be for USER-TO-USER", KDC_ERR_CANT_VERIFY_CERTIFICATE: "KDC_ERR_CANT_VERIFY_CERTIFICATE Reserved for PKINIT", KDC_ERR_INVALID_CERTIFICATE: "KDC_ERR_INVALID_CERTIFICATE Reserved for PKINIT", KDC_ERR_REVOKED_CERTIFICATE: "KDC_ERR_REVOKED_CERTIFICATE Reserved for PKINIT", KDC_ERR_REVOCATION_STATUS_UNKNOWN: "KDC_ERR_REVOCATION_STATUS_UNKNOWN Reserved for PKINIT", KDC_ERR_REVOCATION_STATUS_UNAVAILABLE: "KDC_ERR_REVOCATION_STATUS_UNAVAILABLE Reserved for PKINIT", KDC_ERR_CLIENT_NAME_MISMATCH: "KDC_ERR_CLIENT_NAME_MISMATCH Reserved for PKINIT", KDC_ERR_KDC_NAME_MISMATCH: "KDC_ERR_KDC_NAME_MISMATCH Reserved for PKINIT", } gokrb5-8.4.3/v8/iana/etypeID/000077500000000000000000000000001427031340300155245ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/etypeID/constants.go000066400000000000000000000073561427031340300201020ustar00rootroot00000000000000// Package etypeID provides Kerberos 5 encryption type assigned numbers. package etypeID // Kerberos encryption type assigned numbers. const ( //RESERVED : 0 DES_CBC_CRC int32 = 1 DES_CBC_MD4 int32 = 2 DES_CBC_MD5 int32 = 3 DES_CBC_RAW int32 = 4 DES3_CBC_MD5 int32 = 5 DES3_CBC_RAW int32 = 6 DES3_CBC_SHA1 int32 = 7 DES_HMAC_SHA1 int32 = 8 DSAWITHSHA1_CMSOID int32 = 9 MD5WITHRSAENCRYPTION_CMSOID int32 = 10 SHA1WITHRSAENCRYPTION_CMSOID int32 = 11 RC2CBC_ENVOID int32 = 12 RSAENCRYPTION_ENVOID int32 = 13 RSAES_OAEP_ENV_OID int32 = 14 DES_EDE3_CBC_ENV_OID int32 = 15 DES3_CBC_SHA1_KD int32 = 16 AES128_CTS_HMAC_SHA1_96 int32 = 17 AES256_CTS_HMAC_SHA1_96 int32 = 18 AES128_CTS_HMAC_SHA256_128 int32 = 19 AES256_CTS_HMAC_SHA384_192 int32 = 20 //UNASSIGNED : 21-22 RC4_HMAC int32 = 23 RC4_HMAC_EXP int32 = 24 CAMELLIA128_CTS_CMAC int32 = 25 CAMELLIA256_CTS_CMAC int32 = 26 //UNASSIGNED : 27-64 SUBKEY_KEYMATERIAL int32 = 65 //UNASSIGNED : 66-2147483647 ) // ETypesByName is a map of EncType names to their assigned EncType number. var ETypesByName = map[string]int32{ "des-cbc-crc": DES_CBC_CRC, "des-cbc-md4": DES_CBC_MD4, "des-cbc-md5": DES_CBC_MD5, "des-cbc-raw": DES_CBC_RAW, "des3-cbc-md5": DES3_CBC_MD5, "des3-cbc-raw": DES3_CBC_RAW, "des3-cbc-sha1": DES3_CBC_SHA1, "des3-hmac-sha1": DES_HMAC_SHA1, "des3-cbc-sha1-kd": DES3_CBC_SHA1_KD, "des-hmac-sha1": DES_HMAC_SHA1, "dsaWithSHA1-CmsOID": DSAWITHSHA1_CMSOID, "md5WithRSAEncryption-CmsOID": MD5WITHRSAENCRYPTION_CMSOID, "sha1WithRSAEncryption-CmsOID": SHA1WITHRSAENCRYPTION_CMSOID, "rc2CBC-EnvOID": RC2CBC_ENVOID, "rsaEncryption-EnvOID": RSAENCRYPTION_ENVOID, "rsaES-OAEP-ENV-OID": RSAES_OAEP_ENV_OID, "des-ede3-cbc-Env-OID": DES_EDE3_CBC_ENV_OID, "aes128-cts-hmac-sha1-96": AES128_CTS_HMAC_SHA1_96, "aes128-cts": AES128_CTS_HMAC_SHA1_96, "aes128-sha1": AES128_CTS_HMAC_SHA1_96, "aes256-cts-hmac-sha1-96": AES256_CTS_HMAC_SHA1_96, "aes256-cts": AES256_CTS_HMAC_SHA1_96, "aes256-sha1": AES256_CTS_HMAC_SHA1_96, "aes128-cts-hmac-sha256-128": AES128_CTS_HMAC_SHA256_128, "aes128-sha2": AES128_CTS_HMAC_SHA256_128, "aes256-cts-hmac-sha384-192": AES256_CTS_HMAC_SHA384_192, "aes256-sha2": AES256_CTS_HMAC_SHA384_192, "arcfour-hmac": RC4_HMAC, "rc4-hmac": RC4_HMAC, "arcfour-hmac-md5": RC4_HMAC, "arcfour-hmac-exp": RC4_HMAC_EXP, "rc4-hmac-exp": RC4_HMAC_EXP, "arcfour-hmac-md5-exp": RC4_HMAC_EXP, "camellia128-cts-cmac": CAMELLIA128_CTS_CMAC, "camellia128-cts": CAMELLIA128_CTS_CMAC, "camellia256-cts-cmac": CAMELLIA256_CTS_CMAC, "camellia256-cts": CAMELLIA256_CTS_CMAC, "subkey-keymaterial": SUBKEY_KEYMATERIAL, } // EtypeSupported resolves the etype name string to the etype ID. // If zero is returned the etype is not supported by gokrb5. func EtypeSupported(etype string) int32 { // Slice of supported enctype IDs s := []int32{ AES128_CTS_HMAC_SHA1_96, AES256_CTS_HMAC_SHA1_96, AES128_CTS_HMAC_SHA256_128, AES256_CTS_HMAC_SHA384_192, DES3_CBC_SHA1_KD, RC4_HMAC, } id := ETypesByName[etype] if id == 0 { return id } for _, sid := range s { if id == sid { return id } } return 0 } gokrb5-8.4.3/v8/iana/flags/000077500000000000000000000000001427031340300152555ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/flags/constants.go000066400000000000000000000016761427031340300176320ustar00rootroot00000000000000// Package flags provides Kerberos 5 flag assigned numbers. package flags // Flag values for KRB5 messages and tickets. const ( Reserved = 0 Forwardable = 1 Forwarded = 2 Proxiable = 3 Proxy = 4 AllowPostDate = 5 MayPostDate = 5 PostDated = 6 Invalid = 7 Renewable = 8 Initial = 9 PreAuthent = 10 HWAuthent = 11 OptHardwareAuth = 11 RequestAnonymous = 12 TransitedPolicyChecked = 12 OKAsDelegate = 13 EncPARep = 15 Canonicalize = 15 DisableTransitedCheck = 26 RenewableOK = 27 EncTktInSkey = 28 Renew = 30 Validate = 31 // AP Option Flags // 0 Reserved for future use. APOptionUseSessionKey = 1 APOptionMutualRequired = 2 // 3-31 Reserved for future use. ) gokrb5-8.4.3/v8/iana/keyusage/000077500000000000000000000000001427031340300157765ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/keyusage/constants.go000066400000000000000000000036351427031340300203500ustar00rootroot00000000000000// Package keyusage provides Kerberos 5 key usage assigned numbers. package keyusage // Key usage numbers. const ( AS_REQ_PA_ENC_TIMESTAMP = 1 KDC_REP_TICKET = 2 AS_REP_ENCPART = 3 TGS_REQ_KDC_REQ_BODY_AUTHDATA_SESSION_KEY = 4 TGS_REQ_KDC_REQ_BODY_AUTHDATA_SUB_KEY = 5 TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM = 6 TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR = 7 TGS_REP_ENCPART_SESSION_KEY = 8 TGS_REP_ENCPART_AUTHENTICATOR_SUB_KEY = 9 AP_REQ_AUTHENTICATOR_CHKSUM = 10 AP_REQ_AUTHENTICATOR = 11 AP_REP_ENCPART = 12 KRB_PRIV_ENCPART = 13 KRB_CRED_ENCPART = 14 KRB_SAFE_CHKSUM = 15 KERB_NON_KERB_SALT = 16 KERB_NON_KERB_CKSUM_SALT = 17 //18. Reserved for future use in Kerberos and related protocols. AD_KDC_ISSUED_CHKSUM = 19 //20-21. Reserved for future use in Kerberos and related protocols. GSSAPI_ACCEPTOR_SEAL = 22 GSSAPI_ACCEPTOR_SIGN = 23 GSSAPI_INITIATOR_SEAL = 24 GSSAPI_INITIATOR_SIGN = 25 KEY_USAGE_FAST_REQ_CHKSUM = 50 KEY_USAGE_FAST_ENC = 51 KEY_USAGE_FAST_REP = 52 KEY_USAGE_FAST_FINISHED = 53 KEY_USAGE_ENC_CHALLENGE_CLIENT = 54 KEY_USAGE_ENC_CHALLENGE_KDC = 55 KEY_USAGE_AS_REQ = 56 //26-511. Reserved for future use in Kerberos and related protocols. //512-1023. Reserved for uses internal to a Kerberos implementation. //1024. Encryption for application use in protocols that do not specify key usage values //1025. Checksums for application use in protocols that do not specify key usage values //1026-2047. Reserved for application use. ) gokrb5-8.4.3/v8/iana/msgtype/000077500000000000000000000000001427031340300156515ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/msgtype/constants.go000066400000000000000000000014711427031340300202170ustar00rootroot00000000000000// Package msgtype provides Kerberos 5 message type assigned numbers. package msgtype // KRB message type IDs. const ( KRB_AS_REQ = 10 //Request for initial authentication KRB_AS_REP = 11 //Response to KRB_AS_REQ request KRB_TGS_REQ = 12 //Request for authentication based on TGT KRB_TGS_REP = 13 //Response to KRB_TGS_REQ request KRB_AP_REQ = 14 //Application request to server KRB_AP_REP = 15 //Response to KRB_AP_REQ_MUTUAL KRB_RESERVED16 = 16 //Reserved for user-to-user krb_tgt_request KRB_RESERVED17 = 17 //Reserved for user-to-user krb_tgt_reply KRB_SAFE = 20 // Safe (checksummed) application message KRB_PRIV = 21 // Private (encrypted) application message KRB_CRED = 22 //Private (encrypted) message to forward credentials KRB_ERROR = 30 //Error response ) gokrb5-8.4.3/v8/iana/nametype/000077500000000000000000000000001427031340300160035ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/nametype/constants.go000066400000000000000000000014671427031340300203560ustar00rootroot00000000000000// Package nametype provides Kerberos 5 principal name type numbers. package nametype // Kerberos name type IDs. const ( KRB_NT_UNKNOWN int32 = 0 //Name type not known KRB_NT_PRINCIPAL int32 = 1 //Just the name of the principal as in DCE, or for users KRB_NT_SRV_INST int32 = 2 //Service and other unique instance (krbtgt) KRB_NT_SRV_HST int32 = 3 //Service with host name as instance (telnet, rcommands) KRB_NT_SRV_XHST int32 = 4 //Service with host as remaining components KRB_NT_UID int32 = 5 //Unique ID KRB_NT_X500_PRINCIPAL int32 = 6 //Encoded X.509 Distinguished name [RFC2253] KRB_NT_SMTP_NAME int32 = 7 //Name in form of SMTP email name (e.g., user@example.com) KRB_NT_ENTERPRISE int32 = 10 //Enterprise name; may be mapped to principal name ) gokrb5-8.4.3/v8/iana/patype/000077500000000000000000000000001427031340300154635ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/patype/constants.go000066400000000000000000000047221427031340300200330ustar00rootroot00000000000000// Package patype provides Kerberos 5 pre-authentication type assigned numbers. package patype // Kerberos pre-authentication type assigned numbers. const ( PA_TGS_REQ int32 = 1 PA_ENC_TIMESTAMP int32 = 2 PA_PW_SALT int32 = 3 //RESERVED : 4 PA_ENC_UNIX_TIME int32 = 5 PA_SANDIA_SECUREID int32 = 6 PA_SESAME int32 = 7 PA_OSF_DCE int32 = 8 PA_CYBERSAFE_SECUREID int32 = 9 PA_AFS3_SALT int32 = 10 PA_ETYPE_INFO int32 = 11 PA_SAM_CHALLENGE int32 = 12 PA_SAM_RESPONSE int32 = 13 PA_PK_AS_REQ_OLD int32 = 14 PA_PK_AS_REP_OLD int32 = 15 PA_PK_AS_REQ int32 = 16 PA_PK_AS_REP int32 = 17 PA_PK_OCSP_RESPONSE int32 = 18 PA_ETYPE_INFO2 int32 = 19 PA_USE_SPECIFIED_KVNO int32 = 20 PA_SVR_REFERRAL_INFO int32 = 20 PA_SAM_REDIRECT int32 = 21 PA_GET_FROM_TYPED_DATA int32 = 22 TD_PADATA int32 = 22 PA_SAM_ETYPE_INFO int32 = 23 PA_ALT_PRINC int32 = 24 PA_SERVER_REFERRAL int32 = 25 //UNASSIGNED : 26-29 PA_SAM_CHALLENGE2 int32 = 30 PA_SAM_RESPONSE2 int32 = 31 //UNASSIGNED : 32-40 PA_EXTRA_TGT int32 = 41 //UNASSIGNED : 42-100 TD_PKINIT_CMS_CERTIFICATES int32 = 101 TD_KRB_PRINCIPAL int32 = 102 TD_KRB_REALM int32 = 103 TD_TRUSTED_CERTIFIERS int32 = 104 TD_CERTIFICATE_INDEX int32 = 105 TD_APP_DEFINED_ERROR int32 = 106 TD_REQ_NONCE int32 = 107 TD_REQ_SEQ int32 = 108 TD_DH_PARAMETERS int32 = 109 //UNASSIGNED : 110 TD_CMS_DIGEST_ALGORITHMS int32 = 111 TD_CERT_DIGEST_ALGORITHMS int32 = 112 //UNASSIGNED : 113-127 PA_PAC_REQUEST int32 = 128 PA_FOR_USER int32 = 129 PA_FOR_X509_USER int32 = 130 PA_FOR_CHECK_DUPS int32 = 131 PA_AS_CHECKSUM int32 = 132 PA_FX_COOKIE int32 = 133 PA_AUTHENTICATION_SET int32 = 134 PA_AUTH_SET_SELECTED int32 = 135 PA_FX_FAST int32 = 136 PA_FX_ERROR int32 = 137 PA_ENCRYPTED_CHALLENGE int32 = 138 //UNASSIGNED : 139-140 PA_OTP_CHALLENGE int32 = 141 PA_OTP_REQUEST int32 = 142 PA_OTP_CONFIRM int32 = 143 PA_OTP_PIN_CHANGE int32 = 144 PA_EPAK_AS_REQ int32 = 145 PA_EPAK_AS_REP int32 = 146 PA_PKINIT_KX int32 = 147 PA_PKU2U_NAME int32 = 148 PA_REQ_ENC_PA_REP int32 = 149 PA_AS_FRESHNESS int32 = 150 //UNASSIGNED : 151-164 PA_SUPPORTED_ETYPES int32 = 165 PA_EXTENDED_ERROR int32 = 166 ) gokrb5-8.4.3/v8/iana/trtype/000077500000000000000000000000001427031340300155105ustar00rootroot00000000000000gokrb5-8.4.3/v8/iana/trtype/constants.go000066400000000000000000000003151427031340300200520ustar00rootroot00000000000000// Package trtype provides Transited Encoding Type assigned numbers. package trtype // Transited Encoding Type IDs. const ( DOMAIN_X500_COMPRESS int32 = 1 //Reserved values All others ) gokrb5-8.4.3/v8/kadmin/000077500000000000000000000000001427031340300145145ustar00rootroot00000000000000gokrb5-8.4.3/v8/kadmin/changepasswddata.go000066400000000000000000000012041427031340300203410ustar00rootroot00000000000000package kadmin import ( "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/types" ) // ChangePasswdData is the payload to a password change message. type ChangePasswdData struct { NewPasswd []byte `asn1:"explicit,tag:0"` TargName types.PrincipalName `asn1:"explicit,optional,tag:1"` TargRealm string `asn1:"generalstring,optional,explicit,tag:2"` } // Marshal ChangePasswdData into a byte slice. func (c *ChangePasswdData) Marshal() ([]byte, error) { b, err := asn1.Marshal(*c) if err != nil { return []byte{}, err } //b = asn1tools.AddASNAppTag(b, asnAppTag.) return b, nil } gokrb5-8.4.3/v8/kadmin/changepasswddata_test.go000066400000000000000000000014441427031340300214060ustar00rootroot00000000000000package kadmin import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestChangePasswdData_Marshal(t *testing.T) { t.Parallel() chgpasswd := ChangePasswdData{ NewPasswd: []byte("newpassword"), TargName: types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, "testuser1"), TargRealm: "TEST.GOKRB5", } chpwdb, err := chgpasswd.Marshal() if err != nil { t.Fatalf("error marshaling change passwd data: %v\n", err) } b, err := hex.DecodeString(testdata.MarshaledChangePasswdData) if err != nil { t.Fatalf("Test vector read error: %v", err) } assert.Equal(t, b, chpwdb, "marshaled bytes of change passwd data not as expected") } gokrb5-8.4.3/v8/kadmin/message.go000066400000000000000000000053071427031340300164740ustar00rootroot00000000000000package kadmin import ( "bytes" "encoding/binary" "errors" "fmt" "math" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) const ( verisonHex = "ff80" ) // Request message for changing password. type Request struct { APREQ messages.APReq KRBPriv messages.KRBPriv } // Reply message for a password change. type Reply struct { MessageLength int Version int APREPLength int APREP messages.APRep KRBPriv messages.KRBPriv KRBError messages.KRBError IsKRBError bool ResultCode uint16 Result string } // Marshal a Request into a byte slice. func (m *Request) Marshal() (b []byte, err error) { b = []byte{255, 128} // protocol version number: contains the hex constant 0xff80 (big-endian integer). ab, e := m.APREQ.Marshal() if e != nil { err = fmt.Errorf("error marshaling AP_REQ: %v", e) return } if len(ab) > math.MaxUint16 { err = errors.New("length of AP_REQ greater then max Uint16 size") return } al := make([]byte, 2) binary.BigEndian.PutUint16(al, uint16(len(ab))) b = append(b, al...) b = append(b, ab...) pb, e := m.KRBPriv.Marshal() if e != nil { err = fmt.Errorf("error marshaling KRB_Priv: %v", e) return } b = append(b, pb...) if len(b)+2 > math.MaxUint16 { err = errors.New("length of message greater then max Uint16 size") return } ml := make([]byte, 2) binary.BigEndian.PutUint16(ml, uint16(len(b)+2)) b = append(ml, b...) return } // Unmarshal a byte slice into a Reply. func (m *Reply) Unmarshal(b []byte) error { m.MessageLength = int(binary.BigEndian.Uint16(b[0:2])) m.Version = int(binary.BigEndian.Uint16(b[2:4])) if m.Version != 1 { return fmt.Errorf("kadmin reply has incorrect protocol version number: %d", m.Version) } m.APREPLength = int(binary.BigEndian.Uint16(b[4:6])) if m.APREPLength != 0 { err := m.APREP.Unmarshal(b[6 : 6+m.APREPLength]) if err != nil { return err } err = m.KRBPriv.Unmarshal(b[6+m.APREPLength : m.MessageLength]) if err != nil { return err } } else { m.IsKRBError = true m.KRBError.Unmarshal(b[6:m.MessageLength]) m.ResultCode, m.Result = parseResponse(m.KRBError.EData) } return nil } func parseResponse(b []byte) (c uint16, s string) { c = binary.BigEndian.Uint16(b[0:2]) buf := bytes.NewBuffer(b[2:]) m := make([]byte, len(b)-2) binary.Read(buf, binary.BigEndian, &m) s = string(m) return } // Decrypt the encrypted part of the KRBError within the change password Reply. func (m *Reply) Decrypt(key types.EncryptionKey) error { if m.IsKRBError { return m.KRBError } err := m.KRBPriv.DecryptEncPart(key) if err != nil { return err } m.ResultCode, m.Result = parseResponse(m.KRBPriv.DecryptedEncPart.UserData) return nil } gokrb5-8.4.3/v8/kadmin/message_test.go000066400000000000000000000025241427031340300175310ustar00rootroot00000000000000package kadmin import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalReply(t *testing.T) { t.Parallel() var a Reply b, err := hex.DecodeString(testdata.MarshaledKpasswd_Rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, 236, a.MessageLength, "message length not as expected") assert.Equal(t, 1, a.Version, "message version not as expected") assert.Equal(t, 140, a.APREPLength, "AP_REP length not as expected") assert.Equal(t, iana.PVNO, a.APREP.PVNO, "AP_REP within reply not as expected") assert.Equal(t, msgtype.KRB_AP_REP, a.APREP.MsgType, "AP_REP message type within reply not as expected") assert.Equal(t, int32(18), a.APREP.EncPart.EType, "AP_REQ etype not as expected") assert.Equal(t, iana.PVNO, a.KRBPriv.PVNO, "KRBPriv within reply not as expected") assert.Equal(t, msgtype.KRB_PRIV, a.KRBPriv.MsgType, "KRBPriv type within reply not as expected") assert.Equal(t, int32(18), a.KRBPriv.EncPart.EType, "KRBPriv etype not as expected") } // Request marshal is tested via integration test in the client package due to the dynamic keys and encryption. gokrb5-8.4.3/v8/kadmin/passwd.go000066400000000000000000000036711427031340300163530ustar00rootroot00000000000000// Package kadmin provides Kerberos administration capabilities. package kadmin import ( "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // ChangePasswdMsg generate a change password request and also return the key needed to decrypt the reply. func ChangePasswdMsg(cname types.PrincipalName, realm, password string, tkt messages.Ticket, sessionKey types.EncryptionKey) (r Request, k types.EncryptionKey, err error) { // Create change password data struct and marshal to bytes chgpasswd := ChangePasswdData{ NewPasswd: []byte(password), TargName: cname, TargRealm: realm, } chpwdb, err := chgpasswd.Marshal() if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error marshaling change passwd data") return } // Generate authenticator auth, err := types.NewAuthenticator(realm, cname) if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") return } etype, err := crypto.GetEtype(sessionKey.KeyType) if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey etype") return } err = auth.GenerateSeqNumberAndSubKey(etype.GetETypeID(), etype.GetKeyByteSize()) if err != nil { err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey") return } k = auth.SubKey // Generate AP_REQ APreq, err := messages.NewAPReq(tkt, sessionKey, auth) if err != nil { return } // Form the KRBPriv encpart data kp := messages.EncKrbPrivPart{ UserData: chpwdb, Timestamp: auth.CTime, Usec: auth.Cusec, SequenceNumber: auth.SeqNumber, } kpriv := messages.NewKRBPriv(kp) err = kpriv.EncryptEncPart(k) if err != nil { err = krberror.Errorf(err, krberror.EncryptingError, "error encrypting change passwd data") return } r = Request{ APREQ: APreq, KRBPriv: kpriv, } return } gokrb5-8.4.3/v8/keytab/000077500000000000000000000000001427031340300145305ustar00rootroot00000000000000gokrb5-8.4.3/v8/keytab/keytab.go000066400000000000000000000311011427031340300163320ustar00rootroot00000000000000// Package keytab implements Kerberos keytabs: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html. package keytab import ( "bytes" "encoding/binary" "encoding/json" "errors" "fmt" "io" "os" "strings" "time" "unsafe" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/types" ) const ( keytabFirstByte byte = 05 ) // Keytab struct. type Keytab struct { version uint8 Entries []entry } // Keytab entry struct. type entry struct { Principal principal Timestamp time.Time KVNO8 uint8 Key types.EncryptionKey KVNO uint32 } func (e entry) String() string { return fmt.Sprintf("% 4d %s %-56s %2d %-64x", e.KVNO8, e.Timestamp.Format("02/01/06 15:04:05"), e.Principal.String(), e.Key.KeyType, e.Key.KeyValue, ) } // Keytab entry principal struct. type principal struct { NumComponents int16 `json:"-"` Realm string Components []string NameType int32 } func (p principal) String() string { return fmt.Sprintf("%s@%s", strings.Join(p.Components, "/"), p.Realm) } // New creates new, empty Keytab type. func New() *Keytab { var e []entry return &Keytab{ version: 2, Entries: e, } } // GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal. // If the kvno is zero then the latest kvno will be returned. The kvno is also returned for func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, int, error) { var key types.EncryptionKey var t time.Time var kv int for _, k := range kt.Entries { if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) && k.Key.KeyType == etype && (k.KVNO == uint32(kvno) || kvno == 0) && k.Timestamp.After(t) { p := true for i, n := range k.Principal.Components { if princName.NameString[i] != n { p = false break } } if p { key = k.Key kv = int(k.KVNO) t = k.Timestamp } } } if len(key.KeyValue) < 1 { return key, 0, fmt.Errorf("matching key not found in keytab. Looking for %q realm: %v kvno: %v etype: %v", princName.PrincipalNameString(), realm, kvno, etype) } return key, kv, nil } // Create a new Keytab entry. func newEntry() entry { var b []byte return entry{ Principal: newPrincipal(), Timestamp: time.Time{}, KVNO8: 0, Key: types.EncryptionKey{ KeyType: 0, KeyValue: b, }, KVNO: 0, } } func (kt Keytab) String() string { var s string s = `KVNO Timestamp Principal ET Key ---- ----------------- -------------------------------------------------------- -- ---------------------------------------------------------------- ` for _, entry := range kt.Entries { s += entry.String() + "\n" } return s } // AddEntry adds an entry to the keytab. The password should be provided in plain text and it will be converted using the defined enctype to be stored. func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time, KVNO uint8, encType int32) error { // Generate a key from the password princ, _ := types.ParseSPNString(principalName) key, _, err := crypto.GetKeyFromPassword(password, princ, realm, encType, types.PADataSequence{}) if err != nil { return err } // Populate the keytab entry principal ktep := newPrincipal() ktep.NumComponents = int16(len(princ.NameString)) if kt.version == 1 { ktep.NumComponents += 1 } ktep.Realm = realm ktep.Components = princ.NameString ktep.NameType = princ.NameType // Populate the keytab entry e := newEntry() e.Principal = ktep e.Timestamp = ts e.KVNO8 = KVNO e.KVNO = uint32(KVNO) e.Key = key kt.Entries = append(kt.Entries, e) return nil } // Create a new principal. func newPrincipal() principal { var c []string return principal{ NumComponents: 0, Realm: "", Components: c, NameType: 0, } } // Load a Keytab file into a Keytab type. func Load(ktPath string) (*Keytab, error) { kt := new(Keytab) b, err := os.ReadFile(ktPath) if err != nil { return kt, err } err = kt.Unmarshal(b) return kt, err } // Marshal keytab into byte slice func (kt *Keytab) Marshal() ([]byte, error) { b := []byte{keytabFirstByte, kt.version} for _, e := range kt.Entries { eb, err := e.marshal(int(kt.version)) if err != nil { return b, err } b = append(b, eb...) } return b, nil } // Write the keytab bytes to io.Writer. // Returns the number of bytes written func (kt *Keytab) Write(w io.Writer) (int, error) { b, err := kt.Marshal() if err != nil { return 0, fmt.Errorf("error marshaling keytab: %v", err) } return w.Write(b) } // Unmarshal byte slice of Keytab data into Keytab type. func (kt *Keytab) Unmarshal(b []byte) error { if len(b) < 2 { return fmt.Errorf("byte array is less than 2 bytes: %d", len(b)) } //The first byte of the file always has the value 5 if b[0] != keytabFirstByte { return errors.New("invalid keytab data. First byte does not equal 5") } //Get keytab version //The 2nd byte contains the version number (1 or 2) kt.version = b[1] if kt.version != 1 && kt.version != 2 { return errors.New("invalid keytab data. Keytab version is neither 1 nor 2") } //Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order var endian binary.ByteOrder endian = binary.BigEndian if kt.version == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } // n tracks position in the byte array n := 2 l, err := readInt32(b, &n, &endian) if err != nil { return err } for l != 0 { if l < 0 { //Zero padded so skip over l = l * -1 n = n + int(l) } else { if n < 0 { return fmt.Errorf("%d can't be less than zero", n) } if n+int(l) > len(b) { return fmt.Errorf("%s's length is less than %d", b, n+int(l)) } eb := b[n : n+int(l)] n = n + int(l) ke := newEntry() // p keeps track as to where we are in the byte stream var p int var err error parsePrincipal(eb, &p, kt, &ke, &endian) ke.Timestamp, err = readTimestamp(eb, &p, &endian) if err != nil { return err } rei8, err := readInt8(eb, &p, &endian) if err != nil { return err } ke.KVNO8 = uint8(rei8) rei16, err := readInt16(eb, &p, &endian) if err != nil { return err } ke.Key.KeyType = int32(rei16) rei16, err = readInt16(eb, &p, &endian) if err != nil { return err } kl := int(rei16) ke.Key.KeyValue, err = readBytes(eb, &p, kl, &endian) if err != nil { return err } // The 32-bit key version overrides the 8-bit key version. // If at least 4 bytes are left after the other fields are read and they are non-zero // this indicates the 32-bit version is present. if len(eb)-p >= 4 { // The 32-bit key may be present ri32, err := readInt32(eb, &p, &endian) if err != nil { return err } ke.KVNO = uint32(ri32) } if ke.KVNO == 0 { // Handles if the value from the last 4 bytes was zero and also if there are not the 4 bytes present. Makes sense to put the same value here as KVNO8 ke.KVNO = uint32(ke.KVNO8) } // Add the entry to the keytab kt.Entries = append(kt.Entries, ke) } // Check if there are still 4 bytes left to read // Also check that n is greater than zero if n < 0 || n > len(b) || len(b[n:]) < 4 { break } // Read the size of the next entry l, err = readInt32(b, &n, &endian) if err != nil { return err } } return nil } func (e entry) marshal(v int) ([]byte, error) { var b []byte pb, err := e.Principal.marshal(v) if err != nil { return b, err } b = append(b, pb...) var endian binary.ByteOrder endian = binary.BigEndian if v == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } t := make([]byte, 9) endian.PutUint32(t[0:4], uint32(e.Timestamp.Unix())) t[4] = e.KVNO8 endian.PutUint16(t[5:7], uint16(e.Key.KeyType)) endian.PutUint16(t[7:9], uint16(len(e.Key.KeyValue))) b = append(b, t...) buf := new(bytes.Buffer) err = binary.Write(buf, endian, e.Key.KeyValue) if err != nil { return b, err } b = append(b, buf.Bytes()...) t = make([]byte, 4) endian.PutUint32(t, e.KVNO) b = append(b, t...) // Add the length header t = make([]byte, 4) endian.PutUint32(t, uint32(len(b))) b = append(t, b...) return b, nil } // Parse the Keytab bytes of a principal into a Keytab entry's principal. func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) error { var err error ke.Principal.NumComponents, err = readInt16(b, p, e) if err != nil { return err } if kt.version == 1 { //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2 ke.Principal.NumComponents-- } lenRealm, err := readInt16(b, p, e) if err != nil { return err } realmB, err := readBytes(b, p, int(lenRealm), e) if err != nil { return err } ke.Principal.Realm = string(realmB) for i := 0; i < int(ke.Principal.NumComponents); i++ { l, err := readInt16(b, p, e) if err != nil { return err } compB, err := readBytes(b, p, int(l), e) if err != nil { return err } ke.Principal.Components = append(ke.Principal.Components, string(compB)) } if kt.version != 1 { //Name Type is omitted in version 1 ke.Principal.NameType, err = readInt32(b, p, e) if err != nil { return err } } return nil } func (p principal) marshal(v int) ([]byte, error) { //var b []byte b := make([]byte, 2) var endian binary.ByteOrder endian = binary.BigEndian if v == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } endian.PutUint16(b[0:], uint16(p.NumComponents)) realm, err := marshalString(p.Realm, v) if err != nil { return b, err } b = append(b, realm...) for _, c := range p.Components { cb, err := marshalString(c, v) if err != nil { return b, err } b = append(b, cb...) } if v != 1 { t := make([]byte, 4) endian.PutUint32(t, uint32(p.NameType)) b = append(b, t...) } return b, nil } func marshalString(s string, v int) ([]byte, error) { sb := []byte(s) b := make([]byte, 2) var endian binary.ByteOrder endian = binary.BigEndian if v == 1 && isNativeEndianLittle() { endian = binary.LittleEndian } endian.PutUint16(b[0:], uint16(len(sb))) buf := new(bytes.Buffer) err := binary.Write(buf, endian, sb) if err != nil { return b, err } b = append(b, buf.Bytes()...) return b, err } // Read bytes representing a timestamp. func readTimestamp(b []byte, p *int, e *binary.ByteOrder) (time.Time, error) { i32, err := readInt32(b, p, e) if err != nil { return time.Time{}, err } return time.Unix(int64(i32), 0), nil } // Read bytes representing an eight bit integer. func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8, err error) { if *p < 0 { return 0, fmt.Errorf("%d cannot be less than zero", *p) } if (*p + 1) > len(b) { return 0, fmt.Errorf("%s's length is less than %d", b, *p+1) } buf := bytes.NewBuffer(b[*p : *p+1]) binary.Read(buf, *e, &i) *p++ return } // Read bytes representing a sixteen bit integer. func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16, err error) { if *p < 0 { return 0, fmt.Errorf("%d cannot be less than zero", *p) } if (*p + 2) > len(b) { return 0, fmt.Errorf("%s's length is less than %d", b, *p+2) } buf := bytes.NewBuffer(b[*p : *p+2]) binary.Read(buf, *e, &i) *p += 2 return } // Read bytes representing a thirty two bit integer. func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32, err error) { if *p < 0 { return 0, fmt.Errorf("%d cannot be less than zero", *p) } if (*p + 4) > len(b) { return 0, fmt.Errorf("%s's length is less than %d", b, *p+4) } buf := bytes.NewBuffer(b[*p : *p+4]) binary.Read(buf, *e, &i) *p += 4 return } func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) ([]byte, error) { if s < 0 { return nil, fmt.Errorf("%d cannot be less than zero", s) } i := *p + s if i > len(b) { return nil, fmt.Errorf("%s's length is greater than %d", b, i) } buf := bytes.NewBuffer(b[*p:i]) r := make([]byte, s) if err := binary.Read(buf, *e, &r); err != nil { return nil, err } *p += s return r, nil } func isNativeEndianLittle() bool { var x = 0x012345678 var p = unsafe.Pointer(&x) var bp = (*[4]byte)(p) var endian bool if 0x01 == bp[0] { endian = false } else if (0x78 & 0xff) == (bp[0] & 0xff) { endian = true } else { // Default to big endian endian = false } return endian } // JSON return information about the keys held in the keytab in a JSON format. func (kt *Keytab) JSON() (string, error) { b, err := json.MarshalIndent(kt, "", " ") if err != nil { return "", err } return string(b), nil } gokrb5-8.4.3/v8/keytab/keytab_test.go000066400000000000000000000201021427031340300173700ustar00rootroot00000000000000package keytab import ( "encoding/base64" "encoding/binary" "encoding/hex" "os" "path/filepath" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestUnmarshal(t *testing.T) { t.Parallel() b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := New() err := kt.Unmarshal(b) if err != nil { t.Fatalf("Error parsing keytab data: %v\n", err) } assert.Equal(t, uint8(2), kt.version, "Keytab version not as expected") assert.Equal(t, uint32(1), kt.Entries[0].KVNO, "KVNO not as expected") assert.Equal(t, uint8(1), kt.Entries[0].KVNO8, "KVNO8 not as expected") assert.Equal(t, time.Unix(1505669592, 0), kt.Entries[0].Timestamp, "Timestamp not as expected") assert.Equal(t, int32(17), kt.Entries[0].Key.KeyType, "Key's EType not as expected") assert.Equal(t, "698c4df8e9f60e7eea5a21bf4526ad25", hex.EncodeToString(kt.Entries[0].Key.KeyValue), "Key material not as expected") assert.Equal(t, int16(1), kt.Entries[0].Principal.NumComponents, "Number of components in principal not as expected") assert.Equal(t, int32(1), kt.Entries[0].Principal.NameType, "Name type of principal not as expected") assert.Equal(t, "TEST.GOKRB5", kt.Entries[0].Principal.Realm, "Realm of principal not as expected") assert.Equal(t, "testuser1", kt.Entries[0].Principal.Components[0], "Component in principal not as expected") } func TestMarshal(t *testing.T) { t.Parallel() b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := New() err := kt.Unmarshal(b) if err != nil { t.Fatalf("Error parsing keytab data: %v\n", err) } mb, err := kt.Marshal() if err != nil { t.Fatalf("Error marshaling: %v", err) } assert.Equal(t, b, mb, "Marshaled bytes not the same as input bytes") err = kt.Unmarshal(mb) if err != nil { t.Fatalf("Error parsing marshaled bytes: %v", err) } } func TestLoad(t *testing.T) { t.Parallel() f := "test/testdata/testuser1.testtab" cwd, _ := os.Getwd() dir := os.Getenv("TRAVIS_BUILD_DIR") if dir != "" { f = dir + "/" + f } else if filepath.Base(cwd) == "keytab" { f = "../" + f } kt, err := Load(f) if err != nil { t.Fatalf("could not load keytab: %v", err) } assert.Equal(t, uint8(2), kt.version, "keytab version not as expected") assert.Equal(t, 12, len(kt.Entries), "keytab entry count not as expected: %+v", *kt) for _, e := range kt.Entries { if e.Principal.Realm != "TEST.GOKRB5" { t.Error("principal realm not as expected") } if e.Principal.NameType != int32(1) { t.Error("name type not as expected") } if e.Principal.NumComponents != int16(1) { t.Error("number of component not as expected") } if len(e.Principal.Components) != 1 { t.Error("number of component not as expected") } if e.Principal.Components[0] != "testuser1" { t.Error("principal components not as expected") } if e.Timestamp.IsZero() { t.Error("entry timestamp incorrect") } if e.KVNO == uint32(0) { t.Error("entry kvno not as expected") } if e.KVNO8 == uint8(0) { t.Error("entry kvno8 not as expected") } } } // This test provides inputs to readBytes that previously // caused a panic. func TestReadBytes(t *testing.T) { var endian binary.ByteOrder endian = binary.BigEndian p := 0 if _, err := readBytes(nil, &p, 1, &endian); err == nil { t.Fatal("err should be populated because s was given that exceeds array length") } if _, err := readBytes(nil, &p, -1, &endian); err == nil { t.Fatal("err should be given because negative s was given") } } func TestUnmarshalPotentialPanics(t *testing.T) { kt := New() // Test a good keytab with bad bytes to unmarshal. These should // return errors, but not panic. if err := kt.Unmarshal(nil); err == nil { t.Fatal("should have errored, input is absent") } if err := kt.Unmarshal([]byte{}); err == nil { t.Fatal("should have errored, input is empty") } // Incorrect first byte. if err := kt.Unmarshal([]byte{4}); err == nil { t.Fatal("should have errored, input isn't long enough") } // First byte, but no further content. if err := kt.Unmarshal([]byte{5}); err == nil { t.Fatal("should have errored, input isn't long enough") } } // cxf testing stuff func TestBadKeytabs(t *testing.T) { badPayloads := make([]string, 3) badPayloads = append(badPayloads, "BQIwMDAwMDA=") badPayloads = append(badPayloads, "BQIAAAAwAAEACjAwMDAwMDAwMDAAIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw") badPayloads = append(badPayloads, "BQKAAAAA") for _, v := range badPayloads { decodedKt, _ := base64.StdEncoding.DecodeString(v) parsedKt := new(Keytab) parsedKt.Unmarshal(decodedKt) } } func TestKeytabEntriesUser(t *testing.T) { // Load known-good keytab generated with ktutil ktutilb64 := "BQIAAABGAAEAC0VYQU1QTEUuT1JHAAR1c2VyAAAAAV5ePQAfABIAIG6I6ys5Me8XyS54Ck7kIfFBH/WxBOP3W1DdE/ntBPnGAAAAHwAAADYAAQALRVhBTVBMRS5PUkcABHVzZXIAAAABXl49AB8AEQAQm7fVug9VRBJVhEGjHyN3EgAAAB8AAAA2AAEAC0VYQU1QTEUuT1JHAAR1c2VyAAAAAV5ePQAfABcAEBENDFHhRNNvt+T54BL7uIgAAAAf" ktutilbytes, err := base64.StdEncoding.DecodeString(ktutilb64) if err != nil { t.Errorf("Could not parse b64 ktutil keytab: %s", err) } ktutil := new(Keytab) err = ktutil.Unmarshal(ktutilbytes) if err != nil { t.Fatalf("Could not load ktutil-generated keytab: %s", err) } // Generate the same keytab with gokrb5 var ts time.Time = ktutil.Entries[0].Timestamp var encTypes []int32 = []int32{ etypeID.AES256_CTS_HMAC_SHA1_96, etypeID.AES128_CTS_HMAC_SHA1_96, etypeID.RC4_HMAC, } kt := New() for _, et := range encTypes { err = kt.AddEntry("user", "EXAMPLE.ORG", "hello123", ts, uint8(31), et) if err != nil { t.Errorf("Error adding entry to keytab: %s", err) } } generated, err := kt.Marshal() if err != nil { t.Errorf("Error marshalling generated keytab: %s", err) } // Compare content assert.Equal(t, generated, ktutilbytes, "Service keytab doesn't match ktutil keytab") } func TestKeytabEntriesService(t *testing.T) { // Load known-good keytab generated with ktutil ktutilb64 := "BQIAAABXAAIAC0VYQU1QTEUuT1JHAARIVFRQAA93d3cuZXhhbXBsZS5vcmcAAAABXl49ggoAEgAgOCSpM5CdiZQn1+rUtLtt6sTrg5Saw1DXJMai7vDWJ0QAAAAKAAAARwACAAtFWEFNUExFLk9SRwAESFRUUAAPd3d3LmV4YW1wbGUub3JnAAAAAV5ePYIKABEAEDpczoDyER1jscz0RWkThCMAAAAKAAAARwACAAtFWEFNUExFLk9SRwAESFRUUAAPd3d3LmV4YW1wbGUub3JnAAAAAV5ePYIKABcAELP27YfH0Th5rD+GtJkQmXQAAAAK" ktutilbytes, err := base64.StdEncoding.DecodeString(ktutilb64) if err != nil { t.Errorf("Could not parse b64 ktutil keytab: %s", err) } ktutil := new(Keytab) err = ktutil.Unmarshal(ktutilbytes) if err != nil { t.Errorf("Could not load ktutil-generated keytab: %s", err) } // Generate the same keytab with gokrb5 var ts time.Time = ktutil.Entries[0].Timestamp var encTypes []int32 = []int32{ etypeID.AES256_CTS_HMAC_SHA1_96, etypeID.AES128_CTS_HMAC_SHA1_96, etypeID.RC4_HMAC, } kt := New() for _, et := range encTypes { err = kt.AddEntry("HTTP/www.example.org", "EXAMPLE.ORG", "hello456", ts, uint8(10), et) if err != nil { t.Errorf("Error adding entry to keytab: %s", err) } } generated, err := kt.Marshal() if err != nil { t.Errorf("Error marshalling generated keytab: %s", err) } // Compare content assert.Equal(t, generated, ktutilbytes, "Service keytab doesn't match ktutil keytab") } func TestKeytab_GetEncryptionKey(t *testing.T) { princ := "HTTP/princ.test.gokrb5" realm := "TEST.GOKRB5" kt := New() kt.AddEntry(princ, realm, "abcdefg", time.Unix(100, 0), 1, 18) kt.AddEntry(princ, realm, "abcdefg", time.Unix(200, 0), 2, 18) kt.AddEntry(princ, realm, "abcdefg", time.Unix(300, 0), 3, 18) kt.AddEntry(princ, realm, "abcdefg", time.Unix(400, 0), 4, 18) kt.AddEntry(princ, realm, "abcdefg", time.Unix(350, 0), 5, 18) kt.AddEntry("HTTP/other.test.gokrb5", realm, "abcdefg", time.Unix(500, 0), 5, 18) pn := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, princ) _, kvno, err := kt.GetEncryptionKey(pn, realm, 0, 18) if err != nil { t.Error(err) } assert.Equal(t, 4, kvno) _, kvno, err = kt.GetEncryptionKey(pn, realm, 3, 18) if err != nil { t.Error(err) } assert.Equal(t, 3, kvno) } gokrb5-8.4.3/v8/krberror/000077500000000000000000000000001427031340300151015ustar00rootroot00000000000000gokrb5-8.4.3/v8/krberror/error.go000066400000000000000000000032101427031340300165550ustar00rootroot00000000000000// Package krberror provides error type and functions for gokrb5. package krberror import ( "fmt" "strings" ) // Error type descriptions. const ( separator = " < " EncodingError = "Encoding_Error" NetworkingError = "Networking_Error" DecryptingError = "Decrypting_Error" EncryptingError = "Encrypting_Error" ChksumError = "Checksum_Error" KRBMsgError = "KRBMessage_Handling_Error" ConfigError = "Configuration_Error" KDCError = "KDC_Error" ) // Krberror is an error type for gokrb5 type Krberror struct { RootCause string EText []string } // Error function to implement the error interface. func (e Krberror) Error() string { return fmt.Sprintf("[Root cause: %s] ", e.RootCause) + strings.Join(e.EText, separator) } // Add another error statement to the error. func (e *Krberror) Add(et string, s string) { e.EText = append([]string{fmt.Sprintf("%s: %s", et, s)}, e.EText...) } // New creates a new instance of Krberror. func New(et, s string) Krberror { return Krberror{ RootCause: et, EText: []string{s}, } } // Errorf appends to or creates a new Krberror. func Errorf(err error, et, format string, a ...interface{}) Krberror { if e, ok := err.(Krberror); ok { e.Add(et, fmt.Sprintf(format, a...)) return e } return NewErrorf(et, format+": %s", append(a, err)...) } // NewErrorf creates a new Krberror from a formatted string. func NewErrorf(et, format string, a ...interface{}) Krberror { var s string if len(a) > 0 { s = fmt.Sprintf("%s: %s", et, fmt.Sprintf(format, a...)) } else { s = fmt.Sprintf("%s: %s", et, format) } return Krberror{ RootCause: et, EText: []string{s}, } } gokrb5-8.4.3/v8/krberror/error_test.go000066400000000000000000000014221427031340300176170ustar00rootroot00000000000000package krberror import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestErrorf(t *testing.T) { err := fmt.Errorf("an error") var a Krberror a = Errorf(err, "cause", "some text") assert.Equal(t, "[Root cause: cause] cause: some text: an error", a.Error()) a = Errorf(err, "cause", "arg1=%d arg2=%s", 123, "arg") assert.Equal(t, "[Root cause: cause] cause: arg1=123 arg2=arg: an error", a.Error()) err = NewErrorf("another error", "some text") a = Errorf(err, "cause", "some text") assert.Equal(t, "[Root cause: another error] cause: some text < another error: some text", a.Error()) a = Errorf(err, "cause", "arg1=%d arg2=%s", 123, "arg") assert.Equal(t, "[Root cause: another error] cause: arg1=123 arg2=arg < another error: some text", a.Error()) } gokrb5-8.4.3/v8/messages/000077500000000000000000000000001427031340300150605ustar00rootroot00000000000000gokrb5-8.4.3/v8/messages/APRep.go000066400000000000000000000032771427031340300163670ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) // APRep implements RFC 4120 KRB_AP_REP: https://tools.ietf.org/html/rfc4120#section-5.5.2. type APRep struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` EncPart types.EncryptedData `asn1:"explicit,tag:2"` } // EncAPRepPart is the encrypted part of KRB_AP_REP. type EncAPRepPart struct { CTime time.Time `asn1:"generalized,explicit,tag:0"` Cusec int `asn1:"explicit,tag:1"` Subkey types.EncryptionKey `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` } // Unmarshal bytes b into the APRep struct. func (a *APRep) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.APREP)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_AP_REP if a.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_AP_REP. Expected: %v; Actual: %v", expectedMsgType, a.MsgType) } return nil } // Unmarshal bytes b into the APRep encrypted part struct. func (a *EncAPRepPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncAPRepPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "AP_REP unmarshal error") } return nil } gokrb5-8.4.3/v8/messages/APRep_test.go000066400000000000000000000043761427031340300174270ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalAPRep(t *testing.T) { t.Parallel() var a APRep b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AP_REP, a.MsgType, "MsgType is not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Ticket encPart etype not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Ticket encPart KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "Ticket encPart cipher not as expected") } func TestUnmarshalEncAPRepPart(t *testing.T) { t.Parallel() var a EncAPRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep_enc_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, tt, a.CTime, "CTime not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, int32(1), a.Subkey.KeyType, "Subkey type not as expected") assert.Equal(t, []byte("12345678"), a.Subkey.KeyValue, "Subkey value not as expected") assert.Equal(t, int64(17), a.SequenceNumber, "Sequence number not as expected") } func TestUnmarshalEncAPRepPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncAPRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep_enc_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, tt, a.CTime, "CTime not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") } gokrb5-8.4.3/v8/messages/APReq.go000066400000000000000000000170161427031340300163640ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) type marshalAPReq struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` APOptions asn1.BitString `asn1:"explicit,tag:2"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag Ticket asn1.RawValue `asn1:"explicit,tag:3"` EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"` } // APReq implements RFC 4120 KRB_AP_REQ: https://tools.ietf.org/html/rfc4120#section-5.5.1. type APReq struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` APOptions asn1.BitString `asn1:"explicit,tag:2"` Ticket Ticket `asn1:"explicit,tag:3"` EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"` Authenticator types.Authenticator `asn1:"optional"` } // NewAPReq generates a new KRB_AP_REQ struct. func NewAPReq(tkt Ticket, sessionKey types.EncryptionKey, auth types.Authenticator) (APReq, error) { var a APReq ed, err := encryptAuthenticator(auth, sessionKey, tkt) if err != nil { return a, krberror.Errorf(err, krberror.KRBMsgError, "error creating Authenticator for AP_REQ") } a = APReq{ PVNO: iana.PVNO, MsgType: msgtype.KRB_AP_REQ, APOptions: types.NewKrbFlags(), Ticket: tkt, EncryptedAuthenticator: ed, } return a, nil } // Encrypt Authenticator func encryptAuthenticator(a types.Authenticator, sessionKey types.EncryptionKey, tkt Ticket) (types.EncryptedData, error) { var ed types.EncryptedData m, err := a.Marshal() if err != nil { return ed, krberror.Errorf(err, krberror.EncodingError, "marshaling error of EncryptedData form of Authenticator") } usage := authenticatorKeyUsage(tkt.SName) ed, err = crypto.GetEncryptedData(m, sessionKey, uint32(usage), tkt.EncPart.KVNO) if err != nil { return ed, krberror.Errorf(err, krberror.EncryptingError, "error encrypting Authenticator") } return ed, nil } // DecryptAuthenticator decrypts the Authenticator within the AP_REQ. // sessionKey may simply be the key within the decrypted EncPart of the ticket within the AP_REQ. func (a *APReq) DecryptAuthenticator(sessionKey types.EncryptionKey) error { usage := authenticatorKeyUsage(a.Ticket.SName) ab, e := crypto.DecryptEncPart(a.EncryptedAuthenticator, sessionKey, uint32(usage)) if e != nil { return fmt.Errorf("error decrypting authenticator: %v", e) } err := a.Authenticator.Unmarshal(ab) if err != nil { return fmt.Errorf("error unmarshaling authenticator: %v", err) } return nil } func authenticatorKeyUsage(pn types.PrincipalName) int { if pn.NameString[0] == "krbtgt" { return keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR } return keyusage.AP_REQ_AUTHENTICATOR } // Unmarshal bytes b into the APReq struct. func (a *APReq) Unmarshal(b []byte) error { var m marshalAPReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.APREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "unmarshal error of AP_REQ") } if m.MsgType != msgtype.KRB_AP_REQ { return NewKRBError(types.PrincipalName{}, "", errorcode.KRB_AP_ERR_MSG_TYPE, errorcode.Lookup(errorcode.KRB_AP_ERR_MSG_TYPE)) } a.PVNO = m.PVNO a.MsgType = m.MsgType a.APOptions = m.APOptions a.EncryptedAuthenticator = m.EncryptedAuthenticator a.Ticket, err = unmarshalTicket(m.Ticket.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "unmarshaling error of Ticket within AP_REQ") } return nil } // Marshal APReq struct. func (a *APReq) Marshal() ([]byte, error) { m := marshalAPReq{ PVNO: a.PVNO, MsgType: a.MsgType, APOptions: a.APOptions, EncryptedAuthenticator: a.EncryptedAuthenticator, } var b []byte b, err := a.Ticket.Marshal() if err != nil { return b, err } m.Ticket = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 3, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AP_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.APREQ) return mk, nil } // Verify an AP_REQ using service's keytab, spn and max acceptable clock skew duration. // The service ticket encrypted part and authenticator will be decrypted as part of this operation. func (a *APReq) Verify(kt *keytab.Keytab, d time.Duration, cAddr types.HostAddress, snameOverride *types.PrincipalName) (bool, error) { // Decrypt ticket's encrypted part with service key //TODO decrypt with service's session key from its TGT is use-to-user. Need to figure out how to get TGT. //if types.IsFlagSet(&a.APOptions, flags.APOptionUseSessionKey) { // err := a.Ticket.Decrypt(tgt.DecryptedEncPart.Key) // if err != nil { // return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of ticket provided using session key") // } //} else { // err := a.Ticket.DecryptEncPart(*kt, &a.Ticket.SName) // if err != nil { // return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided") // } //} sname := &a.Ticket.SName if snameOverride != nil { sname = snameOverride } err := a.Ticket.DecryptEncPart(kt, sname) if err != nil { return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided") } // Check time validity of ticket ok, err := a.Ticket.Valid(d) if err != nil || !ok { return ok, err } // Check client's address is listed in the client addresses in the ticket if len(a.Ticket.DecryptedEncPart.CAddr) > 0 { //If client addresses are present check if any of them match the source IP that sent the APReq //If there is no match return KRB_AP_ERR_BADADDR error. if !types.HostAddressesContains(a.Ticket.DecryptedEncPart.CAddr, cAddr) { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "client address not within the list contained in the service ticket") } } // Decrypt authenticator with session key from ticket's encrypted part err = a.DecryptAuthenticator(a.Ticket.DecryptedEncPart.Key) if err != nil { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BAD_INTEGRITY, "could not decrypt authenticator") } // Check CName in authenticator is the same as that in the ticket if !a.Authenticator.CName.Equal(a.Ticket.DecryptedEncPart.CName) { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket") } // Check the clock skew between the client and the service server ct := a.Authenticator.CTime.Add(time.Duration(a.Authenticator.Cusec) * time.Microsecond) t := time.Now().UTC() if t.Sub(ct) > d || ct.Sub(t) > d { return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("clock skew with client too large. greater than %v seconds", d)) } return true, nil } gokrb5-8.4.3/v8/messages/APReq_test.go000066400000000000000000000040671427031340300174250ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalAPReq(t *testing.T) { t.Parallel() var a APReq b, err := hex.DecodeString(testdata.MarshaledKRB5ap_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AP_REQ, a.MsgType, "MsgType is not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.APOptions.Bytes), "AP Options not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "Ticket VNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket SName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "Ticket SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName name string entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Ticket encPart etype not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encPart KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Ticket.EncPart.Cipher, "Ticket encPart cipher not as expected") } func TestMarshalAPReq(t *testing.T) { t.Parallel() var a APReq b, err := hex.DecodeString(testdata.MarshaledKRB5ap_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of Authenticator not as expected") } gokrb5-8.4.3/v8/messages/KDCRep.go000066400000000000000000000350751427031340300164710ustar00rootroot00000000000000package messages // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.4.2 import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) type marshalKDCRep struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` PAData types.PADataSequence `asn1:"explicit,optional,tag:2"` CRealm string `asn1:"generalstring,explicit,tag:3"` CName types.PrincipalName `asn1:"explicit,tag:4"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag Ticket asn1.RawValue `asn1:"explicit,tag:5"` EncPart types.EncryptedData `asn1:"explicit,tag:6"` } // KDCRepFields represents the KRB_KDC_REP fields. type KDCRepFields struct { PVNO int MsgType int PAData []types.PAData CRealm string CName types.PrincipalName Ticket Ticket EncPart types.EncryptedData DecryptedEncPart EncKDCRepPart } // ASRep implements RFC 4120 KRB_AS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2. type ASRep struct { KDCRepFields } // TGSRep implements RFC 4120 KRB_TGS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2. type TGSRep struct { KDCRepFields } // EncKDCRepPart is the encrypted part of KRB_KDC_REP. type EncKDCRepPart struct { Key types.EncryptionKey `asn1:"explicit,tag:0"` LastReqs []LastReq `asn1:"explicit,tag:1"` Nonce int `asn1:"explicit,tag:2"` KeyExpiration time.Time `asn1:"generalized,explicit,optional,tag:3"` Flags asn1.BitString `asn1:"explicit,tag:4"` AuthTime time.Time `asn1:"generalized,explicit,tag:5"` StartTime time.Time `asn1:"generalized,explicit,optional,tag:6"` EndTime time.Time `asn1:"generalized,explicit,tag:7"` RenewTill time.Time `asn1:"generalized,explicit,optional,tag:8"` SRealm string `asn1:"generalstring,explicit,tag:9"` SName types.PrincipalName `asn1:"explicit,tag:10"` CAddr []types.HostAddress `asn1:"explicit,optional,tag:11"` EncPAData types.PADataSequence `asn1:"explicit,optional,tag:12"` } // LastReq part of KRB_KDC_REP. type LastReq struct { LRType int32 `asn1:"explicit,tag:0"` LRValue time.Time `asn1:"generalized,explicit,tag:1"` } // Unmarshal bytes b into the ASRep struct. func (k *ASRep) Unmarshal(b []byte) error { var m marshalKDCRep _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREP)) if err != nil { return processUnmarshalReplyError(b, err) } if m.MsgType != msgtype.KRB_AS_REP { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an AS_REP. Expected: %v; Actual: %v", msgtype.KRB_AS_REP, m.MsgType) } //Process the raw ticket within tkt, err := unmarshalTicket(m.Ticket.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within AS_REP") } k.KDCRepFields = KDCRepFields{ PVNO: m.PVNO, MsgType: m.MsgType, PAData: m.PAData, CRealm: m.CRealm, CName: m.CName, Ticket: tkt, EncPart: m.EncPart, } return nil } // Marshal ASRep struct. func (k *ASRep) Marshal() ([]byte, error) { m := marshalKDCRep{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, CRealm: k.CRealm, CName: k.CName, EncPart: k.EncPart, } b, err := k.Ticket.Marshal() if err != nil { return []byte{}, err } m.Ticket = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 5, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REP") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREP) return mk, nil } // Unmarshal bytes b into the TGSRep struct. func (k *TGSRep) Unmarshal(b []byte) error { var m marshalKDCRep _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREP)) if err != nil { return processUnmarshalReplyError(b, err) } if m.MsgType != msgtype.KRB_TGS_REP { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an TGS_REP. Expected: %v; Actual: %v", msgtype.KRB_TGS_REP, m.MsgType) } //Process the raw ticket within tkt, err := unmarshalTicket(m.Ticket.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within TGS_REP") } k.KDCRepFields = KDCRepFields{ PVNO: m.PVNO, MsgType: m.MsgType, PAData: m.PAData, CRealm: m.CRealm, CName: m.CName, Ticket: tkt, EncPart: m.EncPart, } return nil } // Marshal TGSRep struct. func (k *TGSRep) Marshal() ([]byte, error) { m := marshalKDCRep{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, CRealm: k.CRealm, CName: k.CName, EncPart: k.EncPart, } b, err := k.Ticket.Marshal() if err != nil { return []byte{}, err } m.Ticket = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 5, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REP") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREP) return mk, nil } // Unmarshal bytes b into encrypted part of KRB_KDC_REP. func (e *EncKDCRepPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncASRepPart)) if err != nil { // Try using tag 26 // Ref: RFC 4120 - mentions that some implementations use application tag number 26 wether or not the reply is // a AS-REP or a TGS-REP. _, err = asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncTGSRepPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part within KDC_REP") } } return nil } // Marshal encrypted part of KRB_KDC_REP. func (e *EncKDCRepPart) Marshal() ([]byte, error) { b, err := asn1.Marshal(*e) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AS_REP encpart") } b = asn1tools.AddASNAppTag(b, asnAppTag.EncASRepPart) return b, nil } // DecryptEncPart decrypts the encrypted part of an AS_REP. func (k *ASRep) DecryptEncPart(c *credentials.Credentials) (types.EncryptionKey, error) { var key types.EncryptionKey var err error if c.HasKeytab() { key, _, err = c.Keytab().GetEncryptionKey(k.CName, k.CRealm, k.EncPart.KVNO, k.EncPart.EType) if err != nil { return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part") } } if c.HasPassword() { key, _, err = crypto.GetKeyFromPassword(c.Password(), k.CName, k.CRealm, k.EncPart.EType, k.PAData) if err != nil { return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part") } } if !c.HasKeytab() && !c.HasPassword() { return key, krberror.NewErrorf(krberror.DecryptingError, "no secret available in credentials to perform decryption of AS_REP encrypted part") } b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.AS_REP_ENCPART) if err != nil { return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part") } var denc EncKDCRepPart err = denc.Unmarshal(b) if err != nil { return key, krberror.Errorf(err, krberror.EncodingError, "error unmarshaling decrypted encpart of AS_REP") } k.DecryptedEncPart = denc return key, nil } // Verify checks the validity of AS_REP message. func (k *ASRep) Verify(cfg *config.Config, creds *credentials.Credentials, asReq ASReq) (bool, error) { //Ref RFC 4120 Section 3.1.5 if !k.CName.Equal(asReq.ReqBody.CName) { return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName) } if k.CRealm != asReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "CRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.CRealm) } key, err := k.DecryptEncPart(creds) if err != nil { return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting EncPart of AS_REP") } if k.DecryptedEncPart.Nonce != asReq.ReqBody.Nonce { return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request") } if !k.DecryptedEncPart.SName.Equal(asReq.ReqBody.SName) { return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", asReq.ReqBody.SName, k.DecryptedEncPart.SName) } if k.DecryptedEncPart.SRealm != asReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.DecryptedEncPart.SRealm) } if len(asReq.ReqBody.Addresses) > 0 { if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, asReq.ReqBody.Addresses) { return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the AS_REP does not match those listed in the AS_REQ") } } t := time.Now().UTC() if t.Sub(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(t) > cfg.LibDefaults.Clockskew { return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds()) } // RFC 6806 https://tools.ietf.org/html/rfc6806.html#section-11 if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) && types.IsFlagSet(&k.DecryptedEncPart.Flags, flags.EncPARep) { if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) { return false, krberror.NewErrorf(krberror.KRBMsgError, "KDC did not respond appropriately to FAST negotiation") } for _, pa := range k.DecryptedEncPart.EncPAData { if pa.PADataType == patype.PA_REQ_ENC_PA_REP { var pafast types.PAReqEncPARep err := pafast.Unmarshal(pa.PADataValue) if err != nil { return false, krberror.Errorf(err, krberror.EncodingError, "KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP") } etype, err := crypto.GetChksumEtype(pafast.ChksumType) if err != nil { return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response error") } ab, _ := asReq.Marshal() if !etype.VerifyChecksum(key.KeyValue, ab, pafast.Chksum, keyusage.KEY_USAGE_AS_REQ) { return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response checksum invalid") } } } } return true, nil } // DecryptEncPart decrypts the encrypted part of an TGS_REP. func (k *TGSRep) DecryptEncPart(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.TGS_REP_ENCPART_SESSION_KEY) if err != nil { return krberror.Errorf(err, krberror.DecryptingError, "error decrypting TGS_REP EncPart") } var denc EncKDCRepPart err = denc.Unmarshal(b) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part") } k.DecryptedEncPart = denc return nil } // Verify checks the validity of the TGS_REP message. func (k *TGSRep) Verify(cfg *config.Config, tgsReq TGSReq) (bool, error) { if !k.CName.Equal(tgsReq.ReqBody.CName) { return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.CName, k.CName) } if k.Ticket.Realm != tgsReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "realm in response ticket does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.Ticket.Realm) } if k.DecryptedEncPart.Nonce != tgsReq.ReqBody.Nonce { return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request") } //if k.Ticket.SName.NameType != tgsReq.ReqBody.SName.NameType || k.Ticket.SName.NameString == nil { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.Ticket.SName) //} //for i := range k.Ticket.SName.NameString { // if k.Ticket.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.Ticket.SName) // } //} //if k.DecryptedEncPart.SName.NameType != tgsReq.ReqBody.SName.NameType || k.DecryptedEncPart.SName.NameString == nil { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName) //} //for i := range k.DecryptedEncPart.SName.NameString { // if k.DecryptedEncPart.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] { // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName) // } //} if k.DecryptedEncPart.SRealm != tgsReq.ReqBody.Realm { return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.DecryptedEncPart.SRealm) } if len(k.DecryptedEncPart.CAddr) > 0 { if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, tgsReq.ReqBody.Addresses) { return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the TGS_REP does not match those listed in the TGS_REQ") } } if time.Since(k.DecryptedEncPart.StartTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.StartTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew { if time.Since(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew { return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds.", cfg.LibDefaults.Clockskew.Seconds()) } } return true, nil } gokrb5-8.4.3/v8/messages/KDCRep_test.go000066400000000000000000000526211427031340300175240ustar00rootroot00000000000000package messages import ( "encoding/hex" "fmt" "testing" "time" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/etypeID" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) const ( testuser1EType18Keytab = "05020000004b0001000b544553542e474f4b5242350009746573747573657231000000015898e0770100120020bbdc430aab7e2d4622a0b6951481453b0962e9db8e2f168942ad175cda6d9de900000001" testuser1EType18ASREP = "6b8202f3308202efa003020105a10302010ba22e302c302aa103020113a2230421301f301da003020112a1161b14544553542e474f4b524235746573747573657231a30d1b0b544553542e474f4b524235a4163014a003020101a10d300b1b09746573747573657231a582015a6182015630820152a003020105a10d1b0b544553542e474f4b524235a220301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a382011830820114a003020112a103020101a28201060482010237e486e32cd18ab1ac9f8d42e93f8babd7b3497084cc5599f18ec61961c6d5242d350354d99d67a7604c451116188d16cb719e84377212eac2743440e8c504ef69c755e489cc6b65f935dd032bfc076f9b2c56d816197845b8fe857d738bc59712787631a50e86833d1b0e4732c8712c856417a6a257758e7d01d3182adb3233f0dde65d228c240ed26aa1af69f8d765dc0bc69096fdb037a75af220fea176839528d44b70f7dabfaa2ea506de1296f847176a60c501fd8cef8e0a51399bb6d5f753962d96292e93ffe344c6630db912931d46d88c0279f00719e22d0efcfd4ee33a702d0b660c1f13970a9beec12c0c8af3dda68bd81ac1fe3f126d2a24ebb445c5a682012c30820128a003020112a282011f0482011bb149cc16018072c4c18788d95a33aba540e52c11b54a93e67e788d05de75d8f3d4aa1afafbbfa6fde3eb40e5aa1890644cea2607efd5213a3fd00345b02eeb9ae1b589f36c74c689cd4ec1239dfe61e42ba6afa33f6240e3cfab291e4abb465d273302dbf7dbd148a299a9369044dd03377c1687e7dd36aa66501284a4ca50c0a7b08f4f87aecfa23b0dd0b11490e3ad330906dab715de81fc52f120d09c39990b8b5330d4601cc396b2ed258834329c4cc02c563a12de3ef9bf11e946258bc2ab5257f4caa4d443a7daf0fc25f6f531c2fcba88af8ca55c85300997cd05abbea52811fe2d038ba8f62fc8e3bc71ce04362d356ea2e1df8ac55c784c53cfb07817d48e39fe99fc8788040d98209c79dcf044d97e80de9f47824646" testRealm = "TEST.GOKRB5" testUser = "testuser1" testUserPassword = "passwordvalue" ) func TestUnmarshalASRep(t *testing.T) { t.Parallel() var a ASRep b, err := hex.DecodeString(testdata.MarshaledKRB5as_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestUnmarshalASRep_optionalsNULL(t *testing.T) { t.Parallel() var a ASRep b, err := hex.DecodeString(testdata.MarshaledKRB5as_repOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestMarshalASRep(t *testing.T) { t.Parallel() var a ASRep b, err := hex.DecodeString(testdata.MarshaledKRB5as_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of ASRep not as expected") } func TestUnmarshalTGSRep(t *testing.T) { t.Parallel() var a TGSRep b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestUnmarshalTGSRep_optionalsNULL(t *testing.T) { t.Parallel() var a TGSRep b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_repOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REP, a.MsgType, "MsgType not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "Client Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, iana.PVNO, a.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testdata.TEST_REALM, a.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.Ticket.SName.NameString), "SName in ticket does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Ticket.SName.NameString, "Ticket SName entries not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, iana.PVNO, a.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.Ticket.EncPart.Cipher), "Ticket encrypted part cipher not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "Encrypted part KVNO not as expected") assert.Equal(t, testdata.TEST_CIPHERTEXT, string(a.EncPart.Cipher), "Ticket encrypted part cipher not as expected") } func TestMarshalTGSRep(t *testing.T) { t.Parallel() var a TGSRep b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_rep) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of TGSRep not as expected") } func TestUnmarshalEncKDCRepPart(t *testing.T) { t.Parallel() var a EncKDCRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_kdc_rep_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, 2, len(a.LastReqs), "Number of last request entries not as expected") for i, r := range a.LastReqs { assert.Equal(t, int32(-5), r.LRType, fmt.Sprintf("Last request typ not as expected for last request entry %d", i+1)) assert.Equal(t, tt, r.LRValue, fmt.Sprintf("Last request time value not as expected for last request entry %d", i+1)) } assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Nonce not as expected") assert.Equal(t, tt, a.KeyExpiration, "key expiration time not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.StartTime, "Start time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") assert.Equal(t, tt, a.RenewTill, "Renew Till time not as expected") assert.Equal(t, testdata.TEST_REALM, a.SRealm, "SRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "SName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "SName string entries not as expected") assert.Equal(t, 2, len(a.CAddr), "Number of client addresses not as expected") for i, addr := range a.CAddr { assert.Equal(t, int32(2), addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } } func TestUnmarshalEncKDCRepPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncKDCRepPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_kdc_rep_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, 2, len(a.LastReqs), "Number of last request entries not as expected") for i, r := range a.LastReqs { assert.Equal(t, int32(-5), r.LRType, fmt.Sprintf("Last request typ not as expected for last request entry %d", i+1)) assert.Equal(t, tt, r.LRValue, fmt.Sprintf("Last request time value not as expected for last request entry %d", i+1)) } assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Nonce not as expected") assert.Equal(t, "fe5cba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") assert.Equal(t, testdata.TEST_REALM, a.SRealm, "SRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "SName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "SName string entries not as expected") } func TestUnmarshalASRepDecodeAndDecrypt(t *testing.T) { t.Parallel() var asRep ASRep b, _ := hex.DecodeString(testuser1EType18ASREP) err := asRep.Unmarshal(b) if err != nil { t.Fatalf("AS REP Unmarshal error: %v\n", err) } assert.Equal(t, 5, asRep.PVNO, "PVNO not as expected") assert.Equal(t, 11, asRep.MsgType, "MsgType not as expected") assert.Equal(t, testRealm, asRep.CRealm, "Client Realm not as expected") assert.Equal(t, int32(1), asRep.CName.NameType, "CName NameType not as expected") assert.Equal(t, testUser, asRep.CName.NameString[0], "CName NameType not as expected") assert.Equal(t, int32(19), asRep.PAData[0].PADataType, "PADataType not as expected") assert.Equal(t, 5, asRep.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testRealm, asRep.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, int32(2), asRep.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, "krbtgt", asRep.Ticket.SName.NameString[0], "Ticket service name string not as expected") assert.Equal(t, testRealm, asRep.Ticket.SName.NameString[1], "Ticket service name string not as expected") assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], asRep.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, 1, asRep.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], asRep.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, 0, asRep.EncPart.KVNO, "Encrypted part KVNO not as expected") //t.Log("Finished testing unecrypted parts of AS REP") ktb, _ := hex.DecodeString(testuser1EType18Keytab) kt := keytab.New() err = kt.Unmarshal(ktb) if err != nil { t.Fatalf("keytab parse error: %v\n", err) } cred := credentials.New(testUser, testRealm) _, err = asRep.DecryptEncPart(cred.WithKeytab(kt)) if err != nil { t.Fatalf("Decryption of AS_REP EncPart failed: %v", err) } assert.Equal(t, int32(18), asRep.DecryptedEncPart.Key.KeyType, "KeyType in decrypted EncPart not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.LastReqs[0].LRValue, "LastReqs did not have a time value") assert.Equal(t, 2069991465, asRep.DecryptedEncPart.Nonce, "Nonce value not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.KeyExpiration, "Key expiration not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.AuthTime, "AuthTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.StartTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.EndTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.RenewTill, "RenewTill not a time type") assert.Equal(t, testRealm, asRep.DecryptedEncPart.SRealm, "Service realm not as expected") assert.Equal(t, int32(2), asRep.DecryptedEncPart.SName.NameType, "Name type for AS_REP not as expected") assert.Equal(t, []string{"krbtgt", testRealm}, asRep.DecryptedEncPart.SName.NameString, "Service name string not as expected") } func TestUnmarshalASRepDecodeAndDecrypt_withPassword(t *testing.T) { t.Parallel() var asRep ASRep b, _ := hex.DecodeString(testuser1EType18ASREP) err := asRep.Unmarshal(b) if err != nil { t.Fatalf("AS REP Unmarshal error: %v\n", err) } assert.Equal(t, 5, asRep.PVNO, "PVNO not as expected") assert.Equal(t, 11, asRep.MsgType, "MsgType not as expected") assert.Equal(t, testRealm, asRep.CRealm, "Client Realm not as expected") assert.Equal(t, int32(1), asRep.CName.NameType, "CName NameType not as expected") assert.Equal(t, testUser, asRep.CName.NameString[0], "CName NameType not as expected") assert.Equal(t, int32(19), asRep.PAData[0].PADataType, "PADataType not as expected") assert.Equal(t, 5, asRep.Ticket.TktVNO, "TktVNO not as expected") assert.Equal(t, testRealm, asRep.Ticket.Realm, "Ticket Realm not as expected") assert.Equal(t, int32(2), asRep.Ticket.SName.NameType, "Ticket service nametype not as expected") assert.Equal(t, "krbtgt", asRep.Ticket.SName.NameString[0], "Ticket service name string not as expected") assert.Equal(t, testRealm, asRep.Ticket.SName.NameString[1], "Ticket service name string not as expected") assert.Equal(t, etypeID.AES256_CTS_HMAC_SHA1_96, asRep.Ticket.EncPart.EType, "Etype of ticket encrypted part not as expected") assert.Equal(t, 1, asRep.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected") assert.Equal(t, etypeID.AES256_CTS_HMAC_SHA1_96, asRep.EncPart.EType, "Etype of encrypted part not as expected") assert.Equal(t, 0, asRep.EncPart.KVNO, "Encrypted part KVNO not as expected") cred := credentials.New(testUser, testRealm) _, err = asRep.DecryptEncPart(cred.WithPassword(testUserPassword)) if err != nil { t.Fatalf("Decryption of AS_REP EncPart failed: %v", err) } assert.Equal(t, int32(18), asRep.DecryptedEncPart.Key.KeyType, "KeyType in decrypted EncPart not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.LastReqs[0].LRValue, "LastReqs did not have a time value") assert.Equal(t, 2069991465, asRep.DecryptedEncPart.Nonce, "Nonce value not as expected") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.KeyExpiration, "Key expiration not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.AuthTime, "AuthTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.StartTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.EndTime, "StartTime not a time type") assert.IsType(t, time.Time{}, asRep.DecryptedEncPart.RenewTill, "RenewTill not a time type") assert.Equal(t, testRealm, asRep.DecryptedEncPart.SRealm, "Service realm not as expected") assert.Equal(t, nametype.KRB_NT_SRV_INST, asRep.DecryptedEncPart.SName.NameType, "Name type for AS_REP not as expected") assert.Equal(t, []string{"krbtgt", testRealm}, asRep.DecryptedEncPart.SName.NameString, "Service name string not as expected") } gokrb5-8.4.3/v8/messages/KDCReq.go000066400000000000000000000336461427031340300164740ustar00rootroot00000000000000package messages // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.4.1 import ( "crypto/rand" "fmt" "math" "math/big" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) type marshalKDCReq struct { PVNO int `asn1:"explicit,tag:1"` MsgType int `asn1:"explicit,tag:2"` PAData types.PADataSequence `asn1:"explicit,optional,tag:3"` ReqBody asn1.RawValue `asn1:"explicit,tag:4"` } // KDCReqFields represents the KRB_KDC_REQ fields. type KDCReqFields struct { PVNO int MsgType int PAData types.PADataSequence ReqBody KDCReqBody Renewal bool } // ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1. type ASReq struct { KDCReqFields } // TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1. type TGSReq struct { KDCReqFields } type marshalKDCReqBody struct { KDCOptions asn1.BitString `asn1:"explicit,tag:0"` CName types.PrincipalName `asn1:"explicit,optional,tag:1"` Realm string `asn1:"generalstring,explicit,tag:2"` SName types.PrincipalName `asn1:"explicit,optional,tag:3"` From time.Time `asn1:"generalized,explicit,optional,tag:4"` Till time.Time `asn1:"generalized,explicit,tag:5"` RTime time.Time `asn1:"generalized,explicit,optional,tag:6"` Nonce int `asn1:"explicit,tag:7"` EType []int32 `asn1:"explicit,tag:8"` Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"` EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"` } // KDCReqBody implements the KRB_KDC_REQ request body. type KDCReqBody struct { KDCOptions asn1.BitString `asn1:"explicit,tag:0"` CName types.PrincipalName `asn1:"explicit,optional,tag:1"` Realm string `asn1:"generalstring,explicit,tag:2"` SName types.PrincipalName `asn1:"explicit,optional,tag:3"` From time.Time `asn1:"generalized,explicit,optional,tag:4"` Till time.Time `asn1:"generalized,explicit,tag:5"` RTime time.Time `asn1:"generalized,explicit,optional,tag:6"` Nonce int `asn1:"explicit,tag:7"` EType []int32 `asn1:"explicit,tag:8"` Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"` EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"` AdditionalTickets []Ticket `asn1:"explicit,optional,tag:11"` } // NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request. func NewASReqForTGT(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) { sname := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } return NewASReq(realm, c, cname, sname) } // NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request. func NewASReqForChgPasswd(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) { sname := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"kadmin", "changepw"}, } return NewASReq(realm, c, cname, sname) } // NewASReq generates a new KRB_AS_REQ struct for a given SNAME. func NewASReq(realm string, c *config.Config, cname, sname types.PrincipalName) (ASReq, error) { nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) if err != nil { return ASReq{}, err } t := time.Now().UTC() // Copy the default options to make this thread safe kopts := types.NewKrbFlags() copy(kopts.Bytes, c.LibDefaults.KDCDefaultOptions.Bytes) kopts.BitLength = c.LibDefaults.KDCDefaultOptions.BitLength a := ASReq{ KDCReqFields{ PVNO: iana.PVNO, MsgType: msgtype.KRB_AS_REQ, PAData: types.PADataSequence{}, ReqBody: KDCReqBody{ KDCOptions: kopts, Realm: realm, CName: cname, SName: sname, Till: t.Add(c.LibDefaults.TicketLifetime), Nonce: int(nonce.Int64()), EType: c.LibDefaults.DefaultTktEnctypeIDs, }, }, } if c.LibDefaults.Forwardable { types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable) } if c.LibDefaults.Canonicalize { types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize) } if c.LibDefaults.Proxiable { types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable) } if c.LibDefaults.RenewLifetime != 0 { types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable) a.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime) a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour) } if !c.LibDefaults.NoAddresses { ha, err := types.LocalHostAddresses() if err != nil { return a, fmt.Errorf("could not get local addresses: %v", err) } ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...) a.ReqBody.Addresses = ha } return a, nil } // NewTGSReq generates a new KRB_TGS_REQ struct. func NewTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, tgt Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool) (TGSReq, error) { a, err := tgsReq(cname, sname, kdcRealm, renewal, c) if err != nil { return a, err } err = a.setPAData(tgt, sessionKey) return a, err } // NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7) func NewUser2UserTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, clientTGT Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool, verifyingTGT Ticket) (TGSReq, error) { a, err := tgsReq(cname, sname, kdcRealm, renewal, c) if err != nil { return a, err } a.ReqBody.AdditionalTickets = []Ticket{verifyingTGT} types.SetFlag(&a.ReqBody.KDCOptions, flags.EncTktInSkey) err = a.setPAData(clientTGT, sessionKey) return a, err } // tgsReq populates the fields for a TGS_REQ func tgsReq(cname, sname types.PrincipalName, kdcRealm string, renewal bool, c *config.Config) (TGSReq, error) { nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) if err != nil { return TGSReq{}, err } t := time.Now().UTC() k := KDCReqFields{ PVNO: iana.PVNO, MsgType: msgtype.KRB_TGS_REQ, ReqBody: KDCReqBody{ KDCOptions: types.NewKrbFlags(), Realm: kdcRealm, CName: cname, // Add the CName to make validation of the reply easier SName: sname, Till: t.Add(c.LibDefaults.TicketLifetime), Nonce: int(nonce.Int64()), EType: c.LibDefaults.DefaultTGSEnctypeIDs, }, Renewal: renewal, } if c.LibDefaults.Forwardable { types.SetFlag(&k.ReqBody.KDCOptions, flags.Forwardable) } if c.LibDefaults.Canonicalize { types.SetFlag(&k.ReqBody.KDCOptions, flags.Canonicalize) } if c.LibDefaults.Proxiable { types.SetFlag(&k.ReqBody.KDCOptions, flags.Proxiable) } if c.LibDefaults.RenewLifetime > time.Duration(0) { types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable) k.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime) } if !c.LibDefaults.NoAddresses { ha, err := types.LocalHostAddresses() if err != nil { return TGSReq{}, fmt.Errorf("could not get local addresses: %v", err) } ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...) k.ReqBody.Addresses = ha } if renewal { types.SetFlag(&k.ReqBody.KDCOptions, flags.Renew) types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable) } return TGSReq{ k, }, nil } func (k *TGSReq) setPAData(tgt Ticket, sessionKey types.EncryptionKey) error { // Marshal the request and calculate checksum b, err := k.ReqBody.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REQ body") } etype, err := crypto.GetEtype(sessionKey.KeyType) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype to encrypt authenticator") } cb, err := etype.GetChecksumHash(sessionKey.KeyValue, b, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM) if err != nil { return krberror.Errorf(err, krberror.ChksumError, "error getting etype checksum hash") } // Form PAData for TGS_REQ // Create authenticator auth, err := types.NewAuthenticator(tgt.Realm, k.ReqBody.CName) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") } auth.Cksum = types.Checksum{ CksumType: etype.GetHashID(), Checksum: cb, } // Create AP_REQ apReq, err := NewAPReq(tgt, sessionKey, auth) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AP_REQ") } apb, err := apReq.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data") } k.PAData = types.PADataSequence{ types.PAData{ PADataType: patype.PA_TGS_REQ, PADataValue: apb, }, } return nil } // Unmarshal bytes b into the ASReq struct. func (k *ASReq) Unmarshal(b []byte) error { var m marshalKDCReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling AS_REQ") } expectedMsgType := msgtype.KRB_AS_REQ if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } var reqb KDCReqBody err = reqb.Unmarshal(m.ReqBody.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error processing AS_REQ body") } k.MsgType = m.MsgType k.PAData = m.PAData k.PVNO = m.PVNO k.ReqBody = reqb return nil } // Unmarshal bytes b into the TGSReq struct. func (k *TGSReq) Unmarshal(b []byte) error { var m marshalKDCReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling TGS_REQ") } expectedMsgType := msgtype.KRB_TGS_REQ if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } var reqb KDCReqBody err = reqb.Unmarshal(m.ReqBody.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error processing TGS_REQ body") } k.MsgType = m.MsgType k.PAData = m.PAData k.PVNO = m.PVNO k.ReqBody = reqb return nil } // Unmarshal bytes b into the KRB_KDC_REQ body struct. func (k *KDCReqBody) Unmarshal(b []byte) error { var m marshalKDCReqBody _, err := asn1.Unmarshal(b, &m) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling KDC_REQ body") } k.KDCOptions = m.KDCOptions if len(k.KDCOptions.Bytes) < 4 { tb := make([]byte, 4-len(k.KDCOptions.Bytes)) k.KDCOptions.Bytes = append(tb, k.KDCOptions.Bytes...) k.KDCOptions.BitLength = len(k.KDCOptions.Bytes) * 8 } k.CName = m.CName k.Realm = m.Realm k.SName = m.SName k.From = m.From k.Till = m.Till k.RTime = m.RTime k.Nonce = m.Nonce k.EType = m.EType k.Addresses = m.Addresses k.EncAuthData = m.EncAuthData if len(m.AdditionalTickets.Bytes) > 0 { k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets") } } return nil } // Marshal ASReq struct. func (k *ASReq) Marshal() ([]byte, error) { m := marshalKDCReq{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, } b, err := k.ReqBody.Marshal() if err != nil { var mk []byte return mk, err } m.ReqBody = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 4, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREQ) return mk, nil } // Marshal TGSReq struct. func (k *TGSReq) Marshal() ([]byte, error) { m := marshalKDCReq{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, } b, err := k.ReqBody.Marshal() if err != nil { var mk []byte return mk, err } m.ReqBody = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 4, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREQ) return mk, nil } // Marshal KRB_KDC_REQ body struct. func (k *KDCReqBody) Marshal() ([]byte, error) { var b []byte m := marshalKDCReqBody{ KDCOptions: k.KDCOptions, CName: k.CName, Realm: k.Realm, SName: k.SName, From: k.From, Till: k.Till, RTime: k.RTime, Nonce: k.Nonce, EType: k.EType, Addresses: k.Addresses, EncAuthData: k.EncAuthData, } rawtkts, err := MarshalTicketSequence(k.AdditionalTickets) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body additional tickets") } //The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody rawtkts.Tag = 11 if len(rawtkts.Bytes) > 0 { m.AdditionalTickets = rawtkts } b, err = asn1.Marshal(m) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body") } return b, nil } gokrb5-8.4.3/v8/messages/KDCReq_test.go000066400000000000000000000653671427031340300175400ustar00rootroot00000000000000package messages import ( "encoding/hex" "fmt" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/addrtype" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalKDCReqBody(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_body) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba90", hex.EncodeToString(a.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "Request body CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "Request body CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "Request body CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.From, "Request body From time not as expected") assert.Equal(t, tt, a.Till, "Request body Till time not as expected") assert.Equal(t, tt, a.RTime, "Request body RTime time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.EType, "Etype list not as expected") assert.Equal(t, 2, len(a.Addresses), "Number of client addresses not as expected") for i, addr := range a.Addresses { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.EncAuthData.EType, "Etype of request body encrypted authorization data not as expected") assert.Equal(t, iana.PVNO, a.EncAuthData.KVNO, "KVNO of request body encrypted authorization data not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncAuthData.Cipher, "Ciphertext of request body encrypted authorization data not as expected") assert.Equal(t, 2, len(a.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalKDCReqBody_optionalsNULLexceptsecond_ticket(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_bodyOptionalsNULLexceptsecond_ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba98", hex.EncodeToString(a.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Request body Realm not as expected") assert.Equal(t, tt, a.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 2, len(a.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalKDCReqBody_optionalsNULLexceptserver(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_bodyOptionalsNULLexceptserver) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba90", hex.EncodeToString(a.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 0, len(a.AdditionalTickets), "Number of additional tickets not empty") } func TestUnmarshalASReq(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.CName.NameType, "Request body CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.CName.NameString), "Request body CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.CName.NameString, "Request body CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.From, "Request body From time not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, tt, a.ReqBody.RTime, "Request body RTime time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 2, len(a.ReqBody.Addresses), "Number of client addresses not as expected") for i, addr := range a.ReqBody.Addresses { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.ReqBody.EncAuthData.EType, "Etype of request body encrypted authorization data not as expected") assert.Equal(t, iana.PVNO, a.ReqBody.EncAuthData.KVNO, "KVNO of request body encrypted authorization data not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.ReqBody.EncAuthData.Cipher, "Ciphertext of request body encrypted authorization data not as expected") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalASReq_optionalsNULLexceptsecond_ticket(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_reqOptionalsNULLexceptsecond_ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalASReq_optionalsNULLexceptserver(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_reqOptionalsNULLexceptserver) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_AS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 0, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not empty") } func TestUnmarshalTGSReq(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 2, len(a.PAData), "Number of PAData items in the sequence not as expected") for i, pa := range a.PAData { assert.Equal(t, patype.PA_SAM_RESPONSE, pa.PADataType, fmt.Sprintf("PAData type for entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), pa.PADataValue, fmt.Sprintf("PAData valye for entry %d not as expected", i+1)) } assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.CName.NameType, "Request body CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.CName.NameString), "Request body CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.CName.NameString, "Request body CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.From, "Request body From time not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, tt, a.ReqBody.RTime, "Request body RTime time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 2, len(a.ReqBody.Addresses), "Number of client addresses not as expected") for i, addr := range a.ReqBody.Addresses { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.ReqBody.EncAuthData.EType, "Etype of request body encrypted authorization data not as expected") assert.Equal(t, iana.PVNO, a.ReqBody.EncAuthData.KVNO, "KVNO of request body encrypted authorization data not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.ReqBody.EncAuthData.Cipher, "Ciphertext of request body encrypted authorization data not as expected") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalTGSReq_optionalsNULLexceptsecond_ticket(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_reqOptionalsNULLexceptsecond_ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba98", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 2, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not as expected") for i, tkt := range a.ReqBody.AdditionalTickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Additional ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Additional ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Additional ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Additional ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Additional ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Additional ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Additional ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Additional ticket (%v) encPart cipher not as expected", i+1)) } } func TestUnmarshalTGSReq_optionalsNULLexceptserver(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_reqOptionalsNULLexceptserver) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_TGS_REQ, a.MsgType, "Message ID not as expected") assert.Equal(t, 0, len(a.PAData), "Number of PAData items in the sequence not as expected") assert.Equal(t, "fedcba90", hex.EncodeToString(a.ReqBody.KDCOptions.Bytes), "Request body flags not as expected") assert.Equal(t, testdata.TEST_REALM, a.ReqBody.Realm, "Request body Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.ReqBody.SName.NameType, "Request body SName nametype not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.ReqBody.SName.NameString), "Request body SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.ReqBody.SName.NameString, "Request body SName entries not as expected") assert.Equal(t, tt, a.ReqBody.Till, "Request body Till time not as expected") assert.Equal(t, testdata.TEST_NONCE, a.ReqBody.Nonce, "Request body nounce not as expected") assert.Equal(t, []int32{0, 1}, a.ReqBody.EType, "Etype list not as expected") assert.Equal(t, 0, len(a.ReqBody.Addresses), "Number of client addresses not empty") assert.Equal(t, 0, len(a.ReqBody.EncAuthData.Cipher), "Ciphertext of request body encrypted authorization data not empty") assert.Equal(t, 0, len(a.ReqBody.AdditionalTickets), "Number of additional tickets not empty") } //// Marshal Tests //// func TestMarshalKDCReqBody(t *testing.T) { t.Parallel() var a KDCReqBody b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_body) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } // Marshal and re-unmarshal the result nd then compare mb, err := a.Marshal() if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of KDCReqBody not as expected") } func TestMarshalASReq(t *testing.T) { t.Parallel() var a ASReq b, err := hex.DecodeString(testdata.MarshaledKRB5as_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of ASReq not as expected") } func TestMarshalTGSReq(t *testing.T) { t.Parallel() var a TGSReq b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_req) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of TGSReq not as expected") } gokrb5-8.4.3/v8/messages/KRBCred.go000066400000000000000000000074101427031340300166250ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) type marshalKRBCred struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` Tickets asn1.RawValue `asn1:"explicit,tag:2"` EncPart types.EncryptedData `asn1:"explicit,tag:3"` } // KRBCred implements RFC 4120 KRB_CRED: https://tools.ietf.org/html/rfc4120#section-5.8.1. type KRBCred struct { PVNO int MsgType int Tickets []Ticket EncPart types.EncryptedData DecryptedEncPart EncKrbCredPart } // EncKrbCredPart is the encrypted part of KRB_CRED. type EncKrbCredPart struct { TicketInfo []KrbCredInfo `asn1:"explicit,tag:0"` Nouce int `asn1:"optional,explicit,tag:1"` Timestamp time.Time `asn1:"generalized,optional,explicit,tag:2"` Usec int `asn1:"optional,explicit,tag:3"` SAddress types.HostAddress `asn1:"optional,explicit,tag:4"` RAddress types.HostAddress `asn1:"optional,explicit,tag:5"` } // KrbCredInfo is the KRB_CRED_INFO part of KRB_CRED. type KrbCredInfo struct { Key types.EncryptionKey `asn1:"explicit,tag:0"` PRealm string `asn1:"generalstring,optional,explicit,tag:1"` PName types.PrincipalName `asn1:"optional,explicit,tag:2"` Flags asn1.BitString `asn1:"optional,explicit,tag:3"` AuthTime time.Time `asn1:"generalized,optional,explicit,tag:4"` StartTime time.Time `asn1:"generalized,optional,explicit,tag:5"` EndTime time.Time `asn1:"generalized,optional,explicit,tag:6"` RenewTill time.Time `asn1:"generalized,optional,explicit,tag:7"` SRealm string `asn1:"optional,explicit,ia5,tag:8"` SName types.PrincipalName `asn1:"optional,explicit,tag:9"` CAddr types.HostAddresses `asn1:"optional,explicit,tag:10"` } // Unmarshal bytes b into the KRBCred struct. func (k *KRBCred) Unmarshal(b []byte) error { var m marshalKRBCred _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBCred)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_CRED if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_CRED. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } k.PVNO = m.PVNO k.MsgType = m.MsgType k.EncPart = m.EncPart if len(m.Tickets.Bytes) > 0 { k.Tickets, err = unmarshalTicketsSequence(m.Tickets) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling tickets within KRB_CRED") } } return nil } // DecryptEncPart decrypts the encrypted part of a KRB_CRED. func (k *KRBCred) DecryptEncPart(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.KRB_CRED_ENCPART) if err != nil { return krberror.Errorf(err, krberror.DecryptingError, "error decrypting KRB_CRED EncPart") } var denc EncKrbCredPart err = denc.Unmarshal(b) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part of KRB_CRED") } k.DecryptedEncPart = denc return nil } // Unmarshal bytes b into the encrypted part of KRB_CRED. func (k *EncKrbCredPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncKrbCredPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling EncKrbCredPart") } return nil } gokrb5-8.4.3/v8/messages/KRBCred_test.go000066400000000000000000000213461427031340300176700ustar00rootroot00000000000000package messages import ( "encoding/hex" "fmt" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/addrtype" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalKRBCred(t *testing.T) { t.Parallel() var a KRBCred b, err := hex.DecodeString(testdata.MarshaledKRB5cred) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_CRED, a.MsgType, "Message type not as expected") assert.Equal(t, 2, len(a.Tickets), "Number of tickets not as expected") for i, tkt := range a.Tickets { assert.Equal(t, iana.PVNO, tkt.TktVNO, fmt.Sprintf("Ticket (%v) ticket-vno not as expected", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.Realm, fmt.Sprintf("Ticket (%v) realm not as expected", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Ticket (%v) SName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Ticket (%v) SName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Ticket (%v) SName name string entries not as expected", i+1)) assert.Equal(t, testdata.TEST_ETYPE, tkt.EncPart.EType, fmt.Sprintf("Ticket (%v) encPart etype not as expected", i+1)) assert.Equal(t, iana.PVNO, tkt.EncPart.KVNO, fmt.Sprintf("Ticket (%v) encPart KVNO not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), tkt.EncPart.Cipher, fmt.Sprintf("Ticket (%v) encPart cipher not as expected", i+1)) } assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "encPart etype not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "encPart KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "encPart cipher not as expected") } func TestUnmarshalEncCredPart(t *testing.T) { t.Parallel() var a EncKrbCredPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_cred_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, 2, len(a.TicketInfo), "Number of ticket info items not as expected") for i, tkt := range a.TicketInfo { assert.Equal(t, int32(1), tkt.Key.KeyType, fmt.Sprintf("Key type not as expected in ticket info item %d", i+1)) assert.Equal(t, []byte("12345678"), tkt.Key.KeyValue, fmt.Sprintf("Key value not as expected in ticket info item %d", i+1)) assert.Equal(t, testdata.TEST_REALM, tkt.PRealm, fmt.Sprintf("PRealm not as expected on ticket info item %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.PName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.PName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.PName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, "fedcba98", hex.EncodeToString(tkt.Flags.Bytes), fmt.Sprintf("Flags not as expected on ticket info %d", i+1)) assert.Equal(t, tt, tkt.AuthTime, fmt.Sprintf("Auth time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, tkt.StartTime, fmt.Sprintf("Start time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, tkt.EndTime, fmt.Sprintf("End time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, tkt.RenewTill, fmt.Sprintf("Renew Till time value not as expected for ticket info %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, tkt.SName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(tkt.SName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, tkt.SName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, 2, len(tkt.CAddr), "Number of client addresses not as expected") for j, addr := range tkt.CAddr { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d within ticket info %d", j+1, i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d within ticket info %d", j+1, i+1)) } } assert.Equal(t, testdata.TEST_NONCE, a.Nouce, "Nouce not as expected") assert.Equal(t, tt, a.Timestamp, "Timestamp not as expected") assert.Equal(t, 123456, a.Usec, "Microseconds not as expected") assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SAddress.Address), "Address not as expected for SAddress") assert.Equal(t, addrtype.IPv4, a.RAddress.AddrType, "RAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.RAddress.Address), "Address not as expected for RAddress") } func TestUnmarshalEncCredPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncKrbCredPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_cred_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, 2, len(a.TicketInfo), "Number of ticket info items not as expected") //1st Ticket i := 0 assert.Equal(t, int32(1), a.TicketInfo[i].Key.KeyType, fmt.Sprintf("Key type not as expected in ticket info item %d", i+1)) assert.Equal(t, []byte("12345678"), a.TicketInfo[i].Key.KeyValue, fmt.Sprintf("Key value not as expected in ticket info item %d", i+1)) //2nd Ticket i = 1 assert.Equal(t, int32(1), a.TicketInfo[i].Key.KeyType, fmt.Sprintf("Key type not as expected in ticket info item %d", i+1)) assert.Equal(t, []byte("12345678"), a.TicketInfo[i].Key.KeyValue, fmt.Sprintf("Key value not as expected in ticket info item %d", i+1)) assert.Equal(t, testdata.TEST_REALM, a.TicketInfo[i].PRealm, fmt.Sprintf("PRealm not as expected on ticket info item %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.TicketInfo[i].PName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.TicketInfo[i].PName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.TicketInfo[i].PName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, "fedcba98", hex.EncodeToString(a.TicketInfo[i].Flags.Bytes), fmt.Sprintf("Flags not as expected on ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].AuthTime, fmt.Sprintf("Auth time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].StartTime, fmt.Sprintf("Start time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].EndTime, fmt.Sprintf("End time value not as expected for ticket info %d", i+1)) assert.Equal(t, tt, a.TicketInfo[i].RenewTill, fmt.Sprintf("Renew Till time value not as expected for ticket info %d", i+1)) assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.TicketInfo[i].SName.NameType, fmt.Sprintf("Ticket info (%v) PName NameType not as expected", i+1)) assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.TicketInfo[i].SName.NameString), fmt.Sprintf("Ticket info (%v) PName does not have the expected number of NameStrings", i+1)) assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.TicketInfo[i].SName.NameString, fmt.Sprintf("Ticket info (%v) PName name string entries not as expected", i+1)) assert.Equal(t, 2, len(a.TicketInfo[i].CAddr), "Number of client addresses not as expected") for j, addr := range a.TicketInfo[i].CAddr { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d within ticket info %d", j+1, i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d within ticket info %d", j+1, i+1)) } } gokrb5-8.4.3/v8/messages/KRBError.go000066400000000000000000000063701427031340300170450ustar00rootroot00000000000000// Package messages implements Kerberos 5 message types and methods. package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) // KRBError implements RFC 4120 KRB_ERROR: https://tools.ietf.org/html/rfc4120#section-5.9.1. type KRBError struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` CTime time.Time `asn1:"generalized,optional,explicit,tag:2"` Cusec int `asn1:"optional,explicit,tag:3"` STime time.Time `asn1:"generalized,explicit,tag:4"` Susec int `asn1:"explicit,tag:5"` ErrorCode int32 `asn1:"explicit,tag:6"` CRealm string `asn1:"generalstring,optional,explicit,tag:7"` CName types.PrincipalName `asn1:"optional,explicit,tag:8"` Realm string `asn1:"generalstring,explicit,tag:9"` SName types.PrincipalName `asn1:"explicit,tag:10"` EText string `asn1:"generalstring,optional,explicit,tag:11"` EData []byte `asn1:"optional,explicit,tag:12"` } // NewKRBError creates a new KRBError. func NewKRBError(sname types.PrincipalName, realm string, code int32, etext string) KRBError { t := time.Now().UTC() return KRBError{ PVNO: iana.PVNO, MsgType: msgtype.KRB_ERROR, STime: t, Susec: int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)), ErrorCode: code, SName: sname, Realm: realm, EText: etext, } } // Unmarshal bytes b into the KRBError struct. func (k *KRBError) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBError)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "KRB_ERROR unmarshal error") } expectedMsgType := msgtype.KRB_ERROR if k.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_ERROR. Expected: %v; Actual: %v", expectedMsgType, k.MsgType) } return nil } // Marshal a KRBError into bytes. func (k *KRBError) Marshal() ([]byte, error) { b, err := asn1.Marshal(*k) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error marshaling KRBError") } b = asn1tools.AddASNAppTag(b, asnAppTag.KRBError) return b, nil } // Error method implementing error interface on KRBError struct. func (k KRBError) Error() string { etxt := fmt.Sprintf("KRB Error: %s", errorcode.Lookup(k.ErrorCode)) if k.EText != "" { etxt = fmt.Sprintf("%s - %s", etxt, k.EText) } return etxt } func processUnmarshalReplyError(b []byte, err error) error { switch err.(type) { case asn1.StructuralError: var krberr KRBError tmperr := krberr.Unmarshal(b) if tmperr != nil { return krberror.Errorf(err, krberror.EncodingError, "failed to unmarshal KDC's reply") } return krberr default: return krberror.Errorf(err, krberror.EncodingError, "failed to unmarshal KDC's reply") } } gokrb5-8.4.3/v8/messages/KRBError_test.go000066400000000000000000000074471427031340300201120ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalMarshalKRBError(t *testing.T) { t.Parallel() var a KRBError b, err := hex.DecodeString(testdata.MarshaledKRB5error) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO is not as expected") assert.Equal(t, msgtype.KRB_ERROR, a.MsgType, "Message type is not as expected") assert.Equal(t, tt, a.CTime, "CTime not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.STime, "STime not as expected") assert.Equal(t, 123456, a.Susec, "Service microseconds not as expected") assert.Equal(t, errorcode.KRB_ERR_GENERIC, a.ErrorCode, "Error code not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Ticket SName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Ticket SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Ticket SName name string entries not as expected") assert.Equal(t, "krb5data", a.EText, "EText not as expected") assert.Equal(t, []byte("krb5data"), a.EData, "EData not as expected") b2, err := a.Marshal() if err != nil { t.Errorf("error marshalling KRBError: %v", err) } assert.Equal(t, b, b2, "marshalled bytes not as expected") } func TestUnmarshalMarshalKRBError_optionalsNULL(t *testing.T) { t.Parallel() var a KRBError b, err := hex.DecodeString(testdata.MarshaledKRB5errorOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO is not as expected") assert.Equal(t, msgtype.KRB_ERROR, a.MsgType, "Message type is not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.STime, "STime not as expected") assert.Equal(t, 123456, a.Susec, "Service microseconds not as expected") assert.Equal(t, errorcode.KRB_ERR_GENERIC, a.ErrorCode, "Error code not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "Ticket SName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "Ticket SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "Ticket SName name string entries not as expected") b2, err := a.Marshal() if err != nil { t.Errorf("error marshalling KRBError: %v", err) } assert.Equal(t, b, b2, "marshalled bytes not as expected") } gokrb5-8.4.3/v8/messages/KRBPriv.go000066400000000000000000000067441427031340300167010ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) // KRBPriv implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.7.1. type KRBPriv struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` EncPart types.EncryptedData `asn1:"explicit,tag:3"` DecryptedEncPart EncKrbPrivPart `asn1:"optional,omitempty"` // Not part of ASN1 bytes so marked as optional so unmarshalling works } // EncKrbPrivPart is the encrypted part of KRB_PRIV. type EncKrbPrivPart struct { UserData []byte `asn1:"explicit,tag:0"` Timestamp time.Time `asn1:"generalized,optional,explicit,tag:1"` Usec int `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` SAddress types.HostAddress `asn1:"explicit,tag:4"` RAddress types.HostAddress `asn1:"optional,explicit,tag:5"` } // NewKRBPriv returns a new KRBPriv type. func NewKRBPriv(part EncKrbPrivPart) KRBPriv { return KRBPriv{ PVNO: iana.PVNO, MsgType: msgtype.KRB_PRIV, DecryptedEncPart: part, } } // Unmarshal bytes b into the KRBPriv struct. func (k *KRBPriv) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBPriv)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_PRIV if k.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_PRIV. Expected: %v; Actual: %v", expectedMsgType, k.MsgType) } return nil } // Unmarshal bytes b into the EncKrbPrivPart struct. func (k *EncKrbPrivPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncKrbPrivPart)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "KRB_PRIV unmarshal error") } return nil } // Marshal the KRBPriv. func (k *KRBPriv) Marshal() ([]byte, error) { tk := KRBPriv{ PVNO: k.PVNO, MsgType: k.MsgType, EncPart: k.EncPart, } b, err := asn1.Marshal(tk) if err != nil { return []byte{}, err } b = asn1tools.AddASNAppTag(b, asnAppTag.KRBPriv) return b, nil } // EncryptEncPart encrypts the DecryptedEncPart within the KRBPriv. // Use to prepare for marshaling. func (k *KRBPriv) EncryptEncPart(key types.EncryptionKey) error { b, err := asn1.Marshal(k.DecryptedEncPart) if err != nil { return err } b = asn1tools.AddASNAppTag(b, asnAppTag.EncKrbPrivPart) k.EncPart, err = crypto.GetEncryptedData(b, key, keyusage.KRB_PRIV_ENCPART, 1) if err != nil { return err } return nil } // DecryptEncPart decrypts the encrypted part of the KRBPriv message. func (k *KRBPriv) DecryptEncPart(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.KRB_PRIV_ENCPART) if err != nil { return fmt.Errorf("error decrypting KRBPriv EncPart: %v", err) } err = k.DecryptedEncPart.Unmarshal(b) if err != nil { return fmt.Errorf("error unmarshaling encrypted part: %v", err) } return nil } gokrb5-8.4.3/v8/messages/KRBPriv_test.go000066400000000000000000000102471427031340300177310ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/addrtype" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestUnmarshalKRBPriv(t *testing.T) { t.Parallel() var a KRBPriv b, err := hex.DecodeString(testdata.MarshaledKRB5priv) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_PRIV, a.MsgType, "Message type not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "EncPart KVNO not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "EncPart etype not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "Cipher text of EncPart not as expected") } func TestUnmarshalEncPrivPart(t *testing.T) { t.Parallel() var a EncKrbPrivPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "krb5data", string(a.UserData), "User data not as expected") assert.Equal(t, tt, a.Timestamp, "Timestamp not as expected") assert.Equal(t, 123456, a.Usec, "Microseconds not as expected") assert.Equal(t, int64(17), a.SequenceNumber, "Sequence number not as expected") assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SAddress.Address), "Address not as expected for SAddress") assert.Equal(t, addrtype.IPv4, a.RAddress.AddrType, "RAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.RAddress.Address), "Address not as expected for RAddress") } func TestUnmarshalEncPrivPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncKrbPrivPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, "krb5data", string(a.UserData), "User data not as expected") assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SAddress.Address), "Address not as expected for SAddress") } func TestMarshalKRBPriv(t *testing.T) { t.Parallel() var a KRBPriv b, err := hex.DecodeString(testdata.MarshaledKRB5priv) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("error marshaling KRBPriv: %v", err) } assert.Equal(t, b, mb, "marshaled bytes not as expected") be, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.DecryptedEncPart.Unmarshal(be) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err = a.Marshal() if err != nil { t.Fatalf("error marshaling KRBPriv: %v", err) } assert.Equal(t, b, mb, "marshaled bytes not as expected when it has decrypted encpart") } func TestKRBPriv_EncryptEncPart(t *testing.T) { t.Parallel() var a KRBPriv b, err := hex.DecodeString(testdata.MarshaledKRB5priv) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } b, err = hex.DecodeString(testdata.MarshaledKRB5enc_priv_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.DecryptedEncPart.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } key := types.EncryptionKey{ KeyType: int32(18), KeyValue: []byte("12345678901234567890123456789012"), } err = a.EncryptEncPart(key) if err != nil { t.Fatalf("error encrypting encpart: %v", err) } } gokrb5-8.4.3/v8/messages/KRBSafe.go000066400000000000000000000030321427031340300166220ustar00rootroot00000000000000package messages import ( "fmt" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) // KRBSafe implements RFC 4120 KRB_SAFE: https://tools.ietf.org/html/rfc4120#section-5.6.1. type KRBSafe struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` SafeBody KRBSafeBody `asn1:"explicit,tag:2"` Cksum types.Checksum `asn1:"explicit,tag:3"` } // KRBSafeBody implements the KRB_SAFE_BODY of KRB_SAFE. type KRBSafeBody struct { UserData []byte `asn1:"explicit,tag:0"` Timestamp time.Time `asn1:"generalized,optional,explicit,tag:1"` Usec int `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` SAddress types.HostAddress `asn1:"explicit,tag:4"` RAddress types.HostAddress `asn1:"optional,explicit,tag:5"` } // Unmarshal bytes b into the KRBSafe struct. func (s *KRBSafe) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, s, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBSafe)) if err != nil { return processUnmarshalReplyError(b, err) } expectedMsgType := msgtype.KRB_SAFE if s.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_SAFE. Expected: %v; Actual: %v", expectedMsgType, s.MsgType) } return nil } gokrb5-8.4.3/v8/messages/KRBSafe_test.go000066400000000000000000000051141427031340300176640ustar00rootroot00000000000000package messages import ( "encoding/hex" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/addrtype" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalKRBSafe(t *testing.T) { t.Parallel() var a KRBSafe b, err := hex.DecodeString(testdata.MarshaledKRB5safe) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_SAFE, a.MsgType, "MsgType is not as expected") assert.Equal(t, []byte("krb5data"), a.SafeBody.UserData, "Safe body userdata not as expected") assert.Equal(t, tt, a.SafeBody.Timestamp, "Safe body timestamp not as expected") assert.Equal(t, 123456, a.SafeBody.Usec, "Safe body microseconds not as expected") assert.Equal(t, int64(17), a.SafeBody.SequenceNumber, "Safe body sequence number not as expected") assert.Equal(t, addrtype.IPv4, a.SafeBody.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SafeBody.SAddress.Address), "SAddress not as expected") assert.Equal(t, addrtype.IPv4, a.SafeBody.RAddress.AddrType, "RAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SafeBody.RAddress.Address), "RAddress not as expected") assert.Equal(t, int32(1), a.Cksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.Cksum.Checksum, "Checksum not as expected") } func TestUnmarshalKRBSafe_optionalsNULL(t *testing.T) { t.Parallel() var a KRBSafe b, err := hex.DecodeString(testdata.MarshaledKRB5safeOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected") assert.Equal(t, msgtype.KRB_SAFE, a.MsgType, "MsgType is not as expected") assert.Equal(t, []byte("krb5data"), a.SafeBody.UserData, "Safe body userdata not as expected") assert.Equal(t, addrtype.IPv4, a.SafeBody.SAddress.AddrType, "SAddress type not as expected") assert.Equal(t, "12d00023", hex.EncodeToString(a.SafeBody.SAddress.Address), "SAddress not as expected") assert.Equal(t, int32(1), a.Cksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.Cksum.Checksum, "Checksum not as expected") } gokrb5-8.4.3/v8/messages/Ticket.go000066400000000000000000000244411427031340300166370ustar00rootroot00000000000000package messages import ( "fmt" "log" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/adtype" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/errorcode" "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/pac" "github.com/jcmturner/gokrb5/v8/types" ) // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.3 // Ticket implements the Kerberos ticket. type Ticket struct { TktVNO int `asn1:"explicit,tag:0"` Realm string `asn1:"generalstring,explicit,tag:1"` SName types.PrincipalName `asn1:"explicit,tag:2"` EncPart types.EncryptedData `asn1:"explicit,tag:3"` DecryptedEncPart EncTicketPart `asn1:"optional"` // Not part of ASN1 bytes so marked as optional so unmarshalling works } // EncTicketPart is the encrypted part of the Ticket. type EncTicketPart struct { Flags asn1.BitString `asn1:"explicit,tag:0"` Key types.EncryptionKey `asn1:"explicit,tag:1"` CRealm string `asn1:"generalstring,explicit,tag:2"` CName types.PrincipalName `asn1:"explicit,tag:3"` Transited TransitedEncoding `asn1:"explicit,tag:4"` AuthTime time.Time `asn1:"generalized,explicit,tag:5"` StartTime time.Time `asn1:"generalized,explicit,optional,tag:6"` EndTime time.Time `asn1:"generalized,explicit,tag:7"` RenewTill time.Time `asn1:"generalized,explicit,optional,tag:8"` CAddr types.HostAddresses `asn1:"explicit,optional,tag:9"` AuthorizationData types.AuthorizationData `asn1:"explicit,optional,tag:10"` } // TransitedEncoding part of the ticket's encrypted part. type TransitedEncoding struct { TRType int32 `asn1:"explicit,tag:0"` Contents []byte `asn1:"explicit,tag:1"` } // NewTicket creates a new Ticket instance. func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalName, srealm string, flags asn1.BitString, sktab *keytab.Keytab, eTypeID int32, kvno int, authTime, startTime, endTime, renewTill time.Time) (Ticket, types.EncryptionKey, error) { etype, err := crypto.GetEtype(eTypeID) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting etype for new ticket") } sessionKey, err := types.GenerateEncryptionKey(etype) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error generating session key") } etp := EncTicketPart{ Flags: flags, Key: sessionKey, CRealm: crealm, CName: cname, Transited: TransitedEncoding{}, AuthTime: authTime, StartTime: startTime, EndTime: endTime, RenewTill: renewTill, } b, err := asn1.Marshal(etp) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncodingError, "error marshalling ticket encpart") } b = asn1tools.AddASNAppTag(b, asnAppTag.EncTicketPart) skey, _, err := sktab.GetEncryptionKey(sname, srealm, kvno, eTypeID) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting encryption key for new ticket") } ed, err := crypto.GetEncryptedData(b, skey, keyusage.KDC_REP_TICKET, kvno) if err != nil { return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error encrypting ticket encpart") } tkt := Ticket{ TktVNO: iana.PVNO, Realm: srealm, SName: sname, EncPart: ed, } return tkt, sessionKey, nil } // Unmarshal bytes b into a Ticket struct. func (t *Ticket) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.Ticket)) return err } // Marshal the Ticket. func (t *Ticket) Marshal() ([]byte, error) { b, err := asn1.Marshal(*t) if err != nil { return nil, err } b = asn1tools.AddASNAppTag(b, asnAppTag.Ticket) return b, nil } // Unmarshal bytes b into the EncTicketPart struct. func (t *EncTicketPart) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.EncTicketPart)) return err } // unmarshalTicket returns a ticket from the bytes provided. func unmarshalTicket(b []byte) (t Ticket, err error) { err = t.Unmarshal(b) return } // UnmarshalTicketsSequence returns a slice of Tickets from a raw ASN1 value. func unmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) { //This is a workaround to a asn1 decoding issue in golang - https://github.com/golang/go/issues/17321. It's not pretty I'm afraid //We pull out raw values from the larger raw value (that is actually the data of the sequence of raw values) and track our position moving along the data. b := in.Bytes // Ignore the head of the asn1 stream (1 byte for tag and those for the length) as this is what tells us its a sequence but we're handling it ourselves p := 1 + asn1tools.GetNumberBytesInLengthHeader(in.Bytes) var tkts []Ticket var raw asn1.RawValue for p < (len(b)) { _, err := asn1.UnmarshalWithParams(b[p:], &raw, fmt.Sprintf("application,tag:%d", asnAppTag.Ticket)) if err != nil { return nil, fmt.Errorf("unmarshaling sequence of tickets failed getting length of ticket: %v", err) } t, err := unmarshalTicket(b[p:]) if err != nil { return nil, fmt.Errorf("unmarshaling sequence of tickets failed: %v", err) } p += len(raw.FullBytes) tkts = append(tkts, t) } MarshalTicketSequence(tkts) return tkts, nil } // MarshalTicketSequence marshals a slice of Tickets returning an ASN1 raw value containing the ticket sequence. func MarshalTicketSequence(tkts []Ticket) (asn1.RawValue, error) { raw := asn1.RawValue{ Class: 2, IsCompound: true, } if len(tkts) < 1 { // There are no tickets to marshal return raw, nil } var btkts []byte for i, t := range tkts { b, err := t.Marshal() if err != nil { return raw, fmt.Errorf("error marshaling ticket number %d in sequence of tickets", i+1) } btkts = append(btkts, b...) } // The ASN1 wrapping consists of 2 bytes: // 1st byte -> Identifier Octet - In this case an OCTET STRING (ASN TAG // 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here. // Application Tag: //| Byte: | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | //| Value: | 0 | 1 | 1 | From the RFC spec 4120 | //| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value | btkts = append(asn1tools.MarshalLengthBytes(len(btkts)), btkts...) btkts = append([]byte{byte(32 + asn1.TagSequence)}, btkts...) raw.Bytes = btkts // If we need to create the full bytes then identifier octet is "context-specific" = 128 + "constructed" + 32 + the wrapping explicit tag (11) //fmt.Fprintf(os.Stderr, "mRaw fb: %v\n", raw.FullBytes) return raw, nil } // DecryptEncPart decrypts the encrypted part of the ticket. // The sname argument can be used to specify which service principal's key should be used to decrypt the ticket. // If nil is passed as the sname then the service principal specified within the ticket it used. func (t *Ticket) DecryptEncPart(keytab *keytab.Keytab, sname *types.PrincipalName) error { if sname == nil { sname = &t.SName } key, _, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType) if err != nil { return NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err)) } return t.Decrypt(key) } // Decrypt decrypts the encrypted part of the ticket using the key provided. func (t *Ticket) Decrypt(key types.EncryptionKey) error { b, err := crypto.DecryptEncPart(t.EncPart, key, keyusage.KDC_REP_TICKET) if err != nil { return fmt.Errorf("error decrypting Ticket EncPart: %v", err) } var denc EncTicketPart err = denc.Unmarshal(b) if err != nil { return fmt.Errorf("error unmarshaling encrypted part: %v", err) } t.DecryptedEncPart = denc return nil } // GetPACType returns a Microsoft PAC that has been extracted from the ticket and processed. func (t *Ticket) GetPACType(keytab *keytab.Keytab, sname *types.PrincipalName, l *log.Logger) (bool, pac.PACType, error) { var isPAC bool for _, ad := range t.DecryptedEncPart.AuthorizationData { if ad.ADType == adtype.ADIfRelevant { var ad2 types.AuthorizationData err := ad2.Unmarshal(ad.ADData) if err != nil { l.Printf("PAC authorization data could not be unmarshaled: %v", err) continue } if ad2[0].ADType == adtype.ADWin2KPAC { isPAC = true var p pac.PACType err = p.Unmarshal(ad2[0].ADData) if err != nil { return isPAC, p, fmt.Errorf("error unmarshaling PAC: %v", err) } if sname == nil { sname = &t.SName } key, _, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType) if err != nil { return isPAC, p, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err)) } err = p.ProcessPACInfoBuffers(key, l) return isPAC, p, err } } } return isPAC, pac.PACType{}, nil } // Valid checks it the ticket is currently valid. Max duration passed endtime passed in as argument. func (t *Ticket) Valid(d time.Duration) (bool, error) { // Check for future tickets or invalid tickets time := time.Now().UTC() if t.DecryptedEncPart.StartTime.Sub(time) > d || types.IsFlagSet(&t.DecryptedEncPart.Flags, flags.Invalid) { return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "service ticket provided is not yet valid") } // Check for expired ticket if time.Sub(t.DecryptedEncPart.EndTime) > d { return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "service ticket provided has expired") } return true, nil } gokrb5-8.4.3/v8/messages/Ticket_test.go000066400000000000000000000154501427031340300176760ustar00rootroot00000000000000package messages import ( "bytes" "encoding/hex" "fmt" "log" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/addrtype" "github.com/jcmturner/gokrb5/v8/iana/adtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/iana/trtype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestUnmarshalTicket(t *testing.T) { t.Parallel() var a Ticket b, err := hex.DecodeString(testdata.MarshaledKRB5ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, iana.PVNO, a.TktVNO, "Ticket version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.Realm, "Realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.SName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.SName.NameString), "SName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.SName.NameString, "SName name strings not as expected") assert.Equal(t, testdata.TEST_ETYPE, a.EncPart.EType, "Etype of Ticket EncPart not as expected") assert.Equal(t, iana.PVNO, a.EncPart.KVNO, "KNVO of Ticket EncPart not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.EncPart.Cipher, "Cipher of Ticket EncPart not as expected") } func TestUnmarshalEncTicketPart(t *testing.T) { t.Parallel() var a EncTicketPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_tkt_part) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName string entries not as expected") assert.Equal(t, trtype.DOMAIN_X500_COMPRESS, a.Transited.TRType, "Transisted type not as expected") assert.Equal(t, []byte("EDU,MIT.,ATHENA.,WASHINGTON.EDU,CS."), a.Transited.Contents, "Transisted content not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.StartTime, "Start time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") assert.Equal(t, tt, a.RenewTill, "Renew Till time not as expected") assert.Equal(t, 2, len(a.CAddr), "Number of client addresses not as expected") for i, addr := range a.CAddr { assert.Equal(t, addrtype.IPv4, addr.AddrType, fmt.Sprintf("Host address type not as expected for address item %d", i+1)) assert.Equal(t, "12d00023", hex.EncodeToString(addr.Address), fmt.Sprintf("Host address not as expected for address item %d", i+1)) } for i, ele := range a.AuthorizationData { assert.Equal(t, adtype.ADIfRelevant, ele.ADType, fmt.Sprintf("Authorization data type of element %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_AUTHORIZATION_DATA_VALUE), ele.ADData, fmt.Sprintf("Authorization data of element %d not as expected", i+1)) } } func TestUnmarshalEncTicketPart_optionalsNULL(t *testing.T) { t.Parallel() var a EncTicketPart b, err := hex.DecodeString(testdata.MarshaledKRB5enc_tkt_partOptionalsNULL) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, "fedcba98", hex.EncodeToString(a.Flags.Bytes), "Flags not as expected") assert.Equal(t, int32(1), a.Key.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.Key.KeyValue, "Key value not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName string entries not as expected") assert.Equal(t, trtype.DOMAIN_X500_COMPRESS, a.Transited.TRType, "Transisted type not as expected") assert.Equal(t, []byte("EDU,MIT.,ATHENA.,WASHINGTON.EDU,CS."), a.Transited.Contents, "Transisted content not as expected") assert.Equal(t, tt, a.AuthTime, "Auth time not as expected") assert.Equal(t, tt, a.EndTime, "End time not as expected") } func TestMarshalTicket(t *testing.T) { t.Parallel() var a Ticket b, err := hex.DecodeString(testdata.MarshaledKRB5ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshalled bytes not as expected") } func TestAuthorizationData_GetPACType_GOKRB5TestData(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_AuthorizationData_GOKRB5) if err != nil { t.Fatalf("Test vector read error: %v", err) } var a types.AuthorizationData err = a.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } tkt := Ticket{ Realm: "TEST.GOKRB5", EncPart: types.EncryptedData{ EType: 18, KVNO: 2, }, DecryptedEncPart: EncTicketPart{ AuthorizationData: a, }, } b, _ = hex.DecodeString(testdata.KEYTAB_SYSHTTP_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}} w := bytes.NewBufferString("") l := log.New(w, "", 0) isPAC, pac, err := tkt.GetPACType(kt, &sname, l) if err != nil { t.Log(w.String()) t.Errorf("error getting PAC: %v", err) } assert.True(t, isPAC, "PAC should be present") assert.Equal(t, 5, len(pac.Buffers), "Number of buffers not as expected") assert.Equal(t, uint32(5), pac.CBuffers, "Count of buffers not as expected") assert.Equal(t, uint32(0), pac.Version, "PAC version not as expected") assert.NotNil(t, pac.KerbValidationInfo, "PAC Kerb Validation info is nil") assert.NotNil(t, pac.ClientInfo, "PAC Client Info info is nil") assert.NotNil(t, pac.UPNDNSInfo, "PAC UPN DNS Info info is nil") assert.NotNil(t, pac.KDCChecksum, "PAC KDC Checksum info is nil") assert.NotNil(t, pac.ServerChecksum, "PAC Server checksum info is nil") } gokrb5-8.4.3/v8/pac/000077500000000000000000000000001427031340300140145ustar00rootroot00000000000000gokrb5-8.4.3/v8/pac/client_claims.go000066400000000000000000000016141427031340300171530ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) // Claims reference: https://msdn.microsoft.com/en-us/library/hh553895.aspx // ClientClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh536365.aspx type ClientClaimsInfo struct { ClaimsSetMetadata mstypes.ClaimsSetMetadata ClaimsSet mstypes.ClaimsSet } // Unmarshal bytes into the ClientClaimsInfo struct func (k *ClientClaimsInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) m := new(mstypes.ClaimsSetMetadata) err = dec.Decode(m) if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSetMetadata: %v", err) return } k.ClaimsSetMetadata = *m k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet() if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err) } return } gokrb5-8.4.3/v8/pac/client_claims_test.go000066400000000000000000000201001427031340300202010ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/rpc/v2/mstypes" "github.com/stretchr/testify/assert" ) const ( ClaimsEntryIDStr = "ad://ext/sAMAccountName:88d5d9085ea5c0c0" ClaimsEntryValueStr = "testuser1" ClaimsEntryIDInt64 = "ad://ext/msDS-SupportedE:88d5dea8f1af5f19" ClaimsEntryValueInt64 int64 = 28 ClaimsEntryIDUInt64 = "ad://ext/objectClass:88d5de791e7b27e6" ) func TestPAC_ClientClaimsInfoStr_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoStr) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, uint16(3), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDStr, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []mstypes.LPWSTR{{Value: ClaimsEntryValueStr}}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMultiUint) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, mstypes.ClaimTypeIDUInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(4), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDUInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []uint64{655369, 65543, 65542, 65536}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsInt_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoInt) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, mstypes.ClaimTypeIDInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []int64{ClaimsEntryValueInt64}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMultiStr) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, mstypes.ClaimTypeIDString, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(4), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected") assert.Equal(t, "ad://ext/otherIpPhone:88d5de9f6b4af985", k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []mstypes.LPWSTR{{Value: "str1"}, {Value: "str2"}, {Value: "str3"}, {Value: "str4"}}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } func TestPAC_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) { // Has an int and a str claim type t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMulti) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientClaimsInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrayCount, "claims array count not as expected") assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected") assert.Equal(t, uint32(2), k.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected") assert.Equal(t, uint16(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected") assert.Equal(t, []int64{int64(28)}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected") assert.Equal(t, uint16(3), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].Type, "claims entry type not as expected") assert.Equal(t, uint32(1), k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].TypeString.ValueCount, "claims value count not as expected") assert.Equal(t, ClaimsEntryIDStr, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].ID, "claims entry ID not as expected") assert.Equal(t, []mstypes.LPWSTR{{Value: ClaimsEntryValueStr}}, k.ClaimsSet.ClaimsArrays[0].ClaimEntries[1].TypeString.Value, "claims value not as expected") assert.Equal(t, mstypes.CompressionFormatNone, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") } // Compressed claims not yet supported. //func TestPAC_ClientClaimsInfo_Unmarshal_UnsupportedCompression(t *testing.T) { // t.Parallel() // b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfo_XPRESS_HUFF) // if err != nil { // t.Fatal("Could not decode test data hex string") // } // var k ClientClaimsInfo // err = k.Unmarshal(b) // if err != nil { // t.Fatalf("Error unmarshaling test data: %v", err) // } // assert.Equal(t, mstypes.CompressionFormatXPressHuff, k.ClaimsSetMetadata.CompressionFormat, "compression format not as expected") //} gokrb5-8.4.3/v8/pac/client_info.go000066400000000000000000000017751427031340300166460ustar00rootroot00000000000000package pac import ( "bytes" "github.com/jcmturner/rpc/v2/mstypes" ) // ClientInfo implements https://msdn.microsoft.com/en-us/library/cc237951.aspx type ClientInfo struct { ClientID mstypes.FileTime // A FILETIME structure in little-endian format that contains the Kerberos initial ticket-granting ticket TGT authentication time NameLength uint16 // An unsigned 16-bit integer in little-endian format that specifies the length, in bytes, of the Name field. Name string // An array of 16-bit Unicode characters in little-endian format that contains the client's account name. } // Unmarshal bytes into the ClientInfo struct func (k *ClientInfo) Unmarshal(b []byte) (err error) { //The PAC_CLIENT_INFO structure is a simple structure that is not NDR-encoded. r := mstypes.NewReader(bytes.NewReader(b)) k.ClientID, err = r.FileTime() if err != nil { return } k.NameLength, err = r.Uint16() if err != nil { return } k.Name, err = r.UTF16String(int(k.NameLength)) return } gokrb5-8.4.3/v8/pac/client_info_test.go000066400000000000000000000013401427031340300176710ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "time" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestPAC_ClientInfo_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_Client_Info) if err != nil { t.Fatal("Could not decode test data hex string") } var k ClientInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } assert.Equal(t, time.Date(2017, 5, 6, 15, 53, 11, 000000000, time.UTC), k.ClientID.Time(), "Client ID time not as expected.") assert.Equal(t, uint16(18), k.NameLength, "Client name length not as expected") assert.Equal(t, "testuser1", k.Name, "Client name not as expected") } gokrb5-8.4.3/v8/pac/credentials_info.go000066400000000000000000000047321427031340300176610ustar00rootroot00000000000000package pac import ( "bytes" "errors" "fmt" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) // https://msdn.microsoft.com/en-us/library/cc237931.aspx // CredentialsInfo implements https://msdn.microsoft.com/en-us/library/cc237953.aspx type CredentialsInfo struct { Version uint32 // A 32-bit unsigned integer in little-endian format that defines the version. MUST be 0x00000000. EType uint32 PACCredentialDataEncrypted []byte // Key usage number for encryption: KERB_NON_KERB_SALT (16) PACCredentialData CredentialData } // Unmarshal bytes into the CredentialsInfo struct func (c *CredentialsInfo) Unmarshal(b []byte, k types.EncryptionKey) (err error) { //The CredentialsInfo structure is a simple structure that is not NDR-encoded. r := mstypes.NewReader(bytes.NewReader(b)) c.Version, err = r.Uint32() if err != nil { return } if c.Version != 0 { err = errors.New("credentials info version is not zero") return } c.EType, err = r.Uint32() if err != nil { return } c.PACCredentialDataEncrypted, err = r.ReadBytes(len(b) - 8) if err != nil { err = fmt.Errorf("error reading PAC Credetials Data: %v", err) return } err = c.DecryptEncPart(k) if err != nil { err = fmt.Errorf("error decrypting PAC Credentials Data: %v", err) return } return } // DecryptEncPart decrypts the encrypted part of the CredentialsInfo. func (c *CredentialsInfo) DecryptEncPart(k types.EncryptionKey) error { if k.KeyType != int32(c.EType) { return fmt.Errorf("key provided is not the correct type. Type needed: %d, type provided: %d", c.EType, k.KeyType) } pt, err := crypto.DecryptMessage(c.PACCredentialDataEncrypted, k, keyusage.KERB_NON_KERB_SALT) if err != nil { return err } err = c.PACCredentialData.Unmarshal(pt) if err != nil { return err } return nil } // CredentialData implements https://msdn.microsoft.com/en-us/library/cc237952.aspx type CredentialData struct { CredentialCount uint32 Credentials []SECPKGSupplementalCred // Size is the value of CredentialCount } // Unmarshal converts the bytes provided into a CredentialData type. func (c *CredentialData) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(c) if err != nil { err = fmt.Errorf("error unmarshaling KerbValidationInfo: %v", err) } return } gokrb5-8.4.3/v8/pac/device_claims.go000066400000000000000000000016141427031340300171340ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) // Claims reference: https://msdn.microsoft.com/en-us/library/hh553895.aspx // DeviceClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh554226.aspx type DeviceClaimsInfo struct { ClaimsSetMetadata mstypes.ClaimsSetMetadata ClaimsSet mstypes.ClaimsSet } // Unmarshal bytes into the ClientClaimsInfo struct func (k *DeviceClaimsInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) m := new(mstypes.ClaimsSetMetadata) err = dec.Decode(m) if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSetMetadata: %v", err) return } k.ClaimsSetMetadata = *m k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet() if err != nil { err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err) } return } gokrb5-8.4.3/v8/pac/device_info.go000066400000000000000000000046611427031340300166240ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) // DeviceInfo implements https://msdn.microsoft.com/en-us/library/hh536402.aspx type DeviceInfo struct { UserID uint32 // A 32-bit unsigned integer that contains the RID of the account. If the UserId member equals 0x00000000, the first group SID in this member is the SID for this account. PrimaryGroupID uint32 // A 32-bit unsigned integer that contains the RID for the primary group to which this account belongs. AccountDomainID mstypes.RPCSID `ndr:"pointer"` // A SID structure that contains the SID for the domain of the account.This member is used in conjunction with the UserId, and GroupIds members to create the user and group SIDs for the client. AccountGroupCount uint32 // A 32-bit unsigned integer that contains the number of groups within the account domain to which the account belongs AccountGroupIDs []mstypes.GroupMembership `ndr:"pointer,conformant"` // A pointer to a list of GROUP_MEMBERSHIP (section 2.2.2) structures that contains the groups to which the account belongs in the account domain. The number of groups in this list MUST be equal to GroupCount. SIDCount uint32 // A 32-bit unsigned integer that contains the total number of SIDs present in the ExtraSids member. ExtraSIDs []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"` // A pointer to a list of KERB_SID_AND_ATTRIBUTES structures that contain a list of SIDs corresponding to groups not in domains. If the UserId member equals 0x00000000, the first group SID in this member is the SID for this account. DomainGroupCount uint32 // A 32-bit unsigned integer that contains the number of domains with groups to which the account belongs. DomainGroup []mstypes.DomainGroupMembership `ndr:"pointer,conformant"` // A pointer to a list of DOMAIN_GROUP_MEMBERSHIP structures (section 2.2.3) that contains the domains to which the account belongs to a group. The number of sets in this list MUST be equal to DomainCount. } // Unmarshal bytes into the DeviceInfo struct func (k *DeviceInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(k) if err != nil { err = fmt.Errorf("error unmarshaling DeviceInfo: %v", err) } return } gokrb5-8.4.3/v8/pac/kerb_validation_info.go000066400000000000000000000111441427031340300205140ustar00rootroot00000000000000// Package pac implements Microsoft Privilege Attribute Certificate (PAC) processing. package pac import ( "bytes" "fmt" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) // KERB_VALIDATION_INFO flags. const ( USERFLAG_GUEST = 31 // Authentication was done via the GUEST account; no password was used. USERFLAG_NO_ENCRYPTION_AVAILABLE = 30 // No encryption is available. USERFLAG_LAN_MANAGER_KEY = 28 // LAN Manager key was used for authentication. USERFLAG_SUB_AUTH = 25 // Sub-authentication used; session key came from the sub-authentication package. USERFLAG_EXTRA_SIDS = 26 // Indicates that the ExtraSids field is populated and contains additional SIDs. USERFLAG_MACHINE_ACCOUNT = 24 // Indicates that the account is a machine account. USERFLAG_DC_NTLM2 = 23 // Indicates that the domain controller understands NTLMv2. USERFLAG_RESOURCE_GROUPIDS = 22 // Indicates that the ResourceGroupIds field is populated. USERFLAG_PROFILEPATH = 21 // Indicates that ProfilePath is populated. USERFLAG_NTLM2_NTCHALLENGERESP = 20 // The NTLMv2 response from the NtChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and session key generation. USERFLAG_LM2_LMCHALLENGERESP = 19 // The LMv2 response from the LmChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and session key generation. USERFLAG_AUTH_LMCHALLENGERESP_KEY_NTCHALLENGERESP = 18 // The LMv2 response from the LmChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and the NTLMv2 response from the NtChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used session key generation. ) // KerbValidationInfo implement https://msdn.microsoft.com/en-us/library/cc237948.aspx type KerbValidationInfo struct { LogOnTime mstypes.FileTime LogOffTime mstypes.FileTime KickOffTime mstypes.FileTime PasswordLastSet mstypes.FileTime PasswordCanChange mstypes.FileTime PasswordMustChange mstypes.FileTime EffectiveName mstypes.RPCUnicodeString FullName mstypes.RPCUnicodeString LogonScript mstypes.RPCUnicodeString ProfilePath mstypes.RPCUnicodeString HomeDirectory mstypes.RPCUnicodeString HomeDirectoryDrive mstypes.RPCUnicodeString LogonCount uint16 BadPasswordCount uint16 UserID uint32 PrimaryGroupID uint32 GroupCount uint32 GroupIDs []mstypes.GroupMembership `ndr:"pointer,conformant"` UserFlags uint32 UserSessionKey mstypes.UserSessionKey LogonServer mstypes.RPCUnicodeString LogonDomainName mstypes.RPCUnicodeString LogonDomainID mstypes.RPCSID `ndr:"pointer"` Reserved1 [2]uint32 // Has 2 elements UserAccountControl uint32 SubAuthStatus uint32 LastSuccessfulILogon mstypes.FileTime LastFailedILogon mstypes.FileTime FailedILogonCount uint32 Reserved3 uint32 SIDCount uint32 ExtraSIDs []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"` ResourceGroupDomainSID mstypes.RPCSID `ndr:"pointer"` ResourceGroupCount uint32 ResourceGroupIDs []mstypes.GroupMembership `ndr:"pointer,conformant"` } // Unmarshal bytes into the DeviceInfo struct func (k *KerbValidationInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(k) if err != nil { err = fmt.Errorf("error unmarshaling KerbValidationInfo: %v", err) } return } // GetGroupMembershipSIDs returns a slice of strings containing the group membership SIDs found in the PAC. func (k *KerbValidationInfo) GetGroupMembershipSIDs() []string { var g []string lSID := k.LogonDomainID.String() for i := range k.GroupIDs { g = append(g, fmt.Sprintf("%s-%d", lSID, k.GroupIDs[i].RelativeID)) } for _, s := range k.ExtraSIDs { var exists = false for _, es := range g { if es == s.SID.String() { exists = true break } } if !exists { g = append(g, s.SID.String()) } } for _, r := range k.ResourceGroupIDs { var exists = false s := fmt.Sprintf("%s-%d", k.ResourceGroupDomainSID.String(), r.RelativeID) for _, es := range g { if es == s { exists = true break } } if !exists { g = append(g, s) } } return g } gokrb5-8.4.3/v8/pac/kerb_validation_info_test.go000066400000000000000000000337701427031340300215640ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "time" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/rpc/v2/mstypes" "github.com/stretchr/testify/assert" ) func TestKerbValidationInfo_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info_MS) if err != nil { t.Fatal("Could not decode test data hex string") } var k KerbValidationInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling KerbValidationInfo: %v", err) } assert.Equal(t, time.Date(2006, 4, 28, 1, 42, 50, 925640100, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected") assert.Equal(t, time.Date(2006, 3, 18, 10, 44, 54, 837147900, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected") assert.Equal(t, time.Date(2006, 3, 19, 10, 44, 54, 837147900, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected") assert.Equal(t, "lzhu", k.EffectiveName.Value, "EffectiveName not as expected") assert.Equal(t, "Liqiang(Larry) Zhu", k.FullName.String(), "EffectiveName not as expected") assert.Equal(t, "ntds2.bat", k.LogonScript.String(), "EffectiveName not as expected") assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected") assert.Equal(t, uint16(4180), k.LogonCount, "LogonCount not as expected") assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected") assert.Equal(t, uint32(2914711), k.UserID, "UserID not as expected") assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected") assert.Equal(t, uint32(26), k.GroupCount, "GroupCount not as expected") gids := []mstypes.GroupMembership{ {RelativeID: 3392609, Attributes: 7}, {RelativeID: 2999049, Attributes: 7}, {RelativeID: 3322974, Attributes: 7}, {RelativeID: 513, Attributes: 7}, {RelativeID: 2931095, Attributes: 7}, {RelativeID: 3338539, Attributes: 7}, {RelativeID: 3354830, Attributes: 7}, {RelativeID: 3026599, Attributes: 7}, {RelativeID: 3338538, Attributes: 7}, {RelativeID: 2931096, Attributes: 7}, {RelativeID: 3392610, Attributes: 7}, {RelativeID: 3342740, Attributes: 7}, {RelativeID: 3392630, Attributes: 7}, {RelativeID: 3014318, Attributes: 7}, {RelativeID: 2937394, Attributes: 7}, {RelativeID: 3278870, Attributes: 7}, {RelativeID: 3038018, Attributes: 7}, {RelativeID: 3322975, Attributes: 7}, {RelativeID: 3513546, Attributes: 7}, {RelativeID: 2966661, Attributes: 7}, {RelativeID: 3338434, Attributes: 7}, {RelativeID: 3271401, Attributes: 7}, {RelativeID: 3051245, Attributes: 7}, {RelativeID: 3271606, Attributes: 7}, {RelativeID: 3026603, Attributes: 7}, {RelativeID: 3018354, Attributes: 7}, } assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected") assert.Equal(t, uint32(32), k.UserFlags, "UserFlags not as expected") assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected") assert.Equal(t, "NTDEV-DC-05", k.LogonServer.Value, "LogonServer not as expected") assert.Equal(t, "NTDEV", k.LogonDomainName.Value, "LogonDomainName not as expected") assert.Equal(t, "S-1-5-21-397955417-626881126-188441444", k.LogonDomainID.String(), "LogonDomainID not as expected") assert.Equal(t, uint32(16), k.UserAccountControl, "UserAccountControl not as expected") assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected") assert.Equal(t, uint32(13), k.SIDCount, "SIDCount not as expected") assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same") var es = []struct { sid string attr uint32 }{ {"S-1-5-21-773533881-1816936887-355810188-513", uint32(7)}, {"S-1-5-21-397955417-626881126-188441444-3101812", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3291368", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3291341", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3322973", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3479105", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3271400", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3283393", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3338537", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3038991", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3037999", uint32(536870919)}, {"S-1-5-21-397955417-626881126-188441444-3248111", uint32(536870919)}, } for i, s := range es { assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected") assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected") } assert.Equal(t, uint8(0), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected") assert.Equal(t, 0, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected") b, err = hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info) if err != nil { t.Fatal("Could not decode test data hex string") } var k2 KerbValidationInfo err = k2.Unmarshal(b) if err != nil { t.Fatal("Could not unmarshal KerbValidationInfo") } assert.Equal(t, time.Date(2017, 5, 6, 15, 53, 11, 825766900, time.UTC), k2.LogOnTime.Time(), "LogOnTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.LogOffTime.Time(), "LogOffTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.KickOffTime.Time(), "KickOffTime not as expected") assert.Equal(t, time.Date(2017, 5, 6, 7, 23, 8, 968750000, time.UTC), k2.PasswordLastSet.Time(), "PasswordLastSet not as expected") assert.Equal(t, time.Date(2017, 5, 7, 7, 23, 8, 968750000, time.UTC), k2.PasswordCanChange.Time(), "PasswordCanChange not as expected") assert.Equal(t, "testuser1", k2.EffectiveName.String(), "EffectiveName not as expected") assert.Equal(t, "Test1 User1", k2.FullName.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.LogonScript.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.ProfilePath.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.HomeDirectory.String(), "EffectiveName not as expected") assert.Equal(t, "", k2.HomeDirectoryDrive.String(), "EffectiveName not as expected") assert.Equal(t, uint16(216), k2.LogonCount, "LogonCount not as expected") assert.Equal(t, uint16(0), k2.BadPasswordCount, "BadPasswordCount not as expected") assert.Equal(t, uint32(1105), k2.UserID, "UserID not as expected") assert.Equal(t, uint32(513), k2.PrimaryGroupID, "PrimaryGroupID not as expected") assert.Equal(t, uint32(5), k2.GroupCount, "GroupCount not as expected") gids = []mstypes.GroupMembership{ {RelativeID: 513, Attributes: 7}, {RelativeID: 1108, Attributes: 7}, {RelativeID: 1109, Attributes: 7}, {RelativeID: 1115, Attributes: 7}, {RelativeID: 1116, Attributes: 7}, } assert.Equal(t, gids, k2.GroupIDs, "GroupIDs not as expected") assert.Equal(t, uint32(32), k2.UserFlags, "UserFlags not as expected") assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k2.UserSessionKey, "UserSessionKey not as expected") assert.Equal(t, "ADDC", k2.LogonServer.Value, "LogonServer not as expected") assert.Equal(t, "TEST", k2.LogonDomainName.Value, "LogonDomainName not as expected") assert.Equal(t, "S-1-5-21-3167651404-3865080224-2280184895", k2.LogonDomainID.String(), "LogonDomainID not as expected") assert.Equal(t, uint32(528), k2.UserAccountControl, "UserAccountControl not as expected") assert.Equal(t, uint32(0), k2.SubAuthStatus, "SubAuthStatus not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, uint32(0), k2.FailedILogonCount, "FailedILogonCount not as expected") assert.Equal(t, uint32(2), k2.SIDCount, "SIDCount not as expected") assert.Equal(t, int(k2.SIDCount), len(k2.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same") var es2 = []struct { sid string attr uint32 }{ {"S-1-5-21-3167651404-3865080224-2280184895-1114", uint32(536870919)}, {"S-1-5-21-3167651404-3865080224-2280184895-1111", uint32(536870919)}, } for i, s := range es2 { assert.Equal(t, s.sid, k2.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected") assert.Equal(t, s.attr, k2.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected") } assert.Equal(t, uint8(0), k2.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected") assert.Equal(t, 0, len(k2.ResourceGroupIDs), "ResourceGroupIDs not as expected") } func TestKerbValidationInfo_Unmarshal_DomainTrust(t *testing.T) { b, err := hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info_Trust) if err != nil { t.Fatal("Could not decode test data hex string") } var k KerbValidationInfo err = k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling KerbValidationInfo: %v", err) } assert.Equal(t, time.Date(2017, 10, 14, 12, 03, 41, 52409900, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected") assert.Equal(t, time.Date(2017, 10, 10, 20, 42, 56, 220282300, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected") assert.Equal(t, time.Date(2017, 10, 11, 20, 42, 56, 220282300, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected") assert.Equal(t, "testuser1", k.EffectiveName.String(), "EffectiveName not as expected") assert.Equal(t, "Test1 User1", k.FullName.String(), "EffectiveName not as expected") assert.Equal(t, "", k.LogonScript.String(), "EffectiveName not as expected") assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected") assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected") assert.Equal(t, uint16(46), k.LogonCount, "LogonCount not as expected") assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected") assert.Equal(t, uint32(1106), k.UserID, "UserID not as expected") assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected") assert.Equal(t, uint32(3), k.GroupCount, "GroupCount not as expected") gids := []mstypes.GroupMembership{ {RelativeID: 1110, Attributes: 7}, {RelativeID: 513, Attributes: 7}, {RelativeID: 1109, Attributes: 7}, } assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected") assert.Equal(t, uint32(544), k.UserFlags, "UserFlags not as expected") assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected") assert.Equal(t, "UDC", k.LogonServer.Value, "LogonServer not as expected") assert.Equal(t, "USER", k.LogonDomainName.Value, "LogonDomainName not as expected") assert.Equal(t, "S-1-5-21-2284869408-3503417140-1141177250", k.LogonDomainID.String(), "LogonDomainID not as expected") assert.Equal(t, uint32(528), k.UserAccountControl, "UserAccountControl not as expected") assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected") assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected") assert.Equal(t, uint32(1), k.SIDCount, "SIDCount not as expected") assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same") var es = []struct { sid string attr uint32 }{ {"S-1-18-1", uint32(7)}, } for i, s := range es { assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected") assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected") } assert.Equal(t, uint8(4), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected") assert.Equal(t, "S-1-5-21-3062750306-1230139592-1973306805", k.ResourceGroupDomainSID.String(), "ResourceGroupDomainSID value not as expected") assert.Equal(t, 2, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected") rgids := []mstypes.GroupMembership{ {RelativeID: 1107, Attributes: 536870919}, {RelativeID: 1108, Attributes: 536870919}, } assert.Equal(t, rgids, k.ResourceGroupIDs, "ResourceGroupIDs not as expected") groupSids := []string{"S-1-5-21-2284869408-3503417140-1141177250-1110", "S-1-5-21-2284869408-3503417140-1141177250-513", "S-1-5-21-2284869408-3503417140-1141177250-1109", "S-1-18-1", "S-1-5-21-3062750306-1230139592-1973306805-1107", "S-1-5-21-3062750306-1230139592-1973306805-1108"} assert.Equal(t, groupSids, k.GetGroupMembershipSIDs(), "GroupMembershipSIDs not as expected") } gokrb5-8.4.3/v8/pac/pac_type.go000066400000000000000000000167031427031340300161560ustar00rootroot00000000000000package pac import ( "bytes" "errors" "fmt" "log" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" "github.com/jcmturner/rpc/v2/mstypes" ) const ( infoTypeKerbValidationInfo uint32 = 1 infoTypeCredentials uint32 = 2 infoTypePACServerSignatureData uint32 = 6 infoTypePACKDCSignatureData uint32 = 7 infoTypePACClientInfo uint32 = 10 infoTypeS4UDelegationInfo uint32 = 11 infoTypeUPNDNSInfo uint32 = 12 infoTypePACClientClaimsInfo uint32 = 13 infoTypePACDeviceInfo uint32 = 14 infoTypePACDeviceClaimsInfo uint32 = 15 ) // PACType implements: https://msdn.microsoft.com/en-us/library/cc237950.aspx type PACType struct { CBuffers uint32 Version uint32 Buffers []InfoBuffer Data []byte KerbValidationInfo *KerbValidationInfo CredentialsInfo *CredentialsInfo ServerChecksum *SignatureData KDCChecksum *SignatureData ClientInfo *ClientInfo S4UDelegationInfo *S4UDelegationInfo UPNDNSInfo *UPNDNSInfo ClientClaimsInfo *ClientClaimsInfo DeviceInfo *DeviceInfo DeviceClaimsInfo *DeviceClaimsInfo ZeroSigData []byte } // InfoBuffer implements the PAC Info Buffer: https://msdn.microsoft.com/en-us/library/cc237954.aspx type InfoBuffer struct { ULType uint32 // A 32-bit unsigned integer in little-endian format that describes the type of data present in the buffer contained at Offset. CBBufferSize uint32 // A 32-bit unsigned integer in little-endian format that contains the size, in bytes, of the buffer in the PAC located at Offset. Offset uint64 // A 64-bit unsigned integer in little-endian format that contains the offset to the beginning of the buffer, in bytes, from the beginning of the PACTYPE structure. The data offset MUST be a multiple of eight. The following sections specify the format of each type of element. } // Unmarshal bytes into the PACType struct func (pac *PACType) Unmarshal(b []byte) (err error) { pac.Data = b zb := make([]byte, len(b), len(b)) copy(zb, b) pac.ZeroSigData = zb r := mstypes.NewReader(bytes.NewReader(b)) pac.CBuffers, err = r.Uint32() if err != nil { return } pac.Version, err = r.Uint32() if err != nil { return } buf := make([]InfoBuffer, pac.CBuffers, pac.CBuffers) for i := range buf { buf[i].ULType, err = r.Uint32() if err != nil { return } buf[i].CBBufferSize, err = r.Uint32() if err != nil { return } buf[i].Offset, err = r.Uint64() if err != nil { return } } pac.Buffers = buf return nil } // ProcessPACInfoBuffers processes the PAC Info Buffers. // https://msdn.microsoft.com/en-us/library/cc237954.aspx func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey, l *log.Logger) error { for _, buf := range pac.Buffers { p := make([]byte, buf.CBBufferSize, buf.CBBufferSize) copy(p, pac.Data[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)]) switch buf.ULType { case infoTypeKerbValidationInfo: if pac.KerbValidationInfo != nil { //Must ignore subsequent buffers of this type continue } var k KerbValidationInfo err := k.Unmarshal(p) if err != nil { return fmt.Errorf("error processing KerbValidationInfo: %v", err) } pac.KerbValidationInfo = &k case infoTypeCredentials: // Currently PAC parsing is only useful on the service side in gokrb5 // The CredentialsInfo are only useful when gokrb5 has implemented RFC4556 and only applied on the client side. // Skipping CredentialsInfo - will be revisited under RFC4556 implementation. continue //if pac.CredentialsInfo != nil { // //Must ignore subsequent buffers of this type // continue //} //var k CredentialsInfo //err := k.Unmarshal(p, key) // The encryption key used is the AS reply key only available to the client. //if err != nil { // return fmt.Errorf("error processing CredentialsInfo: %v", err) //} //pac.CredentialsInfo = &k case infoTypePACServerSignatureData: if pac.ServerChecksum != nil { //Must ignore subsequent buffers of this type continue } var k SignatureData zb, err := k.Unmarshal(p) copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb) if err != nil { return fmt.Errorf("error processing ServerChecksum: %v", err) } pac.ServerChecksum = &k case infoTypePACKDCSignatureData: if pac.KDCChecksum != nil { //Must ignore subsequent buffers of this type continue } var k SignatureData zb, err := k.Unmarshal(p) copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb) if err != nil { return fmt.Errorf("error processing KDCChecksum: %v", err) } pac.KDCChecksum = &k case infoTypePACClientInfo: if pac.ClientInfo != nil { //Must ignore subsequent buffers of this type continue } var k ClientInfo err := k.Unmarshal(p) if err != nil { return fmt.Errorf("error processing ClientInfo: %v", err) } pac.ClientInfo = &k case infoTypeS4UDelegationInfo: if pac.S4UDelegationInfo != nil { //Must ignore subsequent buffers of this type continue } var k S4UDelegationInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process S4U_DelegationInfo: %v", err) continue } pac.S4UDelegationInfo = &k case infoTypeUPNDNSInfo: if pac.UPNDNSInfo != nil { //Must ignore subsequent buffers of this type continue } var k UPNDNSInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process UPN_DNSInfo: %v", err) continue } pac.UPNDNSInfo = &k case infoTypePACClientClaimsInfo: if pac.ClientClaimsInfo != nil || len(p) < 1 { //Must ignore subsequent buffers of this type continue } var k ClientClaimsInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process ClientClaimsInfo: %v", err) continue } pac.ClientClaimsInfo = &k case infoTypePACDeviceInfo: if pac.DeviceInfo != nil { //Must ignore subsequent buffers of this type continue } var k DeviceInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process DeviceInfo: %v", err) continue } pac.DeviceInfo = &k case infoTypePACDeviceClaimsInfo: if pac.DeviceClaimsInfo != nil { //Must ignore subsequent buffers of this type continue } var k DeviceClaimsInfo err := k.Unmarshal(p) if err != nil { l.Printf("could not process DeviceClaimsInfo: %v", err) continue } pac.DeviceClaimsInfo = &k } } if ok, err := pac.verify(key); !ok { return err } return nil } func (pac *PACType) verify(key types.EncryptionKey) (bool, error) { if pac.KerbValidationInfo == nil { return false, errors.New("PAC Info Buffers does not contain a KerbValidationInfo") } if pac.ServerChecksum == nil { return false, errors.New("PAC Info Buffers does not contain a ServerChecksum") } if pac.KDCChecksum == nil { return false, errors.New("PAC Info Buffers does not contain a KDCChecksum") } if pac.ClientInfo == nil { return false, errors.New("PAC Info Buffers does not contain a ClientInfo") } etype, err := crypto.GetChksumEtype(int32(pac.ServerChecksum.SignatureType)) if err != nil { return false, err } if ok := etype.VerifyChecksum(key.KeyValue, pac.ZeroSigData, pac.ServerChecksum.Signature, keyusage.KERB_NON_KERB_CKSUM_SALT); !ok { return false, errors.New("PAC service checksum verification failed") } return true, nil } gokrb5-8.4.3/v8/pac/pac_type_test.go000066400000000000000000000033131427031340300172060ustar00rootroot00000000000000package pac import ( "bytes" "encoding/hex" "fmt" "log" "testing" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func TestPACTypeVerify(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_AD_WIN2K_PAC) if err != nil { t.Fatalf("Test vector read error: %v", err) } var pac PACType err = pac.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } b, _ = hex.DecodeString(testdata.KEYTAB_SYSHTTP_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) pn, _ := types.ParseSPNString("sysHTTP") key, _, err := kt.GetEncryptionKey(pn, "TEST.GOKRB5", 2, 18) if err != nil { t.Fatalf("Error getting key: %v", err) } w := bytes.NewBufferString("") l := log.New(w, "", 0) err = pac.ProcessPACInfoBuffers(key, l) if err != nil { t.Fatalf("Processing reference pac error: %v", err) } pacInvalidServerSig := pac // Check the signature to force failure pacInvalidServerSig.ServerChecksum.Signature[0] ^= 0xFF pacInvalidNilKerbValidationInfo := pac pacInvalidNilKerbValidationInfo.KerbValidationInfo = nil pacInvalidNilServerSig := pac pacInvalidNilServerSig.ServerChecksum = nil pacInvalidNilKdcSig := pac pacInvalidNilKdcSig.KDCChecksum = nil pacInvalidClientInfo := pac pacInvalidClientInfo.ClientInfo = nil var pacs = []struct { pac PACType }{ {pacInvalidServerSig}, {pacInvalidNilKerbValidationInfo}, {pacInvalidNilServerSig}, {pacInvalidNilKdcSig}, {pacInvalidClientInfo}, } for i, s := range pacs { v, _ := s.pac.verify(key) assert.False(t, v, fmt.Sprintf("Validation should have failed for test %v", i)) } } gokrb5-8.4.3/v8/pac/s4u_delegation_info.go000066400000000000000000000015701427031340300202670ustar00rootroot00000000000000package pac import ( "bytes" "fmt" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) // S4UDelegationInfo implements https://msdn.microsoft.com/en-us/library/cc237944.aspx type S4UDelegationInfo struct { S4U2proxyTarget mstypes.RPCUnicodeString // The name of the principal to whom the application can forward the ticket. TransitedListSize uint32 S4UTransitedServices []mstypes.RPCUnicodeString `ndr:"pointer,conformant"` // List of all services that have been delegated through by this client and subsequent services or servers.. Size is value of TransitedListSize } // Unmarshal bytes into the S4UDelegationInfo struct func (k *S4UDelegationInfo) Unmarshal(b []byte) (err error) { dec := ndr.NewDecoder(bytes.NewReader(b)) err = dec.Decode(k) if err != nil { err = fmt.Errorf("error unmarshaling S4UDelegationInfo: %v", err) } return } gokrb5-8.4.3/v8/pac/signature_data.go000066400000000000000000000041041427031340300173340ustar00rootroot00000000000000package pac import ( "bytes" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/rpc/v2/mstypes" ) /* https://msdn.microsoft.com/en-us/library/cc237955.aspx The Key Usage Value MUST be KERB_NON_KERB_CKSUM_SALT (17) [MS-KILE] (section 3.1.5.9). Server Signature (SignatureType = 0x00000006) https://msdn.microsoft.com/en-us/library/cc237957.aspx KDC Signature (SignatureType = 0x00000007) https://msdn.microsoft.com/en-us/library/dd357117.aspx */ // SignatureData implements https://msdn.microsoft.com/en-us/library/cc237955.aspx type SignatureData struct { SignatureType uint32 // A 32-bit unsigned integer value in little-endian format that defines the cryptographic system used to calculate the checksum. This MUST be one of the following checksum types: KERB_CHECKSUM_HMAC_MD5 (signature size = 16), HMAC_SHA1_96_AES128 (signature size = 12), HMAC_SHA1_96_AES256 (signature size = 12). Signature []byte // Size depends on the type. See comment above. RODCIdentifier uint16 // A 16-bit unsigned integer value in little-endian format that contains the first 16 bits of the key version number ([MS-KILE] section 3.1.5.8) when the KDC is an RODC. When the KDC is not an RODC, this field does not exist. } // Unmarshal bytes into the SignatureData struct func (k *SignatureData) Unmarshal(b []byte) (rb []byte, err error) { r := mstypes.NewReader(bytes.NewReader(b)) k.SignatureType, err = r.Uint32() if err != nil { return } var c int switch k.SignatureType { case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED: c = 16 case uint32(chksumtype.HMAC_SHA1_96_AES128): c = 12 case uint32(chksumtype.HMAC_SHA1_96_AES256): c = 12 } k.Signature, err = r.ReadBytes(c) if err != nil { return } // When the KDC is not an Read Only Domain Controller (RODC), this field does not exist. if len(b) >= 4+c+2 { k.RODCIdentifier, err = r.Uint16() if err != nil { return } } // Create bytes with zeroed signature needed for checksum verification rb = make([]byte, len(b), len(b)) copy(rb, b) z := make([]byte, len(b), len(b)) copy(rb[4:4+c], z) return } gokrb5-8.4.3/v8/pac/signature_data_test.go000066400000000000000000000034641427031340300204030ustar00rootroot00000000000000package pac import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestPAC_SignatureData_Unmarshal_Server_Signature(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_Server_Signature) if err != nil { t.Fatal("Could not decode test data hex string") } var k SignatureData bz, err := k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } sig, _ := hex.DecodeString("1e251d98d552be7df384f550") zeroed, _ := hex.DecodeString("10000000000000000000000000000000") assert.Equal(t, uint32(chksumtype.HMAC_SHA1_96_AES256), k.SignatureType, "Server signature type not as expected") assert.Equal(t, sig, k.Signature, "Server signature not as expected") assert.Equal(t, uint16(0), k.RODCIdentifier, "RODC Identifier not as expected") assert.Equal(t, zeroed, bz, "Returned bytes with zeroed signature not as expected") } func TestPAC_SignatureData_Unmarshal_KDC_Signature(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testdata.MarshaledPAC_KDC_Signature) if err != nil { t.Fatal("Could not decode test data hex string") } var k SignatureData bz, err := k.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshaling test data: %v", err) } sig, _ := hex.DecodeString("340be28b48765d0519ee9346cf53d822") zeroed, _ := hex.DecodeString("76ffffff00000000000000000000000000000000") assert.Equal(t, chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED, k.SignatureType, "Server signature type not as expected") assert.Equal(t, sig, k.Signature, "Server signature not as expected") assert.Equal(t, uint16(0), k.RODCIdentifier, "RODC Identifier not as expected") assert.Equal(t, zeroed, bz, "Returned bytes with zeroed signature not as expected") } gokrb5-8.4.3/v8/pac/supplemental_cred.go000066400000000000000000000047421427031340300200600ustar00rootroot00000000000000package pac import ( "bytes" "encoding/binary" "errors" "fmt" "github.com/jcmturner/rpc/v2/mstypes" "github.com/jcmturner/rpc/v2/ndr" ) const ( // NTLMSupCredLMOWF indicates that the LM OWF member is present and valid. NTLMSupCredLMOWF uint32 = 31 // NTLMSupCredNTOWF indicates that the NT OWF member is present and valid. NTLMSupCredNTOWF uint32 = 30 ) // NTLMSupplementalCred implements https://msdn.microsoft.com/en-us/library/cc237949.aspx type NTLMSupplementalCred struct { Version uint32 // A 32-bit unsigned integer that defines the credential version.This field MUST be 0x00000000. Flags uint32 LMPassword []byte // A 16-element array of unsigned 8-bit integers that define the LM OWF. The LMPassword member MUST be ignored if the L flag is not set in the Flags member. NTPassword []byte // A 16-element array of unsigned 8-bit integers that define the NT OWF. The NTPassword member MUST be ignored if the N flag is not set in the Flags member. } // Unmarshal converts the bytes provided into a NTLMSupplementalCred. func (c *NTLMSupplementalCred) Unmarshal(b []byte) (err error) { r := mstypes.NewReader(bytes.NewReader(b)) c.Version, err = r.Uint32() if err != nil { return } if c.Version != 0 { err = errors.New("NTLMSupplementalCred version is not zero") return } c.Flags, err = r.Uint32() if err != nil { return } if isFlagSet(c.Flags, NTLMSupCredLMOWF) { c.LMPassword, err = r.ReadBytes(16) if err != nil { return } } if isFlagSet(c.Flags, NTLMSupCredNTOWF) { c.NTPassword, err = r.ReadBytes(16) if err != nil { return } } return } // isFlagSet tests if a flag is set in the uint32 little endian flag func isFlagSet(f uint32, i uint32) bool { //Which byte? b := int(i / 8) //Which bit in byte p := uint(7 - (int(i) - 8*b)) fb := make([]byte, 4) binary.LittleEndian.PutUint32(fb, f) if fb[b]&(1< - no domain specified // \ // @ if strings.Contains(vc[0], `\`) { u := strings.SplitN(vc[0], `\`, 2) domain = u[0] username = u[1] } else if strings.Contains(vc[0], `@`) { u := strings.SplitN(vc[0], `@`, 2) domain = u[1] username = u[0] } else { username = vc[0] } return } gokrb5-8.4.3/v8/service/authenticator_test.go000066400000000000000000000006071427031340300211540ustar00rootroot00000000000000package service import ( "testing" "github.com/jcmturner/goidentity/v6" "github.com/stretchr/testify/assert" ) func TestImplementsInterface(t *testing.T) { t.Parallel() //s := new(SPNEGOAuthenticator) var s KRB5BasicAuthenticator a := new(goidentity.Authenticator) assert.Implements(t, a, s, "SPNEGOAuthenticator type does not implement the goidentity.Authenticator interface") } gokrb5-8.4.3/v8/service/cache.go000066400000000000000000000066541427031340300163160ustar00rootroot00000000000000// Package service provides server side integrations for Kerberos authentication. package service import ( "github.com/jcmturner/gokrb5/v8/types" "sync" "time" ) // Replay cache is required as specified in RFC 4120 section 3.2.3 // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets. type Cache struct { entries map[string]clientEntries mux sync.RWMutex } // clientEntries holds entries of client details sent to the service. type clientEntries struct { replayMap map[time.Time]replayCacheEntry seqNumber int64 subKey types.EncryptionKey } // Cache entry tracking client time values of tickets sent to the service. type replayCacheEntry struct { presentedTime time.Time sName types.PrincipalName cTime time.Time // This combines the ticket's CTime and Cusec } func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) { c.mux.RLock() defer c.mux.RUnlock() ce, ok := c.entries[cname.PrincipalNameString()] return ce, ok } func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) { if ce, ok := c.getClientEntries(cname); ok { c.mux.RLock() defer c.mux.RUnlock() if e, ok := ce.replayMap[t]; ok { return e, true } } return replayCacheEntry{}, false } // Instance of the ServiceCache. This needs to be a singleton. var replayCache Cache var once sync.Once // GetReplayCache returns a pointer to the Cache singleton. func GetReplayCache(d time.Duration) *Cache { // Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries once.Do(func() { replayCache = Cache{ entries: make(map[string]clientEntries), } go func() { for { // TODO consider using a context here. time.Sleep(d) replayCache.ClearOldEntries(d) } }() }) return &replayCache } // AddEntry adds an entry to the Cache. func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) { ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) if ce, ok := c.getClientEntries(a.CName); ok { c.mux.Lock() defer c.mux.Unlock() ce.replayMap[ct] = replayCacheEntry{ presentedTime: time.Now().UTC(), sName: sname, cTime: ct, } ce.seqNumber = a.SeqNumber ce.subKey = a.SubKey } else { c.mux.Lock() defer c.mux.Unlock() c.entries[a.CName.PrincipalNameString()] = clientEntries{ replayMap: map[time.Time]replayCacheEntry{ ct: { presentedTime: time.Now().UTC(), sName: sname, cTime: ct, }, }, seqNumber: a.SeqNumber, subKey: a.SubKey, } } } // ClearOldEntries clears entries from the Cache that are older than the duration provided. func (c *Cache) ClearOldEntries(d time.Duration) { c.mux.Lock() defer c.mux.Unlock() for ke, ce := range c.entries { for k, e := range ce.replayMap { if time.Now().UTC().Sub(e.presentedTime) > d { delete(ce.replayMap, k) } } if len(ce.replayMap) == 0 { delete(c.entries, ke) } } } // IsReplay tests if the Authenticator provided is a replay within the duration defined. If this is not a replay add the entry to the cache for tracking. func (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool { ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) if e, ok := c.getClientEntry(a.CName, ct); ok { if e.sName.Equal(sname) { return true } } c.AddEntry(sname, a) return false } gokrb5-8.4.3/v8/service/settings.go000066400000000000000000000111471427031340300171040ustar00rootroot00000000000000package service import ( "log" "net/http" "time" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/types" ) // Settings defines service side configuration settings. type Settings struct { Keytab *keytab.Keytab ktprinc *types.PrincipalName sname string requireHostAddr bool disablePACDecoding bool cAddr types.HostAddress maxClockSkew time.Duration logger *log.Logger sessionMgr SessionMgr } // NewSettings creates a new service Settings. func NewSettings(kt *keytab.Keytab, settings ...func(*Settings)) *Settings { s := new(Settings) s.Keytab = kt for _, set := range settings { set(s) } return s } // RequireHostAddr used to configure service side to required host addresses to be specified in Kerberos tickets. // // s := NewSettings(kt, RequireHostAddr(true)) func RequireHostAddr(b bool) func(*Settings) { return func(s *Settings) { s.requireHostAddr = b } } // RequireHostAddr indicates if the service should require the host address to be included in the ticket. func (s *Settings) RequireHostAddr() bool { return s.requireHostAddr } // DecodePAC used to configure service side to enable/disable PAC decoding if the PAC is present. // Defaults to enabled if not specified. // // s := NewSettings(kt, DecodePAC(false)) func DecodePAC(b bool) func(*Settings) { return func(s *Settings) { s.disablePACDecoding = !b } } // DecodePAC indicates whether the service should decode any PAC information present in the ticket. func (s *Settings) DecodePAC() bool { return !s.disablePACDecoding } // ClientAddress used to configure service side with the clients host address to be used during validation. // // s := NewSettings(kt, ClientAddress(h)) func ClientAddress(h types.HostAddress) func(*Settings) { return func(s *Settings) { s.cAddr = h } } // ClientAddress returns the client host address which has been provided to the service. func (s *Settings) ClientAddress() types.HostAddress { return s.cAddr } // Logger used to configure service side with a logger. // // s := NewSettings(kt, Logger(l)) func Logger(l *log.Logger) func(*Settings) { return func(s *Settings) { s.logger = l } } // Logger returns the logger instances configured for the service. If none is configured nill will be returned. func (s *Settings) Logger() *log.Logger { return s.logger } // KeytabPrincipal used to override the principal name used to find the key in the keytab. // // s := NewSettings(kt, KeytabPrincipal("someaccount")) func KeytabPrincipal(p string) func(*Settings) { return func(s *Settings) { pn, _ := types.ParseSPNString(p) s.ktprinc = &pn } } // KeytabPrincipal returns the principal name used to find the key in the keytab if it has been overridden. func (s *Settings) KeytabPrincipal() *types.PrincipalName { return s.ktprinc } // MaxClockSkew used to configure service side with the maximum acceptable clock skew // between the service and the issue time of kerberos tickets // // s := NewSettings(kt, MaxClockSkew(d)) func MaxClockSkew(d time.Duration) func(*Settings) { return func(s *Settings) { s.maxClockSkew = d } } // MaxClockSkew returns the maximum acceptable clock skew between the service and the issue time of kerberos tickets. // If none is defined a duration of 5 minutes is returned. func (s *Settings) MaxClockSkew() time.Duration { if s.maxClockSkew.Nanoseconds() == 0 { return time.Duration(5) * time.Minute } return s.maxClockSkew } // SName used provide a specific service name to the service settings. // // s := NewSettings(kt, SName("HTTP/some.service.com")) func SName(sname string) func(*Settings) { return func(s *Settings) { s.sname = sname } } // SName returns the specific service name to the service. func (s *Settings) SName() string { return s.sname } // SessionManager configures a session manager to establish sessions with clients to avoid excessive authentication challenges. // // s := NewSettings(kt, SessionManager(sm)) func SessionManager(sm SessionMgr) func(*Settings) { return func(s *Settings) { s.sessionMgr = sm } } // SessionManager returns any configured session manager. func (s *Settings) SessionManager() SessionMgr { return s.sessionMgr } // SessionMgr must provide a ways to: // // - Create new sessions and in the process add a value to the session under the key provided. // // - Get an existing session returning the value in the session under the key provided. // Return nil bytes and/or error if there is no session. type SessionMgr interface { New(w http.ResponseWriter, r *http.Request, k string, v []byte) error Get(r *http.Request, k string) ([]byte, error) } gokrb5-8.4.3/v8/spnego/000077500000000000000000000000001427031340300145445ustar00rootroot00000000000000gokrb5-8.4.3/v8/spnego/http.go000066400000000000000000000342051427031340300160560ustar00rootroot00000000000000package spnego import ( "bytes" "encoding/base64" "errors" "fmt" "io" "net" "net/http" "net/http/cookiejar" "net/url" "strings" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/goidentity/v6" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/types" ) // Client side functionality // // Client will negotiate authentication with a server using SPNEGO. type Client struct { *http.Client krb5Client *client.Client spn string reqs []*http.Request } type redirectErr struct { reqTarget *http.Request } func (e redirectErr) Error() string { return fmt.Sprintf("redirect to %v", e.reqTarget.URL) } type teeReadCloser struct { io.Reader io.Closer } // NewClient returns a SPNEGO enabled HTTP client. // Be careful when passing in the *http.Client if it is beginning reused in multiple calls to this function. // Ensure reuse of the provided *http.Client is for the same user as a session cookie may have been added to // http.Client's cookie jar. // Incorrect reuse of the provided *http.Client could lead to access to the wrong user's session. func NewClient(krb5Cl *client.Client, httpCl *http.Client, spn string) *Client { if httpCl == nil { httpCl = &http.Client{} } // Add a cookie jar if there isn't one if httpCl.Jar == nil { httpCl.Jar, _ = cookiejar.New(nil) } // Add a CheckRedirect function that will execute any functional already defined and then error with a redirectErr f := httpCl.CheckRedirect httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error { if f != nil { err := f(req, via) if err != nil { return err } } return redirectErr{reqTarget: req} } return &Client{ Client: httpCl, krb5Client: krb5Cl, spn: spn, } } // Do is the SPNEGO enabled HTTP client's equivalent of the http.Client's Do method. func (c *Client) Do(req *http.Request) (resp *http.Response, err error) { var body bytes.Buffer if req.Body != nil { // Use a tee reader to capture any body sent in case we have to replay it again teeR := io.TeeReader(req.Body, &body) teeRC := teeReadCloser{teeR, req.Body} req.Body = teeRC } resp, err = c.Client.Do(req) if err != nil { if ue, ok := err.(*url.Error); ok { if e, ok := ue.Err.(redirectErr); ok { // Picked up a redirect e.reqTarget.Header.Del(HTTPHeaderAuthRequest) c.reqs = append(c.reqs, e.reqTarget) if len(c.reqs) >= 10 { return resp, errors.New("stopped after 10 redirects") } if req.Body != nil { // Refresh the body reader so the body can be sent again e.reqTarget.Body = io.NopCloser(&body) } return c.Do(e.reqTarget) } } return resp, err } if respUnauthorizedNegotiate(resp) { err := SetSPNEGOHeader(c.krb5Client, req, c.spn) if err != nil { return resp, err } if req.Body != nil { // Refresh the body reader so the body can be sent again req.Body = io.NopCloser(&body) } io.Copy(io.Discard, resp.Body) resp.Body.Close() return c.Do(req) } return resp, err } // Get is the SPNEGO enabled HTTP client's equivalent of the http.Client's Get method. func (c *Client) Get(url string) (resp *http.Response, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } return c.Do(req) } // Post is the SPNEGO enabled HTTP client's equivalent of the http.Client's Post method. func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) { req, err := http.NewRequest("POST", url, body) if err != nil { return nil, err } req.Header.Set("Content-Type", contentType) return c.Do(req) } // PostForm is the SPNEGO enabled HTTP client's equivalent of the http.Client's PostForm method. func (c *Client) PostForm(url string, data url.Values) (resp *http.Response, err error) { return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } // Head is the SPNEGO enabled HTTP client's equivalent of the http.Client's Head method. func (c *Client) Head(url string) (resp *http.Response, err error) { req, err := http.NewRequest("HEAD", url, nil) if err != nil { return nil, err } return c.Do(req) } func respUnauthorizedNegotiate(resp *http.Response) bool { if resp.StatusCode == http.StatusUnauthorized { if resp.Header.Get(HTTPHeaderAuthResponse) == HTTPHeaderAuthResponseValueKey { return true } } return false } func setRequestSPN(r *http.Request) (types.PrincipalName, error) { h := strings.TrimSuffix(r.URL.Host, ".") // This if statement checks if the host includes a port number if strings.LastIndex(r.URL.Host, ":") > strings.LastIndex(r.URL.Host, "]") { // There is a port number in the URL h, p, err := net.SplitHostPort(h) if err != nil { return types.PrincipalName{}, err } name, err := net.LookupCNAME(h) if name != "" && err == nil { // Underlyng canonical name should be used for SPN h = strings.ToLower(name) } h = strings.TrimSuffix(h, ".") r.Host = fmt.Sprintf("%s:%s", h, p) return types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, "HTTP/"+h), nil } name, err := net.LookupCNAME(h) if name != "" && err == nil { // Underlyng canonical name should be used for SPN h = strings.ToLower(name) } h = strings.TrimSuffix(h, ".") r.Host = h return types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, "HTTP/"+h), nil } // SetSPNEGOHeader gets the service ticket and sets it as the SPNEGO authorization header on HTTP request object. // To auto generate the SPN from the request object pass a null string "". func SetSPNEGOHeader(cl *client.Client, r *http.Request, spn string) error { if spn == "" { pn, err := setRequestSPN(r) if err != nil { return err } spn = pn.PrincipalNameString() } cl.Log("using SPN %s", spn) s := SPNEGOClient(cl, spn) err := s.AcquireCred() if err != nil { return fmt.Errorf("could not acquire client credential: %v", err) } st, err := s.InitSecContext() if err != nil { return fmt.Errorf("could not initialize context: %v", err) } nb, err := st.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO") } hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb) r.Header.Set(HTTPHeaderAuthRequest, hs) return nil } // Service side functionality // const ( // spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead. spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg==" // spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead. spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC" // spnegoNegTokenRespIncompleteKRB5 - Response token specifying incomplete context and KRB5 as the supported mechtype. spnegoNegTokenRespIncompleteKRB5 = "Negotiate oRQwEqADCgEBoQsGCSqGSIb3EgECAg==" // sessionCredentials is the session value key holding the credentials jcmturner/goidentity/Identity object. sessionCredentials = "github.com/jcmturner/gokrb5/v8/sessionCredentials" // ctxCredentials is the SPNEGO context key holding the credentials jcmturner/goidentity/Identity object. ctxCredentials = "github.com/jcmturner/gokrb5/v8/ctxCredentials" // HTTPHeaderAuthRequest is the header that will hold authn/z information. HTTPHeaderAuthRequest = "Authorization" // HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server. HTTPHeaderAuthResponse = "WWW-Authenticate" // HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO. HTTPHeaderAuthResponseValueKey = "Negotiate" // UnauthorizedMsg is the message returned in the body when authentication fails. UnauthorizedMsg = "Unauthorised.\n" ) // SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper. func SPNEGOKRB5Authenticate(inner http.Handler, kt *keytab.Keytab, settings ...func(*service.Settings)) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Set up the SPNEGO GSS-API mechanism var spnego *SPNEGO h, err := types.GetHostAddress(r.RemoteAddr) if err == nil { // put in this order so that if the user provides a ClientAddress it will override the one here. o := append([]func(*service.Settings){service.ClientAddress(h)}, settings...) spnego = SPNEGOService(kt, o...) } else { spnego = SPNEGOService(kt, settings...) spnego.Log("%s - SPNEGO could not parse client address: %v", r.RemoteAddr, err) } // Check if there is a session manager and if there is an already established session for this client id, err := getSessionCredentials(spnego, r) if err == nil && id.Authenticated() { // There is an established session so bypass auth and serve spnego.Log("%s - SPNEGO request served under session %s", r.RemoteAddr, id.SessionID()) inner.ServeHTTP(w, goidentity.AddToHTTPRequestContext(&id, r)) return } st, err := getAuthorizationNegotiationHeaderAsSPNEGOToken(spnego, r, w) if st == nil || err != nil { // response to client and logging handled in function above so just return return } // Validate the context token authed, ctx, status := spnego.AcceptSecContext(st) if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded { spnegoResponseReject(spnego, w, "%s - SPNEGO validation error: %v", r.RemoteAddr, status) return } if status.Code == gssapi.StatusContinueNeeded { spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO GSS-API continue needed", r.RemoteAddr) return } if authed { // Authentication successful; get user's credentials from the context id := ctx.Value(ctxCredentials).(*credentials.Credentials) // Create a new session if a session manager has been configured err = newSession(spnego, r, w, id) if err != nil { return } spnegoResponseAcceptCompleted(spnego, w, "%s %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain()) // Add the identity to the context and serve the inner/wrapped handler inner.ServeHTTP(w, goidentity.AddToHTTPRequestContext(id, r)) return } // If we get to here we have not authenticationed so just reject spnegoResponseReject(spnego, w, "%s - SPNEGO Kerberos authentication failed", r.RemoteAddr) return }) } func getAuthorizationNegotiationHeaderAsSPNEGOToken(spnego *SPNEGO, r *http.Request, w http.ResponseWriter) (*SPNEGOToken, error) { s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2) if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey { // No Authorization header set so return 401 with WWW-Authenticate Negotiate header w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey) http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) return nil, errors.New("client did not provide a negotiation authorization header") } // Decode the header into an SPNEGO context token b, err := base64.StdEncoding.DecodeString(s[1]) if err != nil { err = fmt.Errorf("error in base64 decoding negotiation header: %v", err) spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO %v", r.RemoteAddr, err) return nil, err } var st SPNEGOToken err = st.Unmarshal(b) if err != nil { // Check if this is a raw KRB5 context token - issue #347. var k5t KRB5Token if k5t.Unmarshal(b) != nil { err = fmt.Errorf("error in unmarshaling SPNEGO token: %v", err) spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO %v", r.RemoteAddr, err) return nil, err } // Wrap it into an SPNEGO context token st.Init = true st.NegTokenInit = NegTokenInit{ MechTypes: []asn1.ObjectIdentifier{k5t.OID}, MechTokenBytes: b, } } return &st, nil } func getSessionCredentials(spnego *SPNEGO, r *http.Request) (credentials.Credentials, error) { var creds credentials.Credentials // Check if there is a session manager and if there is an already established session for this client if sm := spnego.serviceSettings.SessionManager(); sm != nil { cb, err := sm.Get(r, sessionCredentials) if err != nil || cb == nil || len(cb) < 1 { return creds, fmt.Errorf("%s - SPNEGO error getting session and credentials for request: %v", r.RemoteAddr, err) } err = creds.Unmarshal(cb) if err != nil { return creds, fmt.Errorf("%s - SPNEGO credentials malformed in session: %v", r.RemoteAddr, err) } return creds, nil } return creds, errors.New("no session manager configured") } func newSession(spnego *SPNEGO, r *http.Request, w http.ResponseWriter, id *credentials.Credentials) error { if sm := spnego.serviceSettings.SessionManager(); sm != nil { // create new session idb, err := id.Marshal() if err != nil { spnegoInternalServerError(spnego, w, "SPNEGO could not marshal credentials to add to the session: %v", err) return err } err = sm.New(w, r, sessionCredentials, idb) if err != nil { spnegoInternalServerError(spnego, w, "SPNEGO could not create new session: %v", err) return err } spnego.Log("%s %s@%s - SPNEGO new session (%s) created", r.RemoteAddr, id.UserName(), id.Domain(), id.SessionID()) } return nil } // Log and respond to client for error conditions func spnegoNegotiateKRB5MechType(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespIncompleteKRB5) http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) } func spnegoResponseReject(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject) http.Error(w, UnauthorizedMsg, http.StatusUnauthorized) } func spnegoResponseAcceptCompleted(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted) } func spnegoInternalServerError(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) { s.Log(format, v...) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } gokrb5-8.4.3/v8/spnego/http_test.go000066400000000000000000000314031427031340300171120ustar00rootroot00000000000000package spnego import ( "bytes" "crypto/rand" "encoding/base64" "encoding/hex" "errors" "fmt" "io" "log" "mime/multipart" "net/http" "net/http/cookiejar" "net/http/httptest" "os" "sync" "testing" "github.com/gorilla/sessions" "github.com/jcmturner/goidentity/v6" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/test" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestClient_SetSPNEGOHeader(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} l := log.New(os.Stderr, "SPNEGO Client:", log.LstdFlags) cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c, client.Logger(l)) err := cl.Login() if err != nil { t.Fatalf("error on AS_REQ: %v\n", err) } urls := []string{ "http://cname.test.gokrb5", "http://host.test.gokrb5", } paths := []string{ "/modkerb/index.html", //"/modgssapi/index.html", } for _, url := range urls { for _, p := range paths { r, _ := http.NewRequest("GET", url+p, nil) httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("%s request error: %v", url+p, err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected") err = SetSPNEGOHeader(cl, r, "") if err != nil { t.Fatalf("error setting client SPNEGO header: %v", err) } httpResp, err = http.DefaultClient.Do(r) if err != nil { t.Fatalf("%s request error: %v\n", url+p, err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } } } func TestSPNEGOHTTPClient(t *testing.T) { test.Integration(t) b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} l := log.New(os.Stderr, "SPNEGO Client:", log.LstdFlags) cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c, client.Logger(l)) err := cl.Login() if err != nil { t.Fatalf("error on AS_REQ: %v\n", err) } urls := []string{ "http://cname.test.gokrb5", "http://host.test.gokrb5", } // This path issues a redirect which the http client will automatically follow. // It should cause a replay issue if the negInit token is sent in the first instance. paths := []string{ "/modgssapi", // This issues a redirect which the http client will automatically follow. Could cause a replay issue "/redirect", } for _, url := range urls { for _, p := range paths { r, _ := http.NewRequest("GET", url+p, nil) httpCl := http.DefaultClient httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error { t.Logf("http client redirect: %+v", *req) return nil } spnegoCl := NewClient(cl, httpCl, "") httpResp, err := spnegoCl.Do(r) if err != nil { t.Fatalf("%s request error: %v", url+p, err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } } } func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) { s := httpServer() defer s.Close() r, _ := http.NewRequest("GET", s.URL, nil) httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected") assert.Equal(t, "Negotiate", httpResp.Header.Get("WWW-Authenticate"), "Negotiation header not set by server.") } func TestService_SPNEGOKRB_ValidUser(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() r, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() err := SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } func TestService_SPNEGOKRB_ValidUser_RawKRB5Token(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() r, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() sc := SPNEGOClient(cl, "HTTP/host.test.gokrb5") err := sc.AcquireCred() if err != nil { t.Fatalf("could not acquire client credential: %v", err) } st, err := sc.InitSecContext() if err != nil { t.Fatalf("could not initialize context: %v", err) } // Use the raw KRB5 context token nb := st.(*SPNEGOToken).NegTokenInit.MechTokenBytes hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb) r.Header.Set(HTTPHeaderAuthRequest, hs) httpResp, err := http.DefaultClient.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") } func TestService_SPNEGOKRB_Replay(t *testing.T) { test.Integration(t) s := httpServerWithoutSessionManager() defer s.Close() r1, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } // First request with this ticket should be accepted httpResp, err := http.DefaultClient.Do(r1) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") // Use ticket again should be rejected httpResp, err = http.DefaultClient.Do(r1) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.") // Form a 2nd ticket r2, _ := http.NewRequest("GET", s.URL, nil) err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } // First use of 2nd ticket should be accepted httpResp, err = http.DefaultClient.Do(r2) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected") // Using the 1st ticket again should still be rejected httpResp, err = http.DefaultClient.Do(r1) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.") // Using the 2nd again should be rejected as replay httpResp, err = http.DefaultClient.Do(r2) if err != nil { t.Fatalf("Request error: %v\n", err) } assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.") } func TestService_SPNEGOKRB_ReplayCache_Concurrency(t *testing.T) { test.Integration(t) s := httpServerWithoutSessionManager() defer s.Close() r1, _ := http.NewRequest("GET", s.URL, nil) cl := getClient() err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } r1h := r1.Header.Get(HTTPHeaderAuthRequest) r2, _ := http.NewRequest("GET", s.URL, nil) err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5") if err != nil { t.Fatalf("error setting client's SPNEGO header: %v", err) } r2h := r2.Header.Get(HTTPHeaderAuthRequest) // Concurrent 1st requests should be OK var wg sync.WaitGroup wg.Add(2) go httpGet(r1, &wg) go httpGet(r2, &wg) wg.Wait() // A number of concurrent requests with the same ticket should be rejected due to replay var wg2 sync.WaitGroup noReq := 10 wg2.Add(noReq * 2) for i := 0; i < noReq; i++ { rr1, _ := http.NewRequest("GET", s.URL, nil) rr1.Header.Set(HTTPHeaderAuthRequest, r1h) rr2, _ := http.NewRequest("GET", s.URL, nil) rr2.Header.Set(HTTPHeaderAuthRequest, r2h) go httpGet(rr1, &wg2) go httpGet(rr2, &wg2) } wg2.Wait() } func TestService_SPNEGOKRB_Upload(t *testing.T) { test.Integration(t) s := httpServer() defer s.Close() bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) fileWriter, err := bodyWriter.CreateFormFile("uploadfile", "testfile.bin") if err != nil { t.Fatalf("error writing to buffer: %v", err) } data := make([]byte, 10240) rand.Read(data) br := bytes.NewReader(data) _, err = io.Copy(fileWriter, br) if err != nil { t.Fatalf("error copying bytes: %v", err) } bodyWriter.Close() r, _ := http.NewRequest("POST", s.URL, bodyBuf) r.Header.Set("Content-Type", bodyWriter.FormDataContentType()) cl := getClient() cookieJar, _ := cookiejar.New(nil) httpCl := http.DefaultClient httpCl.Jar = cookieJar spnegoCl := NewClient(cl, httpCl, "HTTP/host.test.gokrb5") httpResp, err := spnegoCl.Do(r) if err != nil { t.Fatalf("Request error: %v\n", err) } if httpResp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(httpResp.Body) bodyString := string(bodyBytes) httpResp.Body.Close() t.Errorf("unexpected code from http server (%d): %s", httpResp.StatusCode, bodyString) } } func httpGet(r *http.Request, wg *sync.WaitGroup) { defer wg.Done() http.DefaultClient.Do(r) } func httpServerWithoutSessionManager() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.LstdFlags) b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, service.Logger(l))) return s } func httpServer() *httptest.Server { l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.LstdFlags) b, _ := hex.DecodeString(testdata.HTTP_KEYTAB) kt := keytab.New() kt.Unmarshal(b) th := http.HandlerFunc(testAppHandler) s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, service.Logger(l), service.SessionManager(NewSessionMgr("gokrb5")))) return s } func testAppHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { maxUploadSize := int64(11240) if err := r.ParseMultipartForm(maxUploadSize); err != nil { http.Error(w, fmt.Sprintf("cannot parse multipart form: %v", err), http.StatusBadRequest) return } r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) file, _, err := r.FormFile("uploadfile") if err != nil { http.Error(w, "INVALID_FILE", http.StatusBadRequest) return } defer file.Close() // write out to /dev/null _, err = io.Copy(io.Discard, file) if err != nil { http.Error(w, "WRITE_ERR", http.StatusInternalServerError) return } } w.WriteHeader(http.StatusOK) id := goidentity.FromHTTPRequestContext(r) fmt.Fprintf(w, "\nTEST.GOKRB5 Handler\nAuthenticed user: %s\nUser's realm: %s\n", id.UserName(), id.Domain()) return } func getClient() *client.Client { b, _ := hex.DecodeString(testdata.KEYTAB_TESTUSER1_TEST_GOKRB5) kt := keytab.New() kt.Unmarshal(b) c, _ := config.NewFromString(testdata.KRB5_CONF) c.LibDefaults.NoAddresses = true addr := os.Getenv("TEST_KDC_ADDR") if addr == "" { addr = testdata.KDC_IP_TEST_GOKRB5 } c.Realms[0].KDC = []string{addr + ":" + testdata.KDC_PORT_TEST_GOKRB5} c.Realms[0].KPasswdServer = []string{addr + ":464"} cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c) return cl } type SessionMgr struct { skey []byte store sessions.Store cookieName string } func NewSessionMgr(cookieName string) SessionMgr { skey := []byte("thisistestsecret") // Best practice is to load this key from a secure location. return SessionMgr{ skey: skey, store: sessions.NewCookieStore(skey), cookieName: cookieName, } } func (smgr SessionMgr) Get(r *http.Request, k string) ([]byte, error) { s, err := smgr.store.Get(r, smgr.cookieName) if err != nil { return nil, err } if s == nil { return nil, errors.New("nil session") } b, ok := s.Values[k].([]byte) if !ok { return nil, fmt.Errorf("could not get bytes held in session at %s", k) } return b, nil } func (smgr SessionMgr) New(w http.ResponseWriter, r *http.Request, k string, v []byte) error { s, err := smgr.store.New(r, smgr.cookieName) if err != nil { return fmt.Errorf("could not get new session from session manager: %v", err) } s.Values[k] = v return s.Save(r, w) } gokrb5-8.4.3/v8/spnego/krb5Token.go000066400000000000000000000145411427031340300167440ustar00rootroot00000000000000package spnego import ( "context" "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/types" ) // GSSAPI KRB5 MechToken IDs. const ( TOK_ID_KRB_AP_REQ = "0100" TOK_ID_KRB_AP_REP = "0200" TOK_ID_KRB_ERROR = "0300" ) // KRB5Token context token implementation for GSSAPI. type KRB5Token struct { OID asn1.ObjectIdentifier tokID []byte APReq messages.APReq APRep messages.APRep KRBError messages.KRBError settings *service.Settings context context.Context } // Marshal a KRB5Token into a slice of bytes. func (m *KRB5Token) Marshal() ([]byte, error) { // Create the header b, _ := asn1.Marshal(m.OID) b = append(b, m.tokID...) var tb []byte var err error switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: tb, err = m.APReq.Marshal() if err != nil { return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err) } case TOK_ID_KRB_AP_REP: return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5") case TOK_ID_KRB_ERROR: return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5") } if err != nil { return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err) } b = append(b, tb...) return asn1tools.AddASNAppTag(b, 0), nil } // Unmarshal a KRB5Token. func (m *KRB5Token) Unmarshal(b []byte) error { var oid asn1.ObjectIdentifier r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err) } if !oid.Equal(gssapi.OIDKRB5.OID()) { return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", oid.String(), gssapi.OIDKRB5.OID().String()) } m.OID = oid if len(r) < 2 { return fmt.Errorf("krb5token too short") } m.tokID = r[0:2] switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: var a messages.APReq err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err) } m.APReq = a case TOK_ID_KRB_AP_REP: var a messages.APRep err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err) } m.APRep = a case TOK_ID_KRB_ERROR: var a messages.KRBError err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err) } m.KRBError = a } return nil } // Verify a KRB5Token. func (m *KRB5Token) Verify() (bool, gssapi.Status) { switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: ok, creds, err := service.VerifyAPREQ(&m.APReq, m.settings) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"} } m.context = context.Background() m.context = context.WithValue(m.context, ctxCredentials, creds) return true, gssapi.Status{Code: gssapi.StatusComplete} case TOK_ID_KRB_AP_REP: // Client side // TODO how to verify the AP_REP - not yet implemented return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"} case TOK_ID_KRB_ERROR: if m.KRBError.MsgType != msgtype.KRB_ERROR { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"} } return true, gssapi.Status{Code: gssapi.StatusUnavailable} } return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"} } // IsAPReq tests if the MechToken contains an AP_REQ. func (m *KRB5Token) IsAPReq() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ { return true } return false } // IsAPRep tests if the MechToken contains an AP_REP. func (m *KRB5Token) IsAPRep() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP { return true } return false } // IsKRBError tests if the MechToken contains an KRB_ERROR. func (m *KRB5Token) IsKRBError() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR { return true } return false } // Context returns the KRB5 token's context which will contain any verify user identity information. func (m *KRB5Token) Context() context.Context { return m.context } // NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) { // TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client. var m KRB5Token m.OID = gssapi.OIDKRB5.OID() tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ) m.tokID = tb auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags) if err != nil { return m, err } APReq, err := messages.NewAPReq( tkt, sessionKey, auth, ) if err != nil { return m, err } for _, o := range APOptions { types.SetFlag(&APReq.APOptions, o) } m.APReq = APReq return m, nil } // krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) { //RFC 4121 Section 4.1.1 auth, err := types.NewAuthenticator(creds.Domain(), creds.CName()) if err != nil { return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") } auth.Cksum = types.Checksum{ CksumType: chksumtype.GSSAPI, Checksum: newAuthenticatorChksum(flags), } return auth, nil } // Create new authenticator checksum for kerberos MechToken func newAuthenticatorChksum(flags []int) []byte { a := make([]byte, 24) binary.LittleEndian.PutUint32(a[:4], 16) for _, i := range flags { if i == gssapi.ContextFlagDeleg { x := make([]byte, 28-len(a)) a = append(a, x...) } f := binary.LittleEndian.Uint32(a[20:24]) f |= uint32(i) binary.LittleEndian.PutUint32(a[20:24], f) } return a } gokrb5-8.4.3/v8/spnego/krb5Token_test.go000066400000000000000000000155021427031340300200010ustar00rootroot00000000000000package spnego import ( "encoding/hex" "math" "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) const ( KRB5TokenHex = "6082026306092a864886f71201020201006e8202523082024ea003020105a10302010ea20703050000000000a382015d6182015930820155a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382011830820114a003020112a103020103a28201060482010230621d868c97f30bf401e03bbffcd724bd9d067dce2afc31f71a356449b070cdafcc1ff372d0eb1e7a708b50c0152f3996c45b1ea312a803907fb97192d39f20cdcaea29876190f51de6e2b4a4df0460122ed97f363434e1e120b0e76c172b4424a536987152ac0b73013ab88af4b13a3fcdc63f739039dd46d839709cf5b51bb0ce6cb3af05fab3844caac280929955495235e9d0424f8a1fb9b4bd4f6bba971f40b97e9da60b9dabfcf0b1feebfca02c9a19b327a0004aa8e19192726cf347561fa8ac74afad5d6a264e50cf495b93aac86c77b2bc2d184234f6c2767dbea431485a25687b9044a20b601e968efaefffa1fc5283ff32aa6a53cb6c5cdd2eddcb26a481d73081d4a003020112a103020103a281c70481c4a1b29e420324f7edf9efae39df7bcaaf196a3160cf07e72f52a4ef8a965721b2f3343719c50699046e4fcc18ca26c2bfc7e4a9eddfc9d9cfc57ff2f6bdbbd1fc40ac442195bc669b9a0dbba12563b3e4cac9f4022fc01b8aa2d1ab84815bb078399ff7f4d5f9815eef896a0c7e3c049e6fd9932b97096cdb5861425b9d81753d0743212ded1a0fb55a00bf71a46be5ce5e1c8a5cc327b914347d9efcb6cb31ca363b1850d95c7b6c4c3cc6301615ad907318a0c5379d343610fab17eca9c7dc0a5a60658" AuthChksum = "100000000000000000000000000000000000000030000000" ) func TestKRB5Token_Unmarshal(t *testing.T) { t.Parallel() b, err := hex.DecodeString(KRB5TokenHex) if err != nil { t.Fatalf("Error decoding KRB5Token hex: %v", err) } var mt KRB5Token err = mt.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling KRB5Token: %v", err) } assert.Equal(t, gssapi.OIDKRB5.OID(), mt.OID, "KRB5Token OID not as expected.") assert.Equal(t, []byte{1, 0}, mt.tokID, "TokID not as expected") assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "KRB5Token AP_REQ does not have the right message type.") assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in KRB5Token does not indicate no error.") assert.Equal(t, int32(18), mt.APReq.EncryptedAuthenticator.EType, "Authenticator within AP_REQ does not have the etype expected.") } func TestKRB5Token_newAuthenticatorChksum(t *testing.T) { t.Parallel() b, err := hex.DecodeString(AuthChksum) if err != nil { t.Fatalf("Error decoding KRB5Token hex: %v", err) } cb := newAuthenticatorChksum([]int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}) assert.Equal(t, b, cb, "SPNEGO Authenticator checksum not as expected") } // Test with explicit subkey generation. func TestKRB5Token_newAuthenticatorWithSubkeyGeneration(t *testing.T) { t.Parallel() creds := credentials.New("hftsai", testdata.TEST_REALM) creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING}) var etypeID int32 = 18 keyLen := 32 // etypeID 18 refers to AES256 -> 32 bytes key a, err := krb5TokenAuthenticator(creds, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}) if err != nil { t.Fatalf("Error creating authenticator: %v", err) } a.GenerateSeqNumberAndSubKey(etypeID, keyLen) assert.Equal(t, int32(32771), a.Cksum.CksumType, "Checksum type in authenticator for SPNEGO mechtoken not as expected.") assert.Equal(t, etypeID, a.SubKey.KeyType, "Subkey not of the expected type.") assert.Equal(t, keyLen, len(a.SubKey.KeyValue), "Subkey value not of the right length") var nz bool for _, b := range a.SubKey.KeyValue { if b != byte(0) { nz = true } } assert.True(t, nz, "subkey not initialised") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber > 0 }), "Sequence number is not greater than zero") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber <= math.MaxUint32 })) } // Test without subkey generation. func TestKRB5Token_newAuthenticator(t *testing.T) { t.Parallel() creds := credentials.New("hftsai", testdata.TEST_REALM) creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING}) a, err := krb5TokenAuthenticator(creds, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}) if err != nil { t.Fatalf("Error creating authenticator: %v", err) } assert.Equal(t, int32(32771), a.Cksum.CksumType, "Checksum type in authenticator for SPNEGO mechtoken not as expected.") assert.Equal(t, int32(0), a.SubKey.KeyType, "Subkey not of the expected type.") assert.Nil(t, a.SubKey.KeyValue, "Subkey should not be set.") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber > 0 }), "Sequence number is not greater than zero") assert.Condition(t, assert.Comparison(func() bool { return a.SeqNumber <= math.MaxUint32 })) } func TestNewAPREQKRB5Token_and_Marshal(t *testing.T) { t.Parallel() creds := credentials.New("hftsai", testdata.TEST_REALM) creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING}) cl := client.Client{ Credentials: creds, } var tkt messages.Ticket b, err := hex.DecodeString(testdata.MarshaledKRB5ticket) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = tkt.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } key := types.EncryptionKey{ KeyType: 18, KeyValue: make([]byte, 32), } mt, err := NewKRB5TokenAPREQ(&cl, tkt, key, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{}) if err != nil { t.Fatalf("Error creating KRB5Token: %v", err) } mb, err := mt.Marshal() if err != nil { t.Fatalf("Error unmarshalling KRB5Token: %v", err) } err = mt.Unmarshal(mb) if err != nil { t.Fatalf("Error unmarshalling KRB5Token: %v", err) } assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, mt.OID, "KRB5Token OID not as expected.") assert.Equal(t, []byte{1, 0}, mt.tokID, "TokID not as expected") assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "KRB5Token AP_REQ does not have the right message type.") assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in KRB5Token does not indicate no error.") assert.Equal(t, testdata.TEST_REALM, mt.APReq.Ticket.Realm, "Realm in ticket within the AP_REQ of the KRB5Token not as expected.") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, mt.APReq.Ticket.SName.NameString, "SName in ticket within the AP_REQ of the KRB5Token not as expected.") assert.Equal(t, int32(18), mt.APReq.EncryptedAuthenticator.EType, "Authenticator within AP_REQ does not have the etype expected.") } gokrb5-8.4.3/v8/spnego/negotiationToken.go000066400000000000000000000211271427031340300204170ustar00rootroot00000000000000package spnego import ( "context" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/types" ) // https://msdn.microsoft.com/en-us/library/ms995330.aspx // Negotiation state values. const ( NegStateAcceptCompleted NegState = 0 NegStateAcceptIncomplete NegState = 1 NegStateReject NegState = 2 NegStateRequestMIC NegState = 3 ) // NegState is a type to indicate the SPNEGO negotiation state. type NegState int // NegTokenInit implements Negotiation Token of type Init. type NegTokenInit struct { MechTypes []asn1.ObjectIdentifier ReqFlags asn1.BitString MechTokenBytes []byte MechListMIC []byte mechToken gssapi.ContextToken settings *service.Settings } type marshalNegTokenInit struct { MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"` ReqFlags asn1.BitString `asn1:"explicit,optional,tag:1"` MechTokenBytes []byte `asn1:"explicit,optional,omitempty,tag:2"` MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens } // NegTokenResp implements Negotiation Token of type Resp/Targ type NegTokenResp struct { NegState asn1.Enumerated SupportedMech asn1.ObjectIdentifier ResponseToken []byte MechListMIC []byte mechToken gssapi.ContextToken settings *service.Settings } type marshalNegTokenResp struct { NegState asn1.Enumerated `asn1:"explicit,tag:0"` SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"` ResponseToken []byte `asn1:"explicit,optional,omitempty,tag:2"` MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens } // NegTokenTarg implements Negotiation Token of type Resp/Targ type NegTokenTarg NegTokenResp // Marshal an Init negotiation token func (n *NegTokenInit) Marshal() ([]byte, error) { m := marshalNegTokenInit{ MechTypes: n.MechTypes, ReqFlags: n.ReqFlags, MechTokenBytes: n.MechTokenBytes, MechListMIC: n.MechListMIC, } b, err := asn1.Marshal(m) if err != nil { return nil, err } nt := asn1.RawValue{ Tag: 0, Class: 2, IsCompound: true, Bytes: b, } nb, err := asn1.Marshal(nt) if err != nil { return nil, err } return nb, nil } // Unmarshal an Init negotiation token func (n *NegTokenInit) Unmarshal(b []byte) error { init, nt, err := UnmarshalNegToken(b) if err != nil { return err } if !init { return errors.New("bytes were not that of a NegTokenInit") } nInit := nt.(NegTokenInit) n.MechTokenBytes = nInit.MechTokenBytes n.MechListMIC = nInit.MechListMIC n.MechTypes = nInit.MechTypes n.ReqFlags = nInit.ReqFlags return nil } // Verify an Init negotiation token func (n *NegTokenInit) Verify() (bool, gssapi.Status) { // Check if supported mechanisms are in the MechTypeList var mtSupported bool for _, m := range n.MechTypes { if m.Equal(gssapi.OIDKRB5.OID()) || m.Equal(gssapi.OIDMSLegacyKRB5.OID()) { if n.mechToken == nil && n.MechTokenBytes == nil { return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} } mtSupported = true break } } if !mtSupported { return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"} } // There should be some mechtoken bytes for a KRB5Token (other mech types are not supported) mt := new(KRB5Token) mt.settings = n.settings if n.mechToken == nil { err := mt.Unmarshal(n.MechTokenBytes) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } n.mechToken = mt } else { var ok bool mt, ok = n.mechToken.(*KRB5Token) if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"} } } // Verify the mechtoken return n.mechToken.Verify() } // Context returns the SPNEGO context which will contain any verify user identity information. func (n *NegTokenInit) Context() context.Context { if n.mechToken != nil { mt, ok := n.mechToken.(*KRB5Token) if !ok { return nil } return mt.Context() } return nil } // Marshal a Resp/Targ negotiation token func (n *NegTokenResp) Marshal() ([]byte, error) { m := marshalNegTokenResp{ NegState: n.NegState, SupportedMech: n.SupportedMech, ResponseToken: n.ResponseToken, MechListMIC: n.MechListMIC, } b, err := asn1.Marshal(m) if err != nil { return nil, err } nt := asn1.RawValue{ Tag: 1, Class: 2, IsCompound: true, Bytes: b, } nb, err := asn1.Marshal(nt) if err != nil { return nil, err } return nb, nil } // Unmarshal a Resp/Targ negotiation token func (n *NegTokenResp) Unmarshal(b []byte) error { init, nt, err := UnmarshalNegToken(b) if err != nil { return err } if init { return errors.New("bytes were not that of a NegTokenResp") } nResp := nt.(NegTokenResp) n.MechListMIC = nResp.MechListMIC n.NegState = nResp.NegState n.ResponseToken = nResp.ResponseToken n.SupportedMech = nResp.SupportedMech return nil } // Verify a Resp/Targ negotiation token func (n *NegTokenResp) Verify() (bool, gssapi.Status) { if n.SupportedMech.Equal(gssapi.OIDKRB5.OID()) || n.SupportedMech.Equal(gssapi.OIDMSLegacyKRB5.OID()) { if n.mechToken == nil && n.ResponseToken == nil { return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} } mt := new(KRB5Token) mt.settings = n.settings if n.mechToken == nil { err := mt.Unmarshal(n.ResponseToken) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } n.mechToken = mt } else { var ok bool mt, ok = n.mechToken.(*KRB5Token) if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"} } } if mt == nil { return false, gssapi.Status{Code: gssapi.StatusContinueNeeded} } // Verify the mechtoken return mt.Verify() } return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"} } // State returns the negotiation state of the negotiation response. func (n *NegTokenResp) State() NegState { return NegState(n.NegState) } // Context returns the SPNEGO context which will contain any verify user identity information. func (n *NegTokenResp) Context() context.Context { if n.mechToken != nil { mt, ok := n.mechToken.(*KRB5Token) if !ok { return nil } return mt.Context() } return nil } // UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp. // // The boolean indicates if the response is a NegTokenInit. // If error is nil and the boolean is false the response is a NegTokenResp. func UnmarshalNegToken(b []byte) (bool, interface{}, error) { var a asn1.RawValue _, err := asn1.Unmarshal(b, &a) if err != nil { return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err) } switch a.Tag { case 0: var n marshalNegTokenInit _, err = asn1.Unmarshal(a.Bytes, &n) if err != nil { return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err) } nt := NegTokenInit{ MechTypes: n.MechTypes, ReqFlags: n.ReqFlags, MechTokenBytes: n.MechTokenBytes, MechListMIC: n.MechListMIC, } return true, nt, nil case 1: var n marshalNegTokenResp _, err = asn1.Unmarshal(a.Bytes, &n) if err != nil { return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err) } nt := NegTokenResp{ NegState: n.NegState, SupportedMech: n.SupportedMech, ResponseToken: n.ResponseToken, MechListMIC: n.MechListMIC, } return false, nt, nil default: return false, nil, errors.New("unknown choice type for NegotiationToken") } } // NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5 func NewNegTokenInitKRB5(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) { mt, err := NewKRB5TokenAPREQ(cl, tkt, sessionKey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{}) if err != nil { return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", err) } mtb, err := mt.Marshal() if err != nil { return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", err) } return NegTokenInit{ MechTypes: []asn1.ObjectIdentifier{gssapi.OIDKRB5.OID()}, MechTokenBytes: mtb, }, nil } gokrb5-8.4.3/v8/spnego/negotiationToken_test.go000066400000000000000000000107621427031340300214610ustar00rootroot00000000000000package spnego import ( "encoding/hex" "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" ) const ( testNegTokenInit = "a08202aa308202a6a027302506092a864886f71201020206052b0501050206092a864882f71201020206062b0601050205a2820279048202756082027106092a864886f71201020201006e8202603082025ca003020105a10302010ea20703050000000000a38201706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020103a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020102a282011904820115d4bd890abc456f44e2e7a2e8111bd6767abf03266dfcda97c629af2ece450a5ae1f145e4a4d1bc2c848e66a6c6b31d9740b26b03cdbd2570bfcf126e90adf5f5ebce9e283ff5086da47b129b14fc0aabd4d1df9c1f3c72b80cc614dfc28783450b2c7b7749651f432b47aaa2ff158c0066b757f3fb00dd7b4f63d68276c76373ecdd3f19c66ebc43a81e577f3c263b878356f57e8d6c4eccd587b81538e70392cf7e73fc12a6f7c537a894a7bb5566c83ac4d69757aa320a51d8d690017aebf952add1889adfc3307b0e6cd8c9b57cf8589fbe52800acb6461c25473d49faa1bdceb8bce3f61db23f9cd6a09d5adceb411e1c4546b30b33331e570fd6bc50aa403557e75f488e759750ea038aab6454667d9b64f41a481d23081cfa003020112a281c70481c4d67ba2ae4cf5d917caab1d863605249320e90482563662ed92408a543b6ad5edeb8f9375e9060a205491df082fd2a5fec93dfb76f41012bb60cae20f07adbb77a1aa56f0521f36e1ea10dc9fb762902b254dd7664d0bcc6f751f2003e41990af1b4330d10477bfad638b9f0b704ac80cc47731f8ec8d801762bad8884b8de90adb1dbe7fc7b0ffafd38fb5eb8b6547cee30d89873281ce63ad70042a13478b1a7c2bdde0f223ace62dbb84e2d06f1070f4265f66e0544449335e2fcc4d0aee5bf81c5999" testNegTokenResp = "a1143012a0030a0100a10b06092a864886f712010202" ) func TestUnmarshal_negTokenInit(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } isInit, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } assert.IsType(t, NegTokenInit{}, nt, "Not the expected type NegTokenInit") assert.True(t, isInit, "Boolean indicating type is negTokenInit is not true") nInit := nt.(NegTokenInit) assert.Equal(t, 4, len(nInit.MechTypes)) expectMechTypes := []asn1.ObjectIdentifier{ []int{1, 2, 840, 113554, 1, 2, 2}, []int{1, 3, 5, 1, 5, 2}, []int{1, 2, 840, 48018, 1, 2, 2}, []int{1, 3, 6, 1, 5, 2, 5}, } assert.Equal(t, expectMechTypes, nInit.MechTypes, "MechTypes list in NegTokenInit not as expected") } func TestMarshal_negTokenInit(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } _, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } nInit := nt.(NegTokenInit) mb, err := nInit.Marshal() if err != nil { t.Fatalf("Error marshalling negotiation init token: %v", err) } assert.Equal(t, b, mb, "Marshalled bytes not as expected for NegTokenInit") } func TestUnmarshal_negTokenResp(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } isInit, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } assert.IsType(t, NegTokenResp{}, nt, "Not the expected type NegTokenResp") assert.False(t, isInit, "Boolean indicating type is negTokenInit is not false") nResp := nt.(NegTokenResp) assert.Equal(t, asn1.Enumerated(0), nResp.NegState) assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, nResp.SupportedMech, "SupportedMech type not as expected.") } func TestMarshal_negTokenResp(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testNegTokenResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } _, nt, err := UnmarshalNegToken(b) if err != nil { t.Fatalf("Error unmarshalling negotiation token: %v", err) } nResp := nt.(NegTokenResp) mb, err := nResp.Marshal() if err != nil { t.Fatalf("Error marshalling negotiation init token: %v", err) } assert.Equal(t, b, mb, "Marshalled bytes not as expected for NegTokenResp") } func TestUnmarshal_negTokenInitWithReqFlags(t *testing.T) { mhex := "a01e301ca00d300b06092a864886f712010202a10403020176a2050403010203" mb, err := hex.DecodeString(mhex) if err != nil { t.Fatal(err) } var m NegTokenInit err = m.Unmarshal(mb) if err != nil { t.Fatal(err) } if len(m.MechTokenBytes) != 3 { t.Errorf("unmarshal did not return the correct number of mechToken bytes") } } gokrb5-8.4.3/v8/spnego/spnego.go000066400000000000000000000136401427031340300163720ustar00rootroot00000000000000// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication. package spnego import ( "context" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/service" ) // SPNEGO implements the GSS-API mechanism for RFC 4178 type SPNEGO struct { serviceSettings *service.Settings client *client.Client spn string } // SPNEGOClient configures the SPNEGO mechanism suitable for client side use. func SPNEGOClient(cl *client.Client, spn string) *SPNEGO { s := new(SPNEGO) s.client = cl s.spn = spn s.serviceSettings = service.NewSettings(nil, service.SName(spn)) return s } // SPNEGOService configures the SPNEGO mechanism suitable for service side use. func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO { s := new(SPNEGO) s.serviceSettings = service.NewSettings(kt, options...) return s } // OID returns the GSS-API assigned OID for SPNEGO. func (s *SPNEGO) OID() asn1.ObjectIdentifier { return gssapi.OIDSPNEGO.OID() } // AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO. func (s *SPNEGO) AcquireCred() error { return s.client.AffirmLogin() } // InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos. func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) { tkt, key, err := s.client.GetServiceTicket(s.spn) if err != nil { return &SPNEGOToken{}, err } negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key) if err != nil { return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err) } return &SPNEGOToken{ Init: true, NegTokenInit: negTokenInit, settings: s.serviceSettings, }, nil } // AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and // establish a context. func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) { var ctx context.Context t, ok := ct.(*SPNEGOToken) if !ok { return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"} } t.settings = s.serviceSettings var oid asn1.ObjectIdentifier if t.Init { oid = t.NegTokenInit.MechTypes[0] } if t.Resp { oid = t.NegTokenResp.SupportedMech } if !(oid.Equal(gssapi.OIDKRB5.OID()) || oid.Equal(gssapi.OIDMSLegacyKRB5.OID())) { return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"} } // Flags in the NegInit must be used t.NegTokenInit.ReqFlags ok, status := t.Verify() ctx = t.Context() return ok, ctx, status } // Log will write to the service's logger if it is configured. func (s *SPNEGO) Log(format string, v ...interface{}) { if s.serviceSettings.Logger() != nil { s.serviceSettings.Logger().Output(2, fmt.Sprintf(format, v...)) } } // SPNEGOToken is a GSS-API context token type SPNEGOToken struct { Init bool Resp bool NegTokenInit NegTokenInit NegTokenResp NegTokenResp settings *service.Settings context context.Context } // Marshal SPNEGO context token func (s *SPNEGOToken) Marshal() ([]byte, error) { var b []byte if s.Init { hb, _ := asn1.Marshal(gssapi.OIDSPNEGO.OID()) tb, err := s.NegTokenInit.Marshal() if err != nil { return b, fmt.Errorf("could not marshal NegTokenInit: %v", err) } b = append(hb, tb...) return asn1tools.AddASNAppTag(b, 0), nil } if s.Resp { b, err := s.NegTokenResp.Marshal() if err != nil { return b, fmt.Errorf("could not marshal NegTokenResp: %v", err) } return b, nil } return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp") } // Unmarshal SPNEGO context token func (s *SPNEGOToken) Unmarshal(b []byte) error { var r []byte var err error // We need some data in the array if len(b) < 1 { return fmt.Errorf("provided byte array is empty") } if b[0] != byte(161) { // Not a NegTokenResp/Targ could be a NegTokenInit var oid asn1.ObjectIdentifier r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) if err != nil { return fmt.Errorf("not a valid SPNEGO token: %v", err) } // Check the OID is the SPNEGO OID value SPNEGOOID := gssapi.OIDSPNEGO.OID() if !oid.Equal(SPNEGOOID) { return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String()) } } else { // Could be a NegTokenResp/Targ r = b } _, nt, err := UnmarshalNegToken(r) if err != nil { return err } switch v := nt.(type) { case NegTokenInit: s.Init = true s.NegTokenInit = v s.NegTokenInit.settings = s.settings case NegTokenResp: s.Resp = true s.NegTokenResp = v s.NegTokenResp.settings = s.settings default: return errors.New("unknown choice type for NegotiationToken") } return nil } // Verify the SPNEGOToken func (s *SPNEGOToken) Verify() (bool, gssapi.Status) { if (!s.Init && !s.Resp) || (s.Init && s.Resp) { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"} } if s.Init { s.NegTokenInit.settings = s.settings ok, status := s.NegTokenInit.Verify() if ok { s.context = s.NegTokenInit.Context() } return ok, status } if s.Resp { s.NegTokenResp.settings = s.settings ok, status := s.NegTokenResp.Verify() if ok { s.context = s.NegTokenResp.Context() } return ok, status } // should not be possible to get here return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"} } // Context returns the SPNEGO context which will contain any verify user identity information. func (s *SPNEGOToken) Context() context.Context { return s.context } gokrb5-8.4.3/v8/spnego/spnego_test.go000066400000000000000000000112371427031340300174310ustar00rootroot00000000000000package spnego import ( "encoding/hex" "testing" "github.com/jcmturner/gofork/encoding/asn1" "github.com/stretchr/testify/assert" ) const ( testGSSAPIInit = "608202b606062b0601050502a08202aa308202a6a027302506092a864886f71201020206052b0501050206092a864882f71201020206062b0601050205a2820279048202756082027106092a864886f71201020201006e8202603082025ca003020105a10302010ea20703050000000000a38201706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020103a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020102a282011904820115d4bd890abc456f44e2e7a2e8111bd6767abf03266dfcda97c629af2ece450a5ae1f145e4a4d1bc2c848e66a6c6b31d9740b26b03cdbd2570bfcf126e90adf5f5ebce9e283ff5086da47b129b14fc0aabd4d1df9c1f3c72b80cc614dfc28783450b2c7b7749651f432b47aaa2ff158c0066b757f3fb00dd7b4f63d68276c76373ecdd3f19c66ebc43a81e577f3c263b878356f57e8d6c4eccd587b81538e70392cf7e73fc12a6f7c537a894a7bb5566c83ac4d69757aa320a51d8d690017aebf952add1889adfc3307b0e6cd8c9b57cf8589fbe52800acb6461c25473d49faa1bdceb8bce3f61db23f9cd6a09d5adceb411e1c4546b30b33331e570fd6bc50aa403557e75f488e759750ea038aab6454667d9b64f41a481d23081cfa003020112a281c70481c4eb593beb5afcb1a2a669d54cb85a3772231559f2d40c9f8f053f218ba6eb084ed7efc467d94b88bcd189dda920d6e675ec001a6a2bca11f0a1de37f2f7ae9929f94a86d625b2ec1b213a88cbae6099dda7b172cd3bd1802cb177ae4554d59277004bfd3435248f55044fe7af7b2c9c5a3c43763278c585395aebe2856cdff9f2569d8b823564ce6be2d19748b910ec06bd3c0a9bc5de51ddcf7d875f1108ca6ad935f52d90cb62a18197d9b8e796bef0fbe1463f61df61cfbce6008ae9e1a2d2314a986d" testGSSAPIResp = "a1143012a0030a0100a10b06092a864886f712010202" ) func TestUnmarshal_SPNEGO_Init(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenInit: %v", err) } assert.True(t, s.Init, "SPNEGO does not indicate it contains NegTokenInit as expected") assert.False(t, s.Resp, "SPNEGO indicates is contains a NegTokenResp but it shouldn't") assert.Equal(t, 4, len(s.NegTokenInit.MechTypes)) expectMechTypes := []asn1.ObjectIdentifier{ []int{1, 2, 840, 113554, 1, 2, 2}, []int{1, 3, 5, 1, 5, 2}, []int{1, 2, 840, 48018, 1, 2, 2}, []int{1, 3, 6, 1, 5, 2, 5}, } assert.Equal(t, expectMechTypes, s.NegTokenInit.MechTypes, "MechTypes list in NegTokenInit not as expected") assert.NotZero(t, len(s.NegTokenInit.MechTokenBytes), "MechToken is zero in length") } func TestUnMarshal_SPNEGO_Empty(t *testing.T) { sp := new(SPNEGOToken) // The following tests are intended to ensure we don't panic. if err := sp.Unmarshal(nil); err == nil { t.Fatal("should have errored, input is absent") } if err := sp.Unmarshal([]byte{}); err == nil { t.Fatal("should have errored, input is empty") } if err := sp.Unmarshal([]byte{1}); err == nil { t.Fatal("should have errored, input is too low") } } func TestUnmarshal_SPNEGO_RespTarg(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenResp/NegTokenTarg: %v", err) } assert.True(t, s.Resp, "SPNEGO does not indicate it contains NegTokenResp/Targ as expected") assert.False(t, s.Init, "SPNEGO indicates is contains a NegTokenInit but it shouldn't") assert.Equal(t, asn1.Enumerated(0), s.NegTokenResp.NegState, "Negotiation state not as expected.") assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, s.NegTokenResp.SupportedMech, "SupportedMech type not as expected.") } func TestMarshal_SPNEGO_Init(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIInit) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenInit: %v", err) } mb, err := s.Marshal() if err != nil { t.Fatalf("Error marshalling SPNEGO containing NegTokenInit: %v", err) } assert.Equal(t, b, mb, "Marshaled bytes not as expected") } func TestMarshal_SPNEGO_RespTarg(t *testing.T) { t.Parallel() b, err := hex.DecodeString(testGSSAPIResp) if err != nil { t.Fatalf("Error converting hex string test data to bytes: %v", err) } var s SPNEGOToken err = s.Unmarshal(b) if err != nil { t.Fatalf("Error unmarshalling SPNEGO with NegTokenResp: %v", err) } mb, err := s.Marshal() if err != nil { t.Fatalf("Error marshalling SPNEGO containing NegTokenResp: %v", err) } assert.Equal(t, b, mb, "Marshaled bytes not as expected") } gokrb5-8.4.3/v8/test/000077500000000000000000000000001427031340300142305ustar00rootroot00000000000000gokrb5-8.4.3/v8/test/README.md000066400000000000000000000001411427031340300155030ustar00rootroot00000000000000Source for integration test dependencies can be found at https://github.com/jcmturner/gokrb5-testgokrb5-8.4.3/v8/test/test.go000066400000000000000000000015431427031340300155410ustar00rootroot00000000000000// Package test provides useful resources for the testing of gokrb5. package test import ( "os" "testing" ) // Test enabling environment variable key values. const ( IntegrationEnvVar = "INTEGRATION" ADIntegrationEnvVar = "TESTAD" PrivIntegrationEnvVar = "TESTPRIVILEGED" ) // Integration skips the test unless the integration test environment variable is set. func Integration(t *testing.T) { if os.Getenv(IntegrationEnvVar) != "1" { t.Skip("Skipping integration test") } } // AD skips the test unless the AD test environment variable is set. func AD(t *testing.T) { if os.Getenv(ADIntegrationEnvVar) != "1" { t.Skip("Skipping AD integration test") } } // Privileged skips the test that require local root privilege. func Privileged(t *testing.T) { if os.Getenv(PrivIntegrationEnvVar) != "1" { t.Skip("Skipping DNS integration test") } } gokrb5-8.4.3/v8/test/testdata/000077500000000000000000000000001427031340300160415ustar00rootroot00000000000000gokrb5-8.4.3/v8/test/testdata/test_vectors.go000066400000000000000000001612141427031340300211210ustar00rootroot00000000000000// Package testdata provides Kerberos 5 test reference data. package testdata var TEST_PRINCIPALNAME_NAMESTRING = []string{"hftsai", "extra"} const ( //Expected unmarshaled values TEST_REALM = "ATHENA.MIT.EDU" TEST_CIPHERTEXT = "krbASN.1 test message" TEST_TIME_FORMAT = "20060102150405" TEST_TIME = "19940610060317" TEST_ETYPE int32 = 0 TEST_NONCE = 42 TEST_AUTHORIZATION_DATA_VALUE = "foobar" TEST_PADATA_VALUE = "pa-data" //The test vectors have been sourced from https://github.com/krb5/krb5/blob/master/src/tests/asn.1/reference_encode.out MarshaledKRB5authenticator = "6281A130819EA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A30F300DA003020101A106040431323334A405020301E240A511180F31393934303631303036303331375AA6133011A003020101A10A04083132333435363738A703020111A8243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5authenticatorOptionalsEmpty = "624F304DA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A405020301E240A511180F31393934303631303036303331375A" MarshaledKRB5authenticatorOptionalsNULL = "624F304DA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A405020301E240A511180F31393934303631303036303331375A" MarshaledKRB5ticket = "615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5keyblock = "3011A003020101A10A04083132333435363738" MarshaledKRB5enc_tkt_part = "6382011430820110A007030500FEDCBA98A1133011A003020101A10A04083132333435363738A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A42E302CA003020101A12504234544552C4D49542E2C415448454E412E2C57415348494E47544F4E2E4544552C43532EA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA811180F31393934303631303036303331375AA920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5enc_tkt_partOptionalsNULL = "6381A53081A2A007030500FEDCBA98A1133011A003020101A10A04083132333435363738A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A42E302CA003020101A12504234544552C4D49542E2C415448454E412E2C57415348494E47544F4E2E4544552C43532EA511180F31393934303631303036303331375AA711180F31393934303631303036303331375A" MarshaledKRB5enc_kdc_rep_part = "7A82010E3082010AA0133011A003020101A10A04083132333435363738A13630343018A0030201FBA111180F31393934303631303036303331375A3018A0030201FBA111180F31393934303631303036303331375AA20302012AA311180F31393934303631303036303331375AA407030500FEDCBA98A511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA811180F31393934303631303036303331375AA9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261AB20301E300DA003020102A106040412D00023300DA003020102A106040412D00023" MarshaledKRB5enc_kdc_rep_partOptionalsNULL = "7A81B23081AFA0133011A003020101A10A04083132333435363738A13630343018A0030201FBA111180F31393934303631303036303331375A3018A0030201FBA111180F31393934303631303036303331375AA20302012AA407030500FE5CBA98A511180F31393934303631303036303331375AA711180F31393934303631303036303331375AA9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261" MarshaledKRB5as_rep = "6B81EA3081E7A003020105A10302010BA22630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5as_repOptionalsNULL = "6B81C23081BFA003020105A10302010BA3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_rep = "6D81EA3081E7A003020105A10302010DA22630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_repOptionalsNULL = "6D81C23081BFA003020105A10302010DA3101B0E415448454E412E4D49542E454455A41A3018A003020101A111300F1B066866747361691B056578747261A55E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A6253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5ap_req = "6E819D30819AA003020105A10302010EA207030500FEDCBA98A35E615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A4253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5ap_rep = "6F333031A003020105A10302010FA2253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5ap_rep_enc_part = "7B363034A011180F31393934303631303036303331375AA105020301E240A2133011A003020101A10A04083132333435363738A303020111" MarshaledKRB5ap_rep_enc_partOptionalsNULL = "7B1C301AA011180F31393934303631303036303331375AA105020301E240" MarshaledKRB5as_req = "6A8201E4308201E0A103020105A20302010AA32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A48201AA308201A6A007030500FEDCBA90A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5as_reqOptionalsNULLexceptsecond_ticket = "6A82011430820110A103020105A20302010AA48201023081FFA007030500FEDCBA98A2101B0E415448454E412E4D49542E454455A511180F31393934303631303036303331375AA70302012AA8083006020100020101AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5as_reqOptionalsNULLexceptserver = "6A693067A103020105A20302010AA45B3059A007030500FEDCBA90A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A511180F31393934303631303036303331375AA70302012AA8083006020100020101" MarshaledKRB5tgs_req = "6C8201E4308201E0A103020105A20302010CA32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A48201AA308201A6A007030500FEDCBA90A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_reqOptionalsNULLexceptsecond_ticket = "6C82011430820110A103020105A20302010CA48201023081FFA007030500FEDCBA98A2101B0E415448454E412E4D49542E454455A511180F31393934303631303036303331375AA70302012AA8083006020100020101AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5tgs_reqOptionalsNULLexceptserver = "6C693067A103020105A20302010CA45B3059A007030500FEDCBA90A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A511180F31393934303631303036303331375AA70302012AA8083006020100020101" MarshaledKRB5kdc_req_body = "308201A6A007030500FEDCBA90A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5kdc_req_bodyOptionalsNULLexceptsecond_ticket = "3081FFA007030500FEDCBA98A2101B0E415448454E412E4D49542E454455A511180F31393934303631303036303331375AA70302012AA8083006020100020101AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5kdc_req_bodyOptionalsNULLexceptserver = "3059A007030500FEDCBA90A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A511180F31393934303631303036303331375AA70302012AA8083006020100020101" MarshaledKRB5safe = "746E306CA003020105A103020114A24F304DA00A04086B72623564617461A111180F31393934303631303036303331375AA205020301E240A303020111A40F300DA003020102A106040412D00023A50F300DA003020102A106040412D00023A30F300DA003020101A106040431323334" MarshaledKRB5safeOptionalsNULL = "743E303CA003020105A103020114A21F301DA00A04086B72623564617461A40F300DA003020102A106040412D00023A30F300DA003020101A106040431323334" MarshaledKRB5priv = "75333031A003020105A103020115A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_priv_part = "7C4F304DA00A04086B72623564617461A111180F31393934303631303036303331375AA205020301E240A303020111A40F300DA003020102A106040412D00023A50F300DA003020102A106040412D00023" MarshaledKRB5enc_priv_partOptionalsNULL = "7C1F301DA00A04086B72623564617461A40F300DA003020102A106040412D00023" MarshaledKRB5cred = "7681F63081F3A003020105A103020116A281BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_cred_part = "7D8202233082021FA08201DA308201D63081E8A0133011A003020101A10A04083132333435363738A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A307030500FEDCBA98A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA8101B0E415448454E412E4D49542E454455A91A3018A003020101A111300F1B066866747361691B056578747261AA20301E300DA003020102A106040412D00023300DA003020102A106040412D000233081E8A0133011A003020101A10A04083132333435363738A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A307030500FEDCBA98A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA8101B0E415448454E412E4D49542E454455A91A3018A003020101A111300F1B066866747361691B056578747261AA20301E300DA003020102A106040412D00023300DA003020102A106040412D00023A10302012AA211180F31393934303631303036303331375AA305020301E240A40F300DA003020102A106040412D00023A50F300DA003020102A106040412D00023" MarshaledKRB5enc_cred_partOptionalsNULL = "7D82010E3082010AA0820106308201023015A0133011A003020101A10A040831323334353637383081E8A0133011A003020101A10A04083132333435363738A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A307030500FEDCBA98A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA711180F31393934303631303036303331375AA8101B0E415448454E412E4D49542E454455A91A3018A003020101A111300F1B066866747361691B056578747261AA20301E300DA003020102A106040412D00023300DA003020102A106040412D00023" MarshaledKRB5error = "7E81BA3081B7A003020105A10302011EA211180F31393934303631303036303331375AA305020301E240A411180F31393934303631303036303331375AA505020301E240A60302013CA7101B0E415448454E412E4D49542E454455A81A3018A003020101A111300F1B066866747361691B056578747261A9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261AB0A1B086B72623564617461AC0A04086B72623564617461" MarshaledKRB5errorOptionalsNULL = "7E60305EA003020105A10302011EA305020301E240A411180F31393934303631303036303331375AA505020301E240A60302013CA9101B0E415448454E412E4D49542E454455AA1A3018A003020101A111300F1B066866747361691B056578747261" MarshaledKRB5authorization_data = "3022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5padata_sequence = "30243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461" MarshaledKRB5typed_data = "30243010A00302010DA109040770612D646174613010A00302010DA109040770612D64617461" MarshaledKRB5padataSequenceEmpty = "3000" MarshaledKRB5etype_info = "30333014A003020100A10D040B4D6F72746F6E27732023303005A0030201013014A003020102A10D040B4D6F72746F6E2773202332" MarshaledKRB5etype_infoOnly1 = "30163014A003020100A10D040B4D6F72746F6E2773202330" MarshaledKRB5etype_infoNoInfo = "3000" MarshaledKRB5etype_info2 = "3051301EA003020100A10D1B0B4D6F72746F6E2773202330A208040673326B3A2030300FA003020101A208040673326B3A2031301EA003020102A10D1B0B4D6F72746F6E2773202332A208040673326B3A2032" MarshaledKRB5etype_info2Only1 = "3020301EA003020100A10D1B0B4D6F72746F6E2773202330A208040673326B3A2030" MarshaledKRB5pa_enc_ts = "301AA011180F31393934303631303036303331375AA105020301E240" MarshaledKRB5pa_enc_tsNoUsec = "3013A011180F31393934303631303036303331375A" MarshaledKRB5enc_data = "3023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_dataMSBSetkvno = "3026A003020100A1060204FF000000A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5enc_dataKVNONegOne = "3023A003020100A1030201FFA21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5sam_challenge_2 = "3022A00D300B04096368616C6C656E6765A111300F300DA003020101A106040431323334" MarshaledKRB5sam_challenge_2_body = "3064A00302012AA10703050080000000A20B040974797065206E616D65A411040F6368616C6C656E6765206C6162656CA510040E6368616C6C656E67652069707365A6160414726573706F6E73655F70726F6D70742069707365A8050203543210A903020101" MarshaledKRB5sam_response_2 = "3042A00302012BA10703050080000000A20C040A747261636B2064617461A31D301BA003020101A10402020D36A20E040C6E6F6E6365206F7220736164A4050203543210" MarshaledKRB5enc_sam_response_enc_2 = "301FA003020158A1180416656E635F73616D5F726573706F6E73655F656E635F32" MarshaledKRB5pa_for_user = "304BA01A3018A003020101A111300F1B066866747361691B056578747261A1101B0E415448454E412E4D49542E454455A20F300DA003020101A106040431323334A30A1B086B72623564617461" MarshaledKRB5pa_s4u_x509_user = "3068A0553053A006020400CA149AA11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A312041070615F7334755F783530395F75736572A40703050080000000A10F300DA003020101A106040431323334" MarshaledKRB5ad_kdcissued = "3065A00F300DA003020101A106040431323334A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5ad_signedpath_data = "3081C7A030302EA01A3018A003020101A111300F1B066866747361691B056578747261A1101B0E415448454E412E4D49542E454455A111180F31393934303631303036303331375AA2323030302EA01A3018A003020101A111300F1B066866747361691B056578747261A1101B0E415448454E412E4D49542E454455A32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A4243022300FA003020101A1080406666F6F626172300FA003020101A1080406666F6F626172" MarshaledKRB5ad_signedpath = "303EA003020101A10F300DA003020101A106040431323334A32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461" MarshaledKRB5iakerb_header = "3018A10A04086B72623564617461A20A04086B72623564617461" MarshaledKRB5iakerb_finished = "3011A10F300DA003020101A106040431323334" MarshaledKRB5fast_response = "30819FA02630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A1133011A003020101A10A04083132333435363738A25B3059A011180F31393934303631303036303331375AA105020301E240A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A40F300DA003020101A106040431323334A30302012A" MarshaledKRB5pa_fx_fast_reply = "A0293027A0253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5otp_tokeninfoOptionalsNULL = "300780050000000000" MarshaledKRB5otp_tokeninfo = "307280050077000000810B4578616D706C65636F727082056861726B2183010A8401028509796F7572746F6B656E862875726E3A696574663A706172616D733A786D6C3A6E733A6B657970726F763A70736B633A686F7470A716300B0609608648016503040201300706052B0E03021A880203E8" MarshaledKRB5pa_otp_challengeOptionalsNULL = "301580086D696E6E6F6E6365A209300780050000000000" MarshaledKRB5pa_otp_challenge = "3081A580086D61786E6F6E6365810B7465737473657276696365A27D300780050000000000307280050077000000810B4578616D706C65636F727082056861726B2183010A8401028509796F7572746F6B656E862875726E3A696574663A706172616D733A786D6C3A6E733A6B657970726F763A70736B633A686F7470A716300B0609608648016503040201300706052B0E03021A880203E883076B657973616C74840431323334" MarshaledKRB5pa_otp_reqOptionalsNULL = "302C80050000000000A223A003020100A103020105A21704156B726241534E2E312074657374206D657373616765" MarshaledKRB5pa_otp_req = "3081B98005006000000081056E6F6E6365A223A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A30B0609608648016503040201840203E8850566726F6773860A6D79666972737470696E87056861726B21880F31393934303631303036303331375A89033334368A01028B09796F7572746F6B656E8C2875726E3A696574663A706172616D733A786D6C3A6E733A6B657970726F763A70736B633A686F74708D0B4578616D706C65636F7270" MarshaledKRB5pa_otp_enc_req = "300A80086B72623564617461" MarshaledKRB5kkdcp_message = "308201FCA08201EC048201E86A8201E4308201E0A103020105A20302010AA32630243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461A48201AA308201A6A007030500FEDCBA98A11A3018A003020101A111300F1B066866747361691B056578747261A2101B0E415448454E412E4D49542E454455A31A3018A003020101A111300F1B066866747361691B056578747261A411180F31393934303631303036303331375AA511180F31393934303631303036303331375AA611180F31393934303631303036303331375AA70302012AA8083006020100020101A920301E300DA003020102A106040412D00023300DA003020102A106040412D00023AA253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765AB81BF3081BC615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765615C305AA003020105A1101B0E415448454E412E4D49542E454455A21A3018A003020101A111300F1B066866747361691B056578747261A3253023A003020100A103020105A21704156B726241534E2E312074657374206D657373616765A10A1B086B72623564617461" MarshaledKRB5cammacOptionalsNULL = "3012A010300E300CA003020101A1050403616431" MarshaledKRB5cammac = "3081F2A01E301C300CA003020101A1050403616431300CA003020102A1050403616432A13D303BA01A3018A003020101A111300F1B066866747361691B056578747261A103020105A203020110A3133011A003020101A10A0408636B73756D6B6463A23D303BA01A3018A003020101A111300F1B066866747361691B056578747261A103020105A203020110A3133011A003020101A10A0408636B73756D737663A35230503013A311300FA003020101A1080406636B73756D313039A01A3018A003020101A111300F1B066866747361691B056578747261A103020105A203020110A311300FA003020101A1080406636B73756D32" MarshaledKRB5secure_cookie = "302C02042DF8022530243010A10302010DA209040770612D646174613010A10302010DA209040770612D64617461" //MS PAC Vectors MarshaledPAC_AuthorizationData_MS = "308205523082054ea00402020080a182054404820540040000000000000001000000b004000048000000000000000a00000012000000f804000000000000060000001400000010050000000000000700000014000000280500000000000001100800cccccccca00400000000000000000200d186660f656ac601ffffffffffffff7fffffffffffffff7f17d439fe784ac6011794a328424bc601175424977a81c60108000800040002002400240008000200120012000c0002000000000010000200000000001400020000000000180002005410000097792c00010200001a0000001c000200200000000000000000000000000000000000000016001800200002000a000c002400020028000200000000000000000010000000000000000000000000000000000000000000000000000000000000000d0000002c0002000000000000000000000000000400000000000000040000006c007a00680075001200000000000000120000004c0069007100690061006e00670028004c006100720072007900290020005a00680075000900000000000000090000006e0074006400730032002e0062006100740000000000000000000000000000000000000000000000000000000000000000000000000000001a00000061c433000700000009c32d00070000005eb4320007000000010200000700000097b92c00070000002bf1320007000000ce30330007000000a72e2e00070000002af132000700000098b92c000700000062c4330007000000940133000700000076c4330007000000aefe2d000700000032d22c00070000001608320007000000425b2e00070000005fb4320007000000ca9c35000700000085442d0007000000c2f0320007000000e9ea310007000000ed8e2e0007000000b6eb310007000000ab2e2e0007000000720e2e00070000000c000000000000000b0000004e0054004400450056002d00440043002d003000350000000600000000000000050000004e0054004400450056000000040000000104000000000005150000005951b81766725d2564633b0b0d0000003000020007000000340002000700002038000200070000203c000200070000204000020007000020440002000700002048000200070000204c000200070000205000020007000020540002000700002058000200070000205c00020007000020600002000700002005000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000050000000105000000000005150000005951b81766725d2564633b0b74542f00050000000105000000000005150000005951b81766725d2564633b0be8383200050000000105000000000005150000005951b81766725d2564633b0bcd383200050000000105000000000005150000005951b81766725d2564633b0b5db43200050000000105000000000005150000005951b81766725d2564633b0b41163500050000000105000000000005150000005951b81766725d2564633b0be8ea3100050000000105000000000005150000005951b81766725d2564633b0bc1193200050000000105000000000005150000005951b81766725d2564633b0b29f13200050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00050000000105000000000005150000005951b81766725d2564633b0bef8f3100050000000105000000000005150000005951b81766725d2564633b0b075f2e00000000000049d90e656ac60108006c007a006800750000000000000076ffffff41edce9a34815d3aef7bc98874805d250000000076fffffff7a534dab2c02986efe0fbe5110a4f3200000000" MarshaledPAC_Kerb_Validation_Info_MS = "01100800cccccccca00400000000000000000200d186660f656ac601ffffffffffffff7fffffffffffffff7f17d439fe784ac6011794a328424bc601175424977a81c60108000800040002002400240008000200120012000c0002000000000010000200000000001400020000000000180002005410000097792c00010200001a0000001c000200200000000000000000000000000000000000000016001800200002000a000c002400020028000200000000000000000010000000000000000000000000000000000000000000000000000000000000000d0000002c0002000000000000000000000000000400000000000000040000006c007a00680075001200000000000000120000004c0069007100690061006e00670028004c006100720072007900290020005a00680075000900000000000000090000006e0074006400730032002e0062006100740000000000000000000000000000000000000000000000000000000000000000000000000000001a00000061c433000700000009c32d00070000005eb4320007000000010200000700000097b92c00070000002bf1320007000000ce30330007000000a72e2e00070000002af132000700000098b92c000700000062c4330007000000940133000700000076c4330007000000aefe2d000700000032d22c00070000001608320007000000425b2e00070000005fb4320007000000ca9c35000700000085442d0007000000c2f0320007000000e9ea310007000000ed8e2e0007000000b6eb310007000000ab2e2e0007000000720e2e00070000000c000000000000000b0000004e0054004400450056002d00440043002d003000350000000600000000000000050000004e0054004400450056000000040000000104000000000005150000005951b81766725d2564633b0b0d0000003000020007000000340002000700002038000200070000203c000200070000204000020007000020440002000700002048000200070000204c000200070000205000020007000020540002000700002058000200070000205c00020007000020600002000700002005000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000050000000105000000000005150000005951b81766725d2564633b0b74542f00050000000105000000000005150000005951b81766725d2564633b0be8383200050000000105000000000005150000005951b81766725d2564633b0bcd383200050000000105000000000005150000005951b81766725d2564633b0b5db43200050000000105000000000005150000005951b81766725d2564633b0b41163500050000000105000000000005150000005951b81766725d2564633b0be8ea3100050000000105000000000005150000005951b81766725d2564633b0bc1193200050000000105000000000005150000005951b81766725d2564633b0b29f13200050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00050000000105000000000005150000005951b81766725d2564633b0bef8f3100050000000105000000000005150000005951b81766725d2564633b0b075f2e0000000000" MarshaledPAC_AuthorizationData_GOKRB5 = "3082034730820343a003020101a182033a04820336308203323082032ea00402020080a1820324048203200500000000000000010000002802000058000000000000000a0000001c00000080020000000000000c00000058000000a0020000000000000600000010000000f8020000000000000700000014000000080300000000000001100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000808dd1dc80c6d2011200740065007300740075007300650072003100000000002a001000160040000000000000000000740065007300740075007300650072003100400074006500730074002e0067006f006b0072006200350000000000000054004500530054002e0047004f004b005200420035000000100000001e251d98d552be7df384f55076ffffff340be28b48765d0519ee9346cf53d82200000000" MarshaledPAC_AD_WIN2K_PAC = "0500000000000000010000002802000058000000000000000a0000001c00000080020000000000000c00000058000000a0020000000000000600000010000000f8020000000000000700000014000000080300000000000001100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000808dd1dc80c6d2011200740065007300740075007300650072003100000000002a001000160040000000000000000000740065007300740075007300650072003100400074006500730074002e0067006f006b0072006200350000000000000054004500530054002e0047004f004b005200420035000000100000001e251d98d552be7df384f55076ffffff340be28b48765d0519ee9346cf53d82200000000" MarshaledPAC_Kerb_Validation_Info = "01100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000" MarshaledPAC_Client_Info = "808dd1dc80c6d2011200740065007300740075007300650072003100" MarshaledPAC_UPN_DNS_Info = "2a001000160040000000000000000000740065007300740075007300650072003100400074006500730074002e0067006f006b0072006200350000000000000054004500530054002e0047004f004b005200420035000000" MarshaledPAC_Server_Signature = "100000001e251d98d552be7df384f550" MarshaledPAC_KDC_Signature = "76ffffff340be28b48765d0519ee9346cf53d822" MarshaledPAC_Kerb_Validation_Info_Trust = "01100800cccccccc000200000000000000000200c30bcc79e444d301ffffffffffffff7fffffffffffffff7fc764125a0842d301c7247c84d142d301ffffffffffffff7f12001200040002001600160008000200000000000c0002000000000010000200000000001400020000000000180002002e0000005204000001020000030000001c0002002002000000000000000000000000000000000000060008002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000010000002c00020034000200020000003800020009000000000000000900000074006500730074007500730065007200310000000b000000000000000b0000005400650073007400310020005500730065007200310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000056040000070000000102000007000000550400000700000004000000000000000300000055004400430000000500000000000000040000005500530045005200040000000104000000000005150000002057308834e7d1d0a2fb0444010000003000020007000000010000000101000000000012010000000400000001040000000000051500000062dc8db6c8705249b5459e75020000005304000007000020540400000700002000000000" MarshaledPAC_ClientClaimsInfoStr = "01100800cccccccc000100000000000000000200d80000000400020000000000d8000000000000000000000000000000d800000001100800ccccccccc80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000100000010000200290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a0038003800640035006400390030003800350065006100350063003000630030000000000001000000140002000a000000000000000a00000074006500730074007500730065007200310000000000000000000000" MarshaledPAC_ClientClaimsInfoInt = "01100800cccccccce00000000000000000000200b80000000400020000000000b8000000000000000000000000000000b800000001100800cccccccca80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c0002000100010001000000100002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c0000000000000000000000" MarshaledPAC_ClientClaimsInfoMulti = "01100800cccccccc780100000000000000000200500100000400020000000000500100000000000000000000000000005001000001100800cccccccc400100000000000000000200010000000400020000000000000000000000000001000000010000000200000008000200020000000c000200010001000100000010000200140002000300030001000000180002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c00000000000000290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a00380038006400350064003900300038003500650061003500630030006300300000000000010000001c0002000a000000000000000a000000740065007300740075007300650072003100000000000000" MarshaledPAC_ClientClaimsInfoMultiUint = "01100800ccccccccf00000000000000000000200c80000000400020000000000c8000000000000000000000000000000c800000001100800ccccccccb80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200020002000400000010000200260000000000000026000000610064003a002f002f006500780074002f006f0062006a0065006300740043006c006100730073003a00380038006400350064006500370039003100650037006200320037006500360000000400000009000a000000000007000100000000000600010000000000000001000000000000000000" MarshaledPAC_ClientClaimsInfoMultiStr = "01100800cccccccc480100000000000000000200200100000400020000000000200100000000000000000000000000002001000001100800cccccccc100100000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000400000010000200270000000000000027000000610064003a002f002f006500780074002f006f00740068006500720049007000500068006f006e0065003a003800380064003500640065003900660036006200340061006600390038003500000000000400000014000200180002001c000200200002000500000000000000050000007300740072003100000000000500000000000000050000007300740072003200000000000500000000000000050000007300740072003300000000000500000000000000050000007300740072003400000000000000000000000000" MarshaledPAC_ClientClaimsInfo_XPRESS_HUFF = "01100800ccccccccd00100000000000000000200a80100000400020004000000e0010000000000000000000000000000a8010000727807888708080007000800080008000800080880000080870870887807000080800000000080080000080000000000605767070007777707677700770000000000000000000000000000000000000000000000000000000000000000000000000000000000070007000000000000000000000000000000000000000000000076000700700000007600000000000000750700000000000064770700000000007607000000000000060700000000000077060700000000707770700070000770007700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a85652950bb9d8bae030b2212b90df95764d1b182da22f2c848b23b3cc4efc8e3499701e481cf938e490986a384c3d572250aaab2446572fc26be279c263e4a4c9c2c24f9649e2444d8ddb3277373c600363beb73200baaa783da183dd85830af863e1a00d5cf718aac4879519fbf0745bcc59214493a330f940bf99a446f1ade6df2610c5f154b432eaba964d7ad1f1182e522019fc21ce498a204d06b96a476f7386e6003000000000000" MarshaledChangePasswdData = "3036a00d040b6e657770617373776f7264a1163014a003020101a10d300b1b09746573747573657231a20d1b0b544553542e474f4b524235" MarshaledKpasswd_Req = "037aff8002c16e8202bd308202b9a003020105a10302010ea20703050000000000a38201f1618201ed308201e9a003020105a10d1b0b544553542e474f4b524235a21d301ba003020101a11430121b066b61646d696e1b086368616e67657077a38201b2308201aea003020111a103020101a28201a00482019ca3de94df50e8e9fe7a8c9386f594f469bf08874407fc7b95ddcf22110ef63e62ff0ba3c31c3bb725dc1dde1f4c2f69a4973b4b43c9b4b31f71f676d5e8e7b4d7906b1dfacc9897d865b17f934fb96b802344463bb0746fdd39e9e48ff1b2665dc895a74d3d3aac89512b43bd8ead8f455b9b819cc6f6a34fb7c5975d7c2dbd4349524961215b98f33f5747f1e0c89f3b3637462308953940741ab7fc38ae817ba85800dd911bb78b42264f2d285c2a0a33ca21c1a3d281ec14614010db31c3e3f4d4622b799f97b3d31c4445411278fec62dd8e6e349db280aaa4419b53ef6fbc01f0206bcfea2cbe835b46764c03c138722e54dab53a1080e5d6c99f8cd7a948880677176cfc2d3800f9ef64d1ec4f8bdadc1ae409990c4855a82e265682e8ddaa6dea70a1d7855f3e1e766f5efe428dd6da71c585f5d17d8f81e8f2a4f4b2245f5ff2cc444a2a1ae5d16a15d588597219d5659da537f752ca9b572b635088b325b60e8e62fd99487872261f41dcc466516b89992d277bb8b3a1ca770671fca36dd33c3dd6dab643e6710280661029254054273151ccfca9aaddc55a481ae3081aba003020112a103020101a2819e04819be66387f971d751d7d3ebb6acd815a0991e0ed9f07e2643783e7961fb88127b31f767bf00d1d071a81858b101f4d45460412d8013228f942bc51891e95a06aefa8cedd95e5a3e6e65597c0f05c19ee54dc6dc00b1a3f9d7a95516b5e447c40cd5b462ed6b17a007670311efa44dbe939cab11072b9af1443c3203767bb1a3240542db06dffcaebcedd5c335bb295127bc0e6d99f2c1e87f68de1f547581b03081ada003020105a103020115a381a030819da003020112a103020101a2819004818df272b2726c8f31c578f3b4275bc283828716010a20f0c4369bff474fcf202537060a71edcbe8ba720d0d9b2bac26b58353dc5b2945570374928a819eb3526362eda328e704f1a5ebe3272eed0fa6a6aa7d0f32c4fc0bd2e4ea52a8834ea7b5fb018934df87c18ab625f5c07f6c28e202e0cec63bcc37b1d381d64937998c1bdcd1585695eeffb75f8ce9e736b3" MarshaledKpasswd_Rep = "00ec0001008c6f8189308186a003020105a10302010fa27a3078a003020112a271046f57cb442fd321312aff0b2dcda70fe436812f9805611adf3403ab6cd7708604e86e77f765a8486864f0dbf8d5d065a63790370bc110ed1e3c7eae9890e02407e8a8b349703fed1e7f165e1261a822c5b3e6823c282884f59afeb9f84f2a9845994135dd307eb2f544874393c1c455d475583056a003020105a103020115a34a3048a003020112a241043fdd3edaf0b6cbcab5b663189bafc0a19e6cc03b3c59d989c403735748ebc36088bad852add0f62581eed515fc1f297324df4fa12cb94b7ad5db257165369db5" TESTUSER_PASSWORD = "passwordvalue" // MIT Integration Test Environment KEYTAB_TESTUSER1_TEST_GOKRB5 = "05020000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80100110010698c4df8e9f60e7eea5a21bf4526ad25000000010000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80100120020bbdc430aab7e2d4622a0b6951481453b0962e9db8e2f168942ad175cda6d9de9000000010000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80200110010698c4df8e9f60e7eea5a21bf4526ad25000000020000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80200120020bbdc430aab7e2d4622a0b6951481453b0962e9db8e2f168942ad175cda6d9de9000000020000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d801001300102eb8501967a7886e1f0c63ac9be8c4a0000000010000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d802001300102eb8501967a7886e1f0c63ac9be8c4a0000000020000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d801001400208ad66f209bb07daa186f8a229830f5ba06a3a2a33638f4ec66e1d29324e417ee000000010000004b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d802001400208ad66f209bb07daa186f8a229830f5ba06a3a2a33638f4ec66e1d29324e417ee00000002000000430001000b544553542e474f4b52423500097465737475736572310000000159beb1d801001000184580fb91760dabe6f808c22c26494f644cb35d61d32c79e300000001000000430001000b544553542e474f4b52423500097465737475736572310000000159beb1d802001000184580fb91760dabe6f808c22c26494f644cb35d61d32c79e3000000020000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b544553542e474f4b52423500097465737475736572310000000159beb1d80200170010084768c373663b3bef1f6385883cf7ff00000002" KEYTAB_TESTUSER2_TEST_GOKRB5 = "05020000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb240010011001086824c55ff5de30386dd83dc62b44bb7000000010000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400100120020d8ed27f96be76fd5b281ee9f8029db93cc5fb06c7eb3be9ee753106d3488fa92000000010000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb240020011001086824c55ff5de30386dd83dc62b44bb7000000020000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400200120020d8ed27f96be76fd5b281ee9f8029db93cc5fb06c7eb3be9ee753106d3488fa92000000020000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb24001001300106ccff358aaa8a4a41c444e173b1463c2000000010000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb24002001300106ccff358aaa8a4a41c444e173b1463c2000000020000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb24001001400205cf3773dd920be800229ac1c6f9bf59c6706c583f82c2dea66c9a29152118cd7000000010000004b0001000b544553542e474f4b52423500097465737475736572320000000159beb24002001400205cf3773dd920be800229ac1c6f9bf59c6706c583f82c2dea66c9a29152118cd700000002000000430001000b544553542e474f4b52423500097465737475736572320000000159beb2400100100018bc025746e9e66bd6b62a918f6413d529803192a28aabf79200000001000000430001000b544553542e474f4b52423500097465737475736572320000000159beb2400200100018bc025746e9e66bd6b62a918f6413d529803192a28aabf792000000020000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b544553542e474f4b52423500097465737475736572320000000159beb2400200170010084768c373663b3bef1f6385883cf7ff00000002" KEYTAB_TESTUSER1_TEST_GOKRB5_WRONGPASSWD = "0502000000370001000b544553542e474f4b52423500097465737475736572310000000158ef4bc5010011001039a9a382153105f8708e80f93382654e000000470001000b544553542e474f4b52423500097465737475736572310000000158ef4bc60100120020fc5bb940d6075214e0c6fc0456ce68c33306094198a927b4187d7cf3f4aea50d" KEYTAB_SYSHTTP_TEST_GOKRB5 = "0502000000450001000b544553542e474f4b52423500077379734854545000000001590dc5af020012002043763702868978d1b6d91a36704b987e27e517250055bdfc40b8a6b3848d9aae" KEYTAB_SYSHTTP_RESDOM_GOKRB5 = "05020000005c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d94070100120020e53945463c231ab747635c96d5fc48f6591ce41cec98ad2620b50f52c2bafa96000000010000004c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d940701001100103ae5388332dc948e00427332658c537800000001000000540002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d940701001000183da7b93eb698233de3b07f080b07191a49a83d32d3587c8f000000010000004c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d9407010013001036e58aaaf739aad8bc49115dfd8d304b000000010000005c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d9407010014002055e4bf018dfdd3906f0f84149b6d503a03bdf494ba40f482faf67e7a77b9c05f000000010000004c0002000d524553444f4d2e474f4b5242350004485454500012686f73742e726573646f6d2e676f6b726235000000015a3d94070100170010c050d33acce5fac748f6f26bd686e1c700000001" HTTP_KEYTAB = "0502000000440002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc010011001057a7754c70c4d85c155c718c2f1292b0000000540002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc01001200209cad00bbc72d703258e911dc18e6d5487cf737bf67fd111f0c2463ad6033bf51000000440002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc020011001057a7754c70c4d85c155c718c2f1292b0000000540002000b544553542e474f4b5242350004485454500010686f73742e746573742e676f6b72623500000001590dc4dc02001200209cad00bbc72d703258e911dc18e6d5487cf737bf67fd111f0c2463ad6033bf51" CCACHE_TEST = "0504000c00010008000000060000000000000001000000010000000b544553542e474f4b5242350000000974657374757365723100000001000000010000000b544553542e474f4b5242350000000974657374757365723100000002000000020000000b544553542e474f4b524235000000066b72627467740000000b544553542e474f4b52423500120000002088b94319f2dcd1de20ebd3bf3174778769323bce76ef71fb37a8ba4be93c38df59665b8e59665b8e5967044e5967ad080040c1000000000000000000000000015a6182015630820152a003020105a10d1b0b544553542e474f4b524235a220301ea003020102a11730151b066b72627467741b0b544553542e474f4b524235a382011830820114a003020112a103020101a282010604820102ee32bb7e27ad6f71869be098c4002b291f370d26302c87ffa3eb670345a11fc113a9e5ab9e26ea659104b29e2a60c07dda559654c58aaf5f48bbb3bb9a238745861be336a0672554dac9b38126b2929ce9df2add185d1043c6dd89c7308b9def7b98ba7bcdcd1c00eeb5d99e273e1fe53b88c057106ec3dbcf2a86c38a4c1372418f1afb0227975747edf2172e23716ab5f6fa9a2ee5c0d94e9f66936df767498677861926812d1f887de6f44e5ebd93b63fd8313a499372ea9e889620bd0842bc8a8f8a17e5dea328c77b771cfcd49ac7afa4a9c7236efa30fec1b2072255543aee48cd935ece367e08d24f51bea4b407ace8ed7e67a8d5e1cb528eb16c7ebe7ac50000000000000001000000010000000b544553542e474f4b5242350000000974657374757365723100000000000000030000000c582d4341434845434f4e463a000000156b7262355f6363616368655f636f6e665f646174610000000a666173745f617661696c0000001e6b72627467742f544553542e474f4b52423540544553542e474f4b5242350000000000000000000000000000000000000000000000000000000000000000000000000000037965730000000000000001000000010000000b544553542e474f4b5242350000000974657374757365723100000001000000020000000b544553542e474f4b524235000000044854545000000010686f73742e746573742e676f6b726235001200000020fd325da3f905d743894e828de41b21af7876b6281b66d9e4bb2eefd64078b47659665b8e59665bce5967044e5967ad0800408900000000000000000000000001706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020101a282011904820115ad55d79858ce41647e835769b40540bc32ff4debe101217a7a024016697ee5ff758829940ca576905a260732c43c2996d96b83f9bff010fdbfc8f3bff51cef202a956f8d73d18c2c8865553f55229075270f42dca23d7618ff35e578a972d40746398efd478cf4f1094d99371273b3fbe5b95707011b446ff605ea8cb0e6631ea0ffdd7b562b5aa2de5dd455388e1aa18d8a3a8e81dab058e1b223410a752e5ec82797164dabafdbec8eeef7b072304e46d7d15b575f44cce69a368a9004612ba179b41d4655964933f7eb114a457aa1127291fc6d63deb271e5504de6fccca33260645ef5bd1ea301d74a8dbf751aa181ed92f5edb493d68222e1a34892035b88b6fb0ce104db23f7da22a8e73359d9c322b8e1cc00000000" TEST_HTTP_URL = "http://10.80.88.88" KDC_IP_TEST_GOKRB5 = "10.80.88.88" KDC_IP_TEST_GOKRB5_BADADDR = "10.80.88.153" KDC_PORT_TEST_GOKRB5 = "88" KDC_PORT_TEST_GOKRB5_LASTEST = "98" KDC_PORT_TEST_GOKRB5_RESDOM = "188" KDC_PORT_TEST_GOKRB5_OLD = "78" KDC_PORT_TEST_GOKRB5_SHORTTICKETS = "58" // AD Integration Test Environment KEYTAB_TESTUSER1_USER_GOKRB5 = "05020000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750100110010528b8ba0ae5131fbf71f6ddc5870cdce000000010000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c275010012002016475f89eba70e62af20a20e7bf3ca4ccad5ae22485c93ffb133650bc6f12585000000010000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750100130010a3ddea306fa06c068bc3e1fcf4b280ca000000010000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c27501001400205d2a66a8af5142db59bcaabac8310777bf60a85e8881469e2063bba4dff0c00a000000010000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750200110010528b8ba0ae5131fbf71f6ddc5870cdce000000020000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c275020012002016475f89eba70e62af20a20e7bf3ca4ccad5ae22485c93ffb133650bc6f12585000000020000003b0001000b555345522e474f4b5242350009746573747573657231000000015e80c2750200130010a3ddea306fa06c068bc3e1fcf4b280ca000000020000004b0001000b555345522e474f4b5242350009746573747573657231000000015e80c27502001400205d2a66a8af5142db59bcaabac8310777bf60a85e8881469e2063bba4dff0c00a00000002" KEYTAB_TESTUSER2_USER_GOKRB5 = "05020000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f30100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f30100110010a771a31fae504621fffc644a521e0cee000000010000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f301001200203262b201af7ec73c77bbee75a4ff10950cf2bda56529ead30ced4f0f0b9a591d000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f301001300104be17a4cf1761f0494475617f671fa6a000000010000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80b9f301001400208b1c60589b6c0d78d5e8fe265e92c2babf920a6a2828c37fc343e43497ad9fab000000010000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a0200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a0200110010a771a31fae504621fffc644a521e0cee000000020000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a02001200203262b201af7ec73c77bbee75a4ff10950cf2bda56529ead30ced4f0f0b9a591d000000020000003b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a02001300104be17a4cf1761f0494475617f671fa6a000000020000004b0001000b555345522e474f4b5242350009746573747573657232000000015e80c30a02001400208b1c60589b6c0d78d5e8fe265e92c2babf920a6a2828c37fc343e43497ad9fab00000002" KEYTAB_TESTUSER3_USER_GOKRB5 = "05020000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba950100170010084768c373663b3bef1f6385883cf7ff000000010000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba95010011001025b79e18723ecd0fdf76c3a5bb21d5dd000000010000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba950100120020c98c6dcc3ee520d5712aba339b2aa1930414b24fb52b9f70bf46259a57c1740b000000010000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba95010013001007f06e524ee5d738b5bb464c876a5087000000010000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80ba95010014002024cb938c683c9fcbe548f2febc93f8090fbaf44541751fc2b781e453dba36a11000000010000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d0200170010084768c373663b3bef1f6385883cf7ff000000020000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d020011001025b79e18723ecd0fdf76c3a5bb21d5dd000000020000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d0200120020c98c6dcc3ee520d5712aba339b2aa1930414b24fb52b9f70bf46259a57c1740b000000020000003b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d020013001007f06e524ee5d738b5bb464c876a5087000000020000004b0001000b555345522e474f4b5242350009746573747573657233000000015e80c37d020014002024cb938c683c9fcbe548f2febc93f8090fbaf44541751fc2b781e453dba36a1100000002" KEYTAB_SYSHTTP_RES_GOKRB5 = "0502000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200170010084768c373663b3bef1f6385883cf7ff00000002000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200110010c622e44a32022f4cb81775263151140d00000002000000480001000a5245532e474f4b524235000773797348545450000000015e7a7e2f02001200209da0dc4802bf5d375dfe2a77ddfc5065b3bf789126c2dc89ff4c2aa90dfa43ce00000002000000380001000a5245532e474f4b524235000773797348545450000000015e7a7e2f0200130010541beb216c1cdf22ef7c2225066a385e00000002000000480001000a5245532e474f4b524235000773797348545450000000015e7a7e2f02001400205f0983acd70fcaee0acb7ac4a14f8ad89f3a35915914e696200370637d8fef2300000002" KRB5_CONF = `[libdefaults] default_realm = TEST.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 noaddresses = false [realms] TEST.GOKRB5 = { kdc = 127.0.0.1:88 admin_server = 127.0.0.1:749 default_domain = test.gokrb5 } RESDOM.GOKRB5 = { kdc = 10.80.88.88:188 admin_server = 127.0.0.1:749 default_domain = resdom.gokrb5 } [domain_realm] .test.gokrb5 = TEST.GOKRB5 test.gokrb5 = TEST.GOKRB5 .resdom.gokrb5 = RESDOM.GOKRB5 resdom.gokrb5 = RESDOM.GOKRB5 ` KRB5_CONF_AD = `[libdefaults] default_realm = USER.GOKRB5 dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = yes default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 noaddresses = false [realms] USER.GOKRB5 = { kdc = 192.168.88.100:88 admin_server = 192.168.88.100:464 default_domain = user.gokrb5 } RES.GOKRB5 = { kdc = 192.168.88.101:88 admin_server = 192.168.88.101:464 default_domain = res.gokrb5 } [domain_realm] .user.gokrb5 = USER.GOKRB5 user.gokrb5 = USER.GOKRB5 .res.gokrb5 = RES.GOKRB5 res.gokrb5 = RES.GOKRB5 ` ) gokrb5-8.4.3/v8/test/testdata/testuser1.testtab000066400000000000000000000015061427031340300213720ustar00rootroot00000000000000; TEST.GOKRB5 testuser1YiM~Z!E&%K TEST.GOKRB5 testuser1Y C ~-F"E; bێ/B\m; TEST.GOKRB5 testuser1YiM~Z!E&%K TEST.GOKRB5 testuser1Y C ~-F"E; bێ/B\m; TEST.GOKRB5 testuser1Y.Pgn cĠ; TEST.GOKRB5 testuser1Y.Pgn cĠK TEST.GOKRB5 testuser1Y o }o"068fғ$K TEST.GOKRB5 testuser1Y o }o"068fғ$C TEST.GOKRB5 testuser1YEv ,&IOdL]a,yC TEST.GOKRB5 testuser1YEv ,&IOdL]a,y; TEST.GOKRB5 testuser1YGhsf;;c<; TEST.GOKRB5 testuser1YGhsf;;c<gokrb5-8.4.3/v8/types/000077500000000000000000000000001427031340300144155ustar00rootroot00000000000000gokrb5-8.4.3/v8/types/Authenticator.go000066400000000000000000000050421427031340300175570ustar00rootroot00000000000000// Package types provides Kerberos 5 data types. package types import ( "crypto/rand" "fmt" "math" "math/big" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" ) // Authenticator - A record containing information that can be shown to have been recently generated using the session // key known only by the client and server. // https://tools.ietf.org/html/rfc4120#section-5.5.1 type Authenticator struct { AVNO int `asn1:"explicit,tag:0"` CRealm string `asn1:"generalstring,explicit,tag:1"` CName PrincipalName `asn1:"explicit,tag:2"` Cksum Checksum `asn1:"explicit,optional,tag:3"` Cusec int `asn1:"explicit,tag:4"` CTime time.Time `asn1:"generalized,explicit,tag:5"` SubKey EncryptionKey `asn1:"explicit,optional,tag:6"` SeqNumber int64 `asn1:"explicit,optional,tag:7"` AuthorizationData AuthorizationData `asn1:"explicit,optional,tag:8"` } // NewAuthenticator creates a new Authenticator. func NewAuthenticator(realm string, cname PrincipalName) (Authenticator, error) { seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) if err != nil { return Authenticator{}, err } t := time.Now().UTC() return Authenticator{ AVNO: iana.PVNO, CRealm: realm, CName: cname, Cksum: Checksum{}, Cusec: int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)), CTime: t, SeqNumber: seq.Int64() & 0x3fffffff, }, nil } // GenerateSeqNumberAndSubKey sets the Authenticator's sequence number and subkey. func (a *Authenticator) GenerateSeqNumberAndSubKey(keyType int32, keySize int) error { seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) if err != nil { return err } a.SeqNumber = seq.Int64() & 0x3fffffff //Generate subkey value sk := make([]byte, keySize, keySize) rand.Read(sk) a.SubKey = EncryptionKey{ KeyType: keyType, KeyValue: sk, } return nil } // Unmarshal bytes into the Authenticator. func (a *Authenticator) Unmarshal(b []byte) error { _, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.Authenticator)) return err } // Marshal the Authenticator. func (a *Authenticator) Marshal() ([]byte, error) { b, err := asn1.Marshal(*a) if err != nil { return nil, err } b = asn1tools.AddASNAppTag(b, asnAppTag.Authenticator) return b, nil } gokrb5-8.4.3/v8/types/Authenticator_test.go000066400000000000000000000107421427031340300206210ustar00rootroot00000000000000package types import ( "encoding/hex" "fmt" "testing" "time" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/adtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func unmarshalAuthenticatorTest(t *testing.T, v string) Authenticator { var a Authenticator //t.Logf("Starting unmarshal tests of %s", v) b, err := hex.DecodeString(v) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } return a } func TestUnmarshalAuthenticator(t *testing.T) { t.Parallel() a := unmarshalAuthenticatorTest(t, testdata.MarshaledKRB5authenticator) //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.AVNO, "Authenticator version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, int32(1), a.Cksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.Cksum.Checksum, "Checsum not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.CTime, "Client time not as expected") assert.Equal(t, int32(1), a.SubKey.KeyType, "Subkey type not as expected") assert.Equal(t, []byte("12345678"), a.SubKey.KeyValue, "Subkey value not as expected") assert.Equal(t, 2, len(a.AuthorizationData), "Number of Authorization data items not as expected") for i, entry := range a.AuthorizationData { assert.Equal(t, adtype.ADIfRelevant, entry.ADType, fmt.Sprintf("Authorization type of entry %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_AUTHORIZATION_DATA_VALUE), entry.ADData, fmt.Sprintf("Authorization data of entry %d not as expected", i+1)) } } func TestUnmarshalAuthenticator_optionalsempty(t *testing.T) { t.Parallel() a := unmarshalAuthenticatorTest(t, testdata.MarshaledKRB5authenticatorOptionalsEmpty) //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.AVNO, "Authenticator version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.CTime, "Client time not as expected") } func TestUnmarshalAuthenticator_optionalsNULL(t *testing.T) { t.Parallel() a := unmarshalAuthenticatorTest(t, testdata.MarshaledKRB5authenticatorOptionalsNULL) //Parse the test time value into a time.Time type tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME) assert.Equal(t, iana.PVNO, a.AVNO, "Authenticator version number not as expected") assert.Equal(t, testdata.TEST_REALM, a.CRealm, "CRealm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.CName.NameType, "CName NameType not as expected") assert.Equal(t, len(testdata.TEST_PRINCIPALNAME_NAMESTRING), len(a.CName.NameString), "CName does not have the expected number of NameStrings") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.CName.NameString, "CName entries not as expected") assert.Equal(t, 123456, a.Cusec, "Client microseconds not as expected") assert.Equal(t, tt, a.CTime, "Client time not as expected") } func TestMarshalAuthenticator(t *testing.T) { t.Parallel() var a Authenticator b, err := hex.DecodeString(testdata.MarshaledKRB5authenticator) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of Authenticator not as expected") } gokrb5-8.4.3/v8/types/AuthorizationData.go000066400000000000000000000034431427031340300204020ustar00rootroot00000000000000package types import ( "github.com/jcmturner/gofork/encoding/asn1" ) // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.6 // AuthorizationData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6 type AuthorizationData []AuthorizationDataEntry // AuthorizationDataEntry implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6 type AuthorizationDataEntry struct { ADType int32 `asn1:"explicit,tag:0"` ADData []byte `asn1:"explicit,tag:1"` } // ADIfRelevant implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.1 type ADIfRelevant AuthorizationData // ADKDCIssued implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.2 type ADKDCIssued struct { ADChecksum Checksum `asn1:"explicit,tag:0"` IRealm string `asn1:"optional,generalstring,explicit,tag:1"` Isname PrincipalName `asn1:"optional,explicit,tag:2"` Elements AuthorizationData `asn1:"explicit,tag:3"` } // ADAndOr implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.3 type ADAndOr struct { ConditionCount int32 `asn1:"explicit,tag:0"` Elements AuthorizationData `asn1:"explicit,tag:1"` } // ADMandatoryForKDC implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.4 type ADMandatoryForKDC AuthorizationData // Unmarshal bytes into the ADKDCIssued. func (a *ADKDCIssued) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Unmarshal bytes into the AuthorizationData. func (a *AuthorizationData) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Unmarshal bytes into the AuthorizationDataEntry. func (a *AuthorizationDataEntry) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } gokrb5-8.4.3/v8/types/AuthorizationData_test.go000066400000000000000000000040621427031340300214370ustar00rootroot00000000000000package types import ( "encoding/hex" "fmt" "testing" "github.com/jcmturner/gokrb5/v8/iana/adtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalAuthorizationData(t *testing.T) { t.Parallel() var a AuthorizationData b, err := hex.DecodeString(testdata.MarshaledKRB5authorization_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, 2, len(a), "Number of authorization data entries not as expected") for i, entry := range a { assert.Equal(t, adtype.ADIfRelevant, entry.ADType, fmt.Sprintf("Authorization data type of entry %d not as expected", i+1)) assert.Equal(t, []byte("foobar"), entry.ADData, fmt.Sprintf("Authorization data of entry %d not as expected", i+1)) } } func TestUnmarshalAuthorizationData_kdcissued(t *testing.T) { t.Parallel() var a ADKDCIssued b, err := hex.DecodeString(testdata.MarshaledKRB5ad_kdcissued) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, int32(1), a.ADChecksum.CksumType, "Checksum type not as expected") assert.Equal(t, []byte("1234"), a.ADChecksum.Checksum, "Checksum not as expected") assert.Equal(t, testdata.TEST_REALM, a.IRealm, "Issuing realm not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, a.Isname.NameType, "Issuing name type not as expected") assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, a.Isname.NameString, "Issuing name string entries not as expected") assert.Equal(t, 2, len(a.Elements), "Number of authorization data elements not as expected") for i, ele := range a.Elements { assert.Equal(t, adtype.ADIfRelevant, ele.ADType, fmt.Sprintf("Authorization data type of element %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_AUTHORIZATION_DATA_VALUE), ele.ADData, fmt.Sprintf("Authorization data of element %d not as expected", i+1)) } } gokrb5-8.4.3/v8/types/Cryptosystem.go000066400000000000000000000034521427031340300174750ustar00rootroot00000000000000package types import ( "crypto/rand" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/crypto/etype" ) // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.9 // EncryptedData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9 type EncryptedData struct { EType int32 `asn1:"explicit,tag:0"` KVNO int `asn1:"explicit,optional,tag:1"` Cipher []byte `asn1:"explicit,tag:2"` } // EncryptionKey implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9 // AKA KeyBlock type EncryptionKey struct { KeyType int32 `asn1:"explicit,tag:0"` KeyValue []byte `asn1:"explicit,tag:1" json:"-"` } // Checksum implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9 type Checksum struct { CksumType int32 `asn1:"explicit,tag:0"` Checksum []byte `asn1:"explicit,tag:1"` } // Unmarshal bytes into the EncryptedData. func (a *EncryptedData) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Marshal the EncryptedData. func (a *EncryptedData) Marshal() ([]byte, error) { edb, err := asn1.Marshal(*a) if err != nil { return edb, err } return edb, nil } // Unmarshal bytes into the EncryptionKey. func (a *EncryptionKey) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // Unmarshal bytes into the Checksum. func (a *Checksum) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } // GenerateEncryptionKey creates a new EncryptionKey with a random key value. func GenerateEncryptionKey(etype etype.EType) (EncryptionKey, error) { k := EncryptionKey{ KeyType: etype.GetETypeID(), } b := make([]byte, etype.GetKeyByteSize(), etype.GetKeyByteSize()) _, err := rand.Read(b) if err != nil { return k, err } k.KeyValue = b return k, nil } gokrb5-8.4.3/v8/types/Cryptosystem_test.go000066400000000000000000000053451427031340300205370ustar00rootroot00000000000000package types import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalEncryptedData(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, testdata.TEST_ETYPE, a.EType, "Encrypted data Etype not as expected") assert.Equal(t, iana.PVNO, a.KVNO, "Encrypted data KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Cipher, "Ecrypted data ciphertext not as expected") } func TestUnmarshalEncryptedData_MSBsetkvno(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_dataMSBSetkvno) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, testdata.TEST_ETYPE, a.EType, "Encrypted data Etype not as expected") assert.Equal(t, -16777216, a.KVNO, "Encrypted data KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Cipher, "Ecrypted data ciphertext not as expected") } func TestUnmarshalEncryptedData_kvno_neg1(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_dataKVNONegOne) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, testdata.TEST_ETYPE, a.EType, "Encrypted data Etype not as expected") assert.Equal(t, -1, a.KVNO, "Encrypted data KVNO not as expected") assert.Equal(t, []byte(testdata.TEST_CIPHERTEXT), a.Cipher, "Ecrypted data ciphertext not as expected") } func TestUnmarshalEncryptionKey(t *testing.T) { t.Parallel() var a EncryptionKey b, err := hex.DecodeString(testdata.MarshaledKRB5keyblock) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, int32(1), a.KeyType, "Key type not as expected") assert.Equal(t, []byte("12345678"), a.KeyValue, "Key value not as expected") } func TestMarshalEncryptedData(t *testing.T) { t.Parallel() var a EncryptedData b, err := hex.DecodeString(testdata.MarshaledKRB5enc_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } mb, err := a.Marshal() if err != nil { t.Fatalf("Marshal of ticket errored: %v", err) } assert.Equal(t, b, mb, "Marshal bytes of Encrypted Data not as expected") } gokrb5-8.4.3/v8/types/HostAddress.go000066400000000000000000000077261427031340300172030ustar00rootroot00000000000000package types // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.5 import ( "bytes" "fmt" "net" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/iana/addrtype" ) // HostAddresses implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.5 type HostAddresses []HostAddress // HostAddress implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.5 type HostAddress struct { AddrType int32 `asn1:"explicit,tag:0"` Address []byte `asn1:"explicit,tag:1"` } // GetHostAddress returns a HostAddress struct from a string in the format : func GetHostAddress(s string) (HostAddress, error) { var h HostAddress cAddr, _, err := net.SplitHostPort(s) if err != nil { return h, fmt.Errorf("invalid format of client address: %v", err) } ip := net.ParseIP(cAddr) var ht int32 if ip.To4() != nil { ht = addrtype.IPv4 ip = ip.To4() } else if ip.To16() != nil { ht = addrtype.IPv6 ip = ip.To16() } else { return h, fmt.Errorf("could not determine client's address types: %v", err) } h = HostAddress{ AddrType: ht, Address: ip, } return h, nil } // GetAddress returns a string representation of the HostAddress. func (h *HostAddress) GetAddress() (string, error) { var b []byte _, err := asn1.Unmarshal(h.Address, &b) return string(b), err } // LocalHostAddresses returns a HostAddresses struct for the local machines interface IP addresses. func LocalHostAddresses() (ha HostAddresses, err error) { ifs, err := net.Interfaces() if err != nil { return } for _, iface := range ifs { if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 { // Interface is either loopback of not up continue } addrs, err := iface.Addrs() if err != nil { continue } for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: ip = v.IP case *net.IPAddr: ip = v.IP } var a HostAddress if ip.To16() == nil { //neither IPv4 or IPv6 continue } if ip.To4() != nil { //Is IPv4 a.AddrType = addrtype.IPv4 a.Address = ip.To4() } else { a.AddrType = addrtype.IPv6 a.Address = ip.To16() } ha = append(ha, a) } } return ha, nil } // HostAddressesFromNetIPs returns a HostAddresses type from a slice of net.IP func HostAddressesFromNetIPs(ips []net.IP) (ha HostAddresses) { for _, ip := range ips { ha = append(ha, HostAddressFromNetIP(ip)) } return ha } // HostAddressFromNetIP returns a HostAddress type from a net.IP func HostAddressFromNetIP(ip net.IP) HostAddress { if ip.To4() != nil { //Is IPv4 return HostAddress{ AddrType: addrtype.IPv4, Address: ip.To4(), } } return HostAddress{ AddrType: addrtype.IPv6, Address: ip.To16(), } } // HostAddressesEqual tests if two HostAddress slices are equal. func HostAddressesEqual(h, a []HostAddress) bool { if len(h) != len(a) { return false } for _, e := range a { var found bool for _, i := range h { if e.Equal(i) { found = true break } } if !found { return false } } return true } // HostAddressesContains tests if a HostAddress is contained in a HostAddress slice. func HostAddressesContains(h []HostAddress, a HostAddress) bool { for _, e := range h { if e.Equal(a) { return true } } return false } // Equal tests if the HostAddress is equal to another HostAddress provided. func (h *HostAddress) Equal(a HostAddress) bool { if h.AddrType != a.AddrType { return false } return bytes.Equal(h.Address, a.Address) } // Contains tests if a HostAddress is contained within the HostAddresses struct. func (h *HostAddresses) Contains(a HostAddress) bool { for _, e := range *h { if e.Equal(a) { return true } } return false } // Equal tests if a HostAddress slice is equal to the HostAddresses struct. func (h *HostAddresses) Equal(a []HostAddress) bool { if len(*h) != len(a) { return false } for _, e := range a { if !h.Contains(e) { return false } } return true } gokrb5-8.4.3/v8/types/HostAddress_test.go000066400000000000000000000014131427031340300202250ustar00rootroot00000000000000package types import ( "encoding/hex" "testing" "github.com/jcmturner/gokrb5/v8/iana/addrtype" "github.com/stretchr/testify/assert" ) func TestGetHostAddress(t *testing.T) { tests := []struct { str string ipType int32 hex string }{ {"192.168.1.100", addrtype.IPv4, "c0a80164"}, {"127.0.0.1", addrtype.IPv4, "7f000001"}, {"[fe80::1cf3:b43b:df29:d43e]", addrtype.IPv6, "fe800000000000001cf3b43bdf29d43e"}, } for _, test := range tests { h, err := GetHostAddress(test.str + ":1234") if err != nil { t.Errorf("error getting host for %s: %v", test.str, err) } assert.Equal(t, test.ipType, h.AddrType, "wrong address type for %s", test.str) assert.Equal(t, test.hex, hex.EncodeToString(h.Address), "wrong address bytes for %s", test.str) } } gokrb5-8.4.3/v8/types/KerberosFlags.go000066400000000000000000000030041427031340300174720ustar00rootroot00000000000000package types // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.2.8 import ( "github.com/jcmturner/gofork/encoding/asn1" ) // NewKrbFlags returns an ASN1 BitString struct of the right size for KrbFlags. func NewKrbFlags() asn1.BitString { f := asn1.BitString{} f.Bytes = make([]byte, 4) f.BitLength = len(f.Bytes) * 8 return f } // SetFlags sets the flags of an ASN1 BitString. func SetFlags(f *asn1.BitString, j []int) { for _, i := range j { SetFlag(f, i) } } // SetFlag sets a flag in an ASN1 BitString. func SetFlag(f *asn1.BitString, i int) { for l := len(f.Bytes); l < 4; l++ { (*f).Bytes = append((*f).Bytes, byte(0)) (*f).BitLength = len((*f).Bytes) * 8 } //Which byte? b := i / 8 //Which bit in byte p := uint(7 - (i - 8*b)) (*f).Bytes[b] = (*f).Bytes[b] | (1 << p) } // UnsetFlags unsets flags in an ASN1 BitString. func UnsetFlags(f *asn1.BitString, j []int) { for _, i := range j { UnsetFlag(f, i) } } // UnsetFlag unsets a flag in an ASN1 BitString. func UnsetFlag(f *asn1.BitString, i int) { for l := len(f.Bytes); l < 4; l++ { (*f).Bytes = append((*f).Bytes, byte(0)) (*f).BitLength = len((*f).Bytes) * 8 } //Which byte? b := i / 8 //Which bit in byte p := uint(7 - (i - 8*b)) (*f).Bytes[b] = (*f).Bytes[b] &^ (1 << p) } // IsFlagSet tests if a flag is set in the ASN1 BitString. func IsFlagSet(f *asn1.BitString, i int) bool { //Which byte? b := i / 8 //Which bit in byte p := uint(7 - (i - 8*b)) if (*f).Bytes[b]&(1</@ // a PrincipalName type will be returned with the name type set to KRB_NT_PRINCIPAL(1) // and the realm will be returned as a string. If the "@" suffix // is not included in the SPN then the value of realm string returned will be "" func ParseSPNString(spn string) (pn PrincipalName, realm string) { if strings.Contains(spn, "@") { s := strings.Split(spn, "@") realm = s[len(s)-1] spn = strings.TrimSuffix(spn, "@"+realm) } pn = NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) return } gokrb5-8.4.3/v8/types/PrincipalName_test.go000066400000000000000000000030501427031340300205230ustar00rootroot00000000000000package types import ( "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/stretchr/testify/assert" "testing" ) func TestPrincipalName_GetSalt(t *testing.T) { t.Parallel() pn := PrincipalName{ NameType: 1, NameString: []string{"firststring", "secondstring"}, } assert.Equal(t, "TEST.GOKRB5firststringsecondstring", pn.GetSalt("TEST.GOKRB5"), "Principal name default salt not as expected") } func TestParseSPNString(t *testing.T) { pn, realm := ParseSPNString("HTTP/www.example.com@REALM.COM") assert.Equal(t, "REALM.COM", realm, "realm value not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, pn.NameType, "name type not as expected") assert.Equal(t, "HTTP", pn.NameString[0], "first element of name string not as expected") assert.Equal(t, "www.example.com", pn.NameString[1], "second element of name string not as expected") pn, realm = ParseSPNString("HTTP/www.example.com") assert.Equal(t, "", realm, "realm value not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, pn.NameType, "name type not as expected") assert.Equal(t, "HTTP", pn.NameString[0], "first element of name string not as expected") assert.Equal(t, "www.example.com", pn.NameString[1], "second element of name string not as expected") pn, realm = ParseSPNString("www.example.com@REALM.COM") assert.Equal(t, "REALM.COM", realm, "realm value not as expected") assert.Equal(t, nametype.KRB_NT_PRINCIPAL, pn.NameType, "name type not as expected") assert.Equal(t, "www.example.com", pn.NameString[0], "second element of name string not as expected") } gokrb5-8.4.3/v8/types/TypedData.go000066400000000000000000000010561427031340300166250ustar00rootroot00000000000000package types import "github.com/jcmturner/gofork/encoding/asn1" // TypedData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.9.1 type TypedData struct { DataType int32 `asn1:"explicit,tag:0"` DataValue []byte `asn1:"optional,explicit,tag:1"` } // TypedDataSequence implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.9.1 type TypedDataSequence []TypedData // Unmarshal bytes into the TypedDataSequence. func (a *TypedDataSequence) Unmarshal(b []byte) error { _, err := asn1.Unmarshal(b, a) return err } gokrb5-8.4.3/v8/types/TypedData_test.go000066400000000000000000000015031427031340300176610ustar00rootroot00000000000000package types import ( "encoding/hex" "fmt" "testing" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/test/testdata" "github.com/stretchr/testify/assert" ) func TestUnmarshalTypedData(t *testing.T) { t.Parallel() var a TypedDataSequence b, err := hex.DecodeString(testdata.MarshaledKRB5typed_data) if err != nil { t.Fatalf("Test vector read error: %v", err) } err = a.Unmarshal(b) if err != nil { t.Fatalf("Unmarshal error: %v", err) } assert.Equal(t, 2, len(a), "Number of typed data elements not as expected") for i, d := range a { assert.Equal(t, patype.PA_SAM_RESPONSE, d.DataType, fmt.Sprintf("Data type of element %d not as expected", i+1)) assert.Equal(t, []byte(testdata.TEST_PADATA_VALUE), d.DataValue, fmt.Sprintf("Data value of element %d not as expected", i+1)) } }