Add Kerberos SPNEGO auth plugin (#7908)

This commit is contained in:
Becca Petrin
2019-12-11 11:18:37 -08:00
committed by GitHub
parent f259edcceb
commit 73fd8f314f
166 changed files with 21105 additions and 956 deletions

View File

@@ -342,6 +342,7 @@ func TestPredict_Plugins(t *testing.T) {
"hana-database-plugin", "hana-database-plugin",
"influxdb-database-plugin", "influxdb-database-plugin",
"jwt", "jwt",
"kerberos",
"kmip", "kmip",
"kubernetes", "kubernetes",
"kv", "kv",

View File

@@ -28,6 +28,7 @@ import (
credCF "github.com/hashicorp/vault-plugin-auth-cf" credCF "github.com/hashicorp/vault-plugin-auth-cf"
credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin" credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin"
credOIDC "github.com/hashicorp/vault-plugin-auth-jwt" credOIDC "github.com/hashicorp/vault-plugin-auth-jwt"
credKerb "github.com/hashicorp/vault-plugin-auth-kerberos"
credOCI "github.com/hashicorp/vault-plugin-auth-oci" credOCI "github.com/hashicorp/vault-plugin-auth-oci"
credAws "github.com/hashicorp/vault/builtin/credential/aws" credAws "github.com/hashicorp/vault/builtin/credential/aws"
credCert "github.com/hashicorp/vault/builtin/credential/cert" credCert "github.com/hashicorp/vault/builtin/credential/cert"
@@ -176,6 +177,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
"cf": &credCF.CLIHandler{}, "cf": &credCF.CLIHandler{},
"gcp": &credGcp.CLIHandler{}, "gcp": &credGcp.CLIHandler{},
"github": &credGitHub.CLIHandler{}, "github": &credGitHub.CLIHandler{},
"kerberos": &credKerb.CLIHandler{},
"ldap": &credLdap.CLIHandler{}, "ldap": &credLdap.CLIHandler{},
"oci": &credOCI.CLIHandler{}, "oci": &credOCI.CLIHandler{},
"oidc": &credOIDC.CLIHandler{}, "oidc": &credOIDC.CLIHandler{},

11
go.mod
View File

@@ -74,6 +74,7 @@ require (
github.com/hashicorp/vault-plugin-auth-cf v0.0.0-20190821162840-1c2205826fee github.com/hashicorp/vault-plugin-auth-cf v0.0.0-20190821162840-1c2205826fee
github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190930204802-acfd134850c2 github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190930204802-acfd134850c2
github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20191010173058-65cf93bad3f2 github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20191010173058-65cf93bad3f2
github.com/hashicorp/vault-plugin-auth-kerberos v0.1.1
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190925162726-2e5b0b8184e6 github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190925162726-2e5b0b8184e6
github.com/hashicorp/vault-plugin-auth-oci v0.0.0-20190904175623-97c0c0187c5c github.com/hashicorp/vault-plugin-auth-oci v0.0.0-20190904175623-97c0c0187c5c
github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190814210117-e079e01fbb93 github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190814210117-e079e01fbb93
@@ -83,8 +84,8 @@ require (
github.com/hashicorp/vault-plugin-secrets-gcp v0.5.3-0.20191112195538-3c798536d157 github.com/hashicorp/vault-plugin-secrets-gcp v0.5.3-0.20191112195538-3c798536d157
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.2-0.20190814210149-315cdbf5de6e github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.2-0.20190814210149-315cdbf5de6e
github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d0 github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d0
github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff github.com/hashicorp/vault/api v1.0.5-0.20191208020111-805a0bc9b460
github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2 github.com/hashicorp/vault/sdk v0.1.14-0.20191208020111-805a0bc9b460
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
github.com/jackc/pgx v3.3.0+incompatible // indirect github.com/jackc/pgx v3.3.0+incompatible // indirect
@@ -109,7 +110,7 @@ require (
github.com/nwaples/rardecode v1.0.0 // indirect github.com/nwaples/rardecode v1.0.0 // indirect
github.com/oklog/run v1.0.0 github.com/oklog/run v1.0.0
github.com/oracle/oci-go-sdk v7.0.0+incompatible github.com/oracle/oci-go-sdk v7.0.0+incompatible
github.com/ory/dockertest v3.3.4+incompatible github.com/ory/dockertest v3.3.5+incompatible
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/posener/complete v1.2.1 github.com/posener/complete v1.2.1
@@ -122,12 +123,12 @@ require (
github.com/shirou/gopsutil v2.19.9+incompatible github.com/shirou/gopsutil v2.19.9+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.4.0
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
go.etcd.io/bbolt v1.3.2 go.etcd.io/bbolt v1.3.2
go.etcd.io/etcd v0.0.0-20190412021913-f29b1ada1971 go.etcd.io/etcd v0.0.0-20190412021913-f29b1ada1971
go.uber.org/atomic v1.4.0 go.uber.org/atomic v1.4.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
google.golang.org/api v0.5.0 google.golang.org/api v0.5.0

26
go.sum
View File

@@ -178,6 +178,8 @@ github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ= github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ=
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -335,6 +337,8 @@ github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/gokrb5 v7.3.1-0.20191209171754-1a6fa9886ec3+incompatible h1:9fIGLV0+jeF9D/oT4gZE0oh336tsKA7mxjinPOxKIjU=
github.com/hashicorp/gokrb5 v7.3.1-0.20191209171754-1a6fa9886ec3+incompatible/go.mod h1:ke+MQBkyg7J7V+tM7GWSeDTlF27zlpm+u9nP3AANFEg=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -374,6 +378,8 @@ github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190930204802-acfd134850c2
github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190930204802-acfd134850c2/go.mod h1:j0hMnnTD44zXGQhLM1jarYDaTmSp6OPiOzgFQ6mNgzc= github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190930204802-acfd134850c2/go.mod h1:j0hMnnTD44zXGQhLM1jarYDaTmSp6OPiOzgFQ6mNgzc=
github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20191010173058-65cf93bad3f2 h1:Oi9HO9/JItId2XYLEoTIW9Wcfg5sblxxO5Nr7ln1jnk= github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20191010173058-65cf93bad3f2 h1:Oi9HO9/JItId2XYLEoTIW9Wcfg5sblxxO5Nr7ln1jnk=
github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20191010173058-65cf93bad3f2/go.mod h1:Ti2NPndKhSGpSL6gWg11n7TkmuI7318BIPeojayIVRU= github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20191010173058-65cf93bad3f2/go.mod h1:Ti2NPndKhSGpSL6gWg11n7TkmuI7318BIPeojayIVRU=
github.com/hashicorp/vault-plugin-auth-kerberos v0.1.1 h1:yFjNLRr4PqUgIa6rgvkP/lFtNu0sC9tIqbsKz3ulCsQ=
github.com/hashicorp/vault-plugin-auth-kerberos v0.1.1/go.mod h1:w86spj0Uf45A/F1DqsPn/r3w+VColv+yC0CEZCrv5s0=
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190925162726-2e5b0b8184e6 h1:WgxwYXCuZJtU/oIDah4A99+MuqzzL/oGQu9421IYZ6M= github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190925162726-2e5b0b8184e6 h1:WgxwYXCuZJtU/oIDah4A99+MuqzzL/oGQu9421IYZ6M=
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190925162726-2e5b0b8184e6/go.mod h1:qkrONCr71ckSCTItJQ1j9uet/faieZJ5c7+GZugTm7s= github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190925162726-2e5b0b8184e6/go.mod h1:qkrONCr71ckSCTItJQ1j9uet/faieZJ5c7+GZugTm7s=
github.com/hashicorp/vault-plugin-auth-oci v0.0.0-20190904175623-97c0c0187c5c h1:z6LQZvs1OtoVy2XgbgNhiDgp0U62Xbstn7/cgNZvh6g= github.com/hashicorp/vault-plugin-auth-oci v0.0.0-20190904175623-97c0c0187c5c h1:z6LQZvs1OtoVy2XgbgNhiDgp0U62Xbstn7/cgNZvh6g=
@@ -405,6 +411,8 @@ github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGU
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90=
github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
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/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2 h1:mex1izRBCD+7WjieGgRdy7e651vD/lvB1bD9vNE/3K4= github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2 h1:mex1izRBCD+7WjieGgRdy7e651vD/lvB1bD9vNE/3K4=
github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2/go.mod h1:xkfESuHriIekR+4RoV+fu91j/CfnYM29Zi2tMFw5iD4= github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2/go.mod h1:xkfESuHriIekR+4RoV+fu91j/CfnYM29Zi2tMFw5iD4=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
@@ -533,6 +541,8 @@ github.com/oracle/oci-go-sdk v7.0.0+incompatible h1:oj5ESjXwwkFRdhZSnPlShvLWYdt/
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8= github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8=
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA=
github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
@@ -631,6 +641,8 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -681,6 +693,8 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPl
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 h1:PDpCLFAH/YIX0QpHPf2eO7L4rC2OOirBrKtXTLLiNTY=
golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -820,6 +834,8 @@ google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -832,6 +848,16 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/ldap.v3 v3.0.3 h1:YKRHW/2sIl05JsCtx/5ZuUueFuJyoj/6+DGXe3wp6ro=
gopkg.in/ldap.v3 v3.0.3/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/ory-am/dockertest.v3 v3.3.4 h1:oen8RiwxVNxtQ1pRoV4e4jqh6UjNsOuIZ1NXns6jdcw= gopkg.in/ory-am/dockertest.v3 v3.3.4 h1:oen8RiwxVNxtQ1pRoV4e4jqh6UjNsOuIZ1NXns6jdcw=

View File

@@ -11,6 +11,7 @@ import (
credCF "github.com/hashicorp/vault-plugin-auth-cf" credCF "github.com/hashicorp/vault-plugin-auth-cf"
credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin" credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin"
credJWT "github.com/hashicorp/vault-plugin-auth-jwt" credJWT "github.com/hashicorp/vault-plugin-auth-jwt"
credKerb "github.com/hashicorp/vault-plugin-auth-kerberos"
credKube "github.com/hashicorp/vault-plugin-auth-kubernetes" credKube "github.com/hashicorp/vault-plugin-auth-kubernetes"
credOCI "github.com/hashicorp/vault-plugin-auth-oci" credOCI "github.com/hashicorp/vault-plugin-auth-oci"
credAppId "github.com/hashicorp/vault/builtin/credential/app-id" credAppId "github.com/hashicorp/vault/builtin/credential/app-id"
@@ -77,6 +78,7 @@ func newRegistry() *registry {
"gcp": credGcp.Factory, "gcp": credGcp.Factory,
"github": credGitHub.Factory, "github": credGitHub.Factory,
"jwt": credJWT.Factory, "jwt": credJWT.Factory,
"kerberos": credKerb.Factory,
"kubernetes": credKube.Factory, "kubernetes": credKube.Factory,
"ldap": credLdap.Factory, "ldap": credLdap.Factory,
"oci": credOCI.Factory, "oci": credOCI.Factory,

201
vendor/github.com/hashicorp/gokrb5/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
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.

86
vendor/github.com/hashicorp/gokrb5/asn1tools/tools.go generated vendored Normal file
View File

@@ -0,0 +1,86 @@
// 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
}
*/

189
vendor/github.com/hashicorp/gokrb5/client/ASExchange.go generated vendored Normal file
View File

@@ -0,0 +1,189 @@
package client
import (
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/crypto/etype"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/iana/patype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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) {
//The preferred ordering of the "hint" pre-authentication data that
//affect client key selection is: ETYPE-INFO2, followed by ETYPE-INFO,
//followed by PW-SALT.
//A KDC SHOULD NOT send PA-PW-SALT when issuing a KRB-ERROR message
//that requests additional pre-authentication. Implementation note:
//Some KDC implementations issue an erroneous PA-PW-SALT when issuing a
//KRB-ERROR message that requests additional pre-authentication.
//Therefore, clients SHOULD ignore a PA-PW-SALT accompanying a
//KRB-ERROR message that requests additional pre-authentication.
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
}

View File

@@ -0,0 +1,103 @@
package client
import (
"github.com/hashicorp/gokrb5/iana/flags"
"github.com/hashicorp/gokrb5/iana/nametype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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(), kdcRealm, cl.Config, tgt, sessionKey, 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: <SERVICE>/<FQDN> 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
}

110
vendor/github.com/hashicorp/gokrb5/client/cache.go generated vendored Normal file
View File

@@ -0,0 +1,110 @@
package client
import (
"errors"
"sync"
"time"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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
}

229
vendor/github.com/hashicorp/gokrb5/client/client.go generated vendored Normal file
View File

@@ -0,0 +1,229 @@
// Package client provides a client library and methods for Kerberos 5 authentication.
package client
import (
"errors"
"fmt"
"time"
"github.com/hashicorp/gokrb5/config"
"github.com/hashicorp/gokrb5/credentials"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/crypto/etype"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/iana/nametype"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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
}
// 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")
}

224
vendor/github.com/hashicorp/gokrb5/client/network.go generated vendored Normal file
View File

@@ -0,0 +1,224 @@
package client
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"time"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/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 https://tools.ietf.org/html/rfc4120#section-7.2.2
Each request (KRB_KDC_REQ) and response (KRB_KDC_REP or KRB_ERROR)
sent over the TCP stream is preceded by the length of the request as
4 octets in network byte order. The high bit of the length is
reserved for future expansion and MUST currently be set to zero. If
a KDC that does not understand how to interpret a set high bit of the
length encoding receives a request with the high order bit of the
length set, it MUST return a KRB-ERROR message with the error
KRB_ERR_FIELD_TOOLONG and MUST close the TCP stream.
NB: network byte order == big endian
*/
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
}

95
vendor/github.com/hashicorp/gokrb5/client/passwd.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
package client
import (
"fmt"
"net"
"github.com/hashicorp/gokrb5/kadmin"
"github.com/hashicorp/gokrb5/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 kdamin: %s", r.Result)
}
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)
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)
err = r.Unmarshal(rb)
return
}

255
vendor/github.com/hashicorp/gokrb5/client/session.go generated vendored Normal file
View File

@@ -0,0 +1,255 @@
package client
import (
"fmt"
"strings"
"sync"
"time"
"github.com/hashicorp/gokrb5/iana/nametype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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])
}

69
vendor/github.com/hashicorp/gokrb5/client/settings.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package 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...)
}
}

30
vendor/github.com/hashicorp/gokrb5/config/error.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package 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...),
}
}

139
vendor/github.com/hashicorp/gokrb5/config/hosts.go generated vendored Normal file
View File

@@ -0,0 +1,139 @@
package 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 {
ks = r.KDC
break
}
}
count = len(ks)
if count < 1 {
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"
}
c, 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 = c
for k, v := range addrs {
kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
}
}
// Order the kdcs randomly for preference.
kdcs = randServOrder(ks)
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
}

726
vendor/github.com/hashicorp/gokrb5/config/krb5conf.go generated vendored Normal file
View File

@@ -0,0 +1,726 @@
// 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/hashicorp/gokrb5/iana/etypeID"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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)
}

View File

@@ -0,0 +1,348 @@
package credentials
import (
"bytes"
"encoding/binary"
"errors"
"io/ioutil"
"strings"
"time"
"unsafe"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
const (
headerFieldTagKDCOffset = 1
)
// The first byte of the file always has the value 5.
// The value of the second byte contains the version number (1 through 4)
// Versions 1 and 2 of the file format use native byte order for integer representations.
// Versions 3 and 4 always use big-endian byte order
// After the two-byte version indicator, the file has three parts:
// 1) the header (in version 4 only)
// 2) the default principal name
// 3) a sequence of credentials
// 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 {
// At this time there is only one defined header field.
// Its tag value is 1, its length is always 8.
// Its contents are two 32-bit integers giving the seconds and microseconds
// of the time offset of the KDC relative to the client.
// Adding this offset to the current time on the client should give the current time on the KDC, if that offset has not changed since the initial authentication.
// Done as a switch in case other tag values are added in the future.
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
}

View File

@@ -0,0 +1,309 @@
// Package credentials provides credentials management for Kerberos 5 authentication.
package credentials
import (
"time"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/gokrb5/iana/nametype"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/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
}
// 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)
}

View File

@@ -0,0 +1,173 @@
package crypto
import (
"crypto/aes"
"crypto/hmac"
"crypto/sha1"
"hash"
"github.com/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/rfc3961"
"github.com/hashicorp/gokrb5/crypto/rfc3962"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/iana/etypeID"
)
// RFC 3962
//+--------------------------------------------------------------------+
//| protocol key format 128- or 256-bit string |
//| |
//| string-to-key function PBKDF2+DK with variable |
//| iteration count (see |
//| above) |
//| |
//| default string-to-key parameters 00 00 10 00 |
//| |
//| key-generation seed length key size |
//| |
//| random-to-key function identity function |
//| |
//| hash function, H SHA-1 |
//| |
//| HMAC output size, h 12 octets (96 bits) |
//| |
//| message block size, m 1 octet |
//| |
//| encryption/decryption functions, AES in CBC-CTS mode |
//| E and D (cipher block size 16 |
//| octets), with next-to- |
//| last block (last block |
//| if only one) as CBC-style |
//| ivec |
//+--------------------------------------------------------------------+
//
//+--------------------------------------------------------------------+
//| encryption types |
//+--------------------------------------------------------------------+
//| type name etype value key size |
//+--------------------------------------------------------------------+
//| aes128-cts-hmac-sha1-96 17 128 |
//| aes256-cts-hmac-sha1-96 18 256 |
//+--------------------------------------------------------------------+
//
//+--------------------------------------------------------------------+
//| checksum types |
//+--------------------------------------------------------------------+
//| type name sumtype value length |
//+--------------------------------------------------------------------+
//| hmac-sha1-96-aes128 15 96 |
//| hmac-sha1-96-aes256 16 96 |
//+--------------------------------------------------------------------+
// 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)
}

View File

@@ -0,0 +1,135 @@
package crypto
import (
"crypto/aes"
"crypto/hmac"
"crypto/sha256"
"hash"
"github.com/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/rfc8009"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/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.
// The HMAC is calculated over the cipher state concatenated with the
// AES output, instead of being calculated over the confounder and
// plaintext. This allows the message receiver to verify the
// integrity of the message before decrypting the message.
// Therefore 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)
}

View File

@@ -0,0 +1,173 @@
package crypto
import (
"crypto/aes"
"crypto/hmac"
"crypto/sha1"
"hash"
"github.com/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/rfc3961"
"github.com/hashicorp/gokrb5/crypto/rfc3962"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/iana/etypeID"
)
// RFC 3962
//+--------------------------------------------------------------------+
//| protocol key format 128- or 256-bit string |
//| |
//| string-to-key function PBKDF2+DK with variable |
//| iteration count (see |
//| above) |
//| |
//| default string-to-key parameters 00 00 10 00 |
//| |
//| key-generation seed length key size |
//| |
//| random-to-key function identity function |
//| |
//| hash function, H SHA-1 |
//| |
//| HMAC output size, h 12 octets (96 bits) |
//| |
//| message block size, m 1 octet |
//| |
//| encryption/decryption functions, AES in CBC-CTS mode |
//| E and D (cipher block size 16 |
//| octets), with next-to- |
//| last block (last block |
//| if only one) as CBC-style |
//| ivec |
//+--------------------------------------------------------------------+
//
//+--------------------------------------------------------------------+
//| encryption types |
//+--------------------------------------------------------------------+
//| type name etype value key size |
//+--------------------------------------------------------------------+
//| aes128-cts-hmac-sha1-96 17 128 |
//| aes256-cts-hmac-sha1-96 18 256 |
//+--------------------------------------------------------------------+
//
//+--------------------------------------------------------------------+
//| checksum types |
//+--------------------------------------------------------------------+
//| type name sumtype value length |
//+--------------------------------------------------------------------+
//| hmac-sha1-96-aes128 15 96 |
//| hmac-sha1-96-aes256 16 96 |
//+--------------------------------------------------------------------+
// 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)
}

View File

@@ -0,0 +1,135 @@
package crypto
import (
"crypto/aes"
"crypto/hmac"
"crypto/sha512"
"hash"
"github.com/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/rfc8009"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/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.
// The HMAC is calculated over the cipher state concatenated with the
// AES output, instead of being calculated over the confounder and
// plaintext. This allows the message receiver to verify the
// integrity of the message before decrypting the message.
// Therefore 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)
}

View File

@@ -0,0 +1,143 @@
// Package common provides encryption methods common across encryption types
package common
import (
"bytes"
"crypto/hmac"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/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 ciphertext output is the concatenation of the output of the basic
//encryption function E and a (possibly truncated) HMAC using the
//specified hash function H, both applied to the plaintext with a
//random confounder prefix and sufficient padding to bring it to a
//multiple of the message block size. When the HMAC is computed, the
//key is used in the protocol key form.
expectedMAC, _ := GetChecksumHash(msg, key, usage, etype)
return hmac.Equal(chksum, expectedMAC)
}
// GetUsageKc returns the checksum key usage value for the usage number un.
//
// RFC 3961: The "well-known constant" used for the DK function is the key usage number, expressed as four octets in big-endian order, followed by one octet indicated below.
//
// Kc = DK(base-key, usage | 0x99);
func GetUsageKc(un uint32) []byte {
return getUsage(un, 0x99)
}
// GetUsageKe returns the encryption key usage value for the usage number un
//
// RFC 3961: The "well-known constant" used for the DK function is the key usage number, expressed as four octets in big-endian order, followed by one octet indicated below.
//
// Ke = DK(base-key, usage | 0xAA);
func GetUsageKe(un uint32) []byte {
return getUsage(un, 0xAA)
}
// GetUsageKi returns the integrity key usage value for the usage number un
//
// RFC 3961: The "well-known constant" used for the DK function is the key usage number, expressed as four octets in big-endian order, followed by one octet indicated below.
//
// Ki = DK(base-key, usage | 0x55);
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)
}

175
vendor/github.com/hashicorp/gokrb5/crypto/crypto.go generated vendored Normal file
View File

@@ -0,0 +1,175 @@
// Package crypto implements cryptographic functions for Kerberos 5 implementation.
package crypto
import (
"encoding/hex"
"fmt"
"github.com/hashicorp/gokrb5/crypto/etype"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/iana/etypeID"
"github.com/hashicorp/gokrb5/iana/patype"
"github.com/hashicorp/gokrb5/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
}

View File

@@ -0,0 +1,174 @@
package crypto
import (
"crypto/des"
"crypto/hmac"
"crypto/sha1"
"errors"
"hash"
"github.com/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/rfc3961"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/iana/etypeID"
)
//RFC: 3961 Section 6.3
/*
des3-cbc-hmac-sha1-kd, hmac-sha1-des3-kd
------------------------------------------------
protocol key format 24 bytes, parity in low
bit of each
key-generation seed 21 bytes
length
hash function SHA-1
HMAC output size 160 bits
message block size 8 bytes
default string-to-key empty string
params
encryption and triple-DES encrypt and
decryption functions decrypt, in outer-CBC
mode (cipher block size
8 octets)
key generation functions:
random-to-key DES3random-to-key (see
below)
string-to-key DES3string-to-key (see
below)
The des3-cbc-hmac-sha1-kd encryption type is assigned the value
sixteen (16). The hmac-sha1-des3-kd checksum algorithm is assigned a
checksum type number of twelve (12)*/
// 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)
}

View File

@@ -0,0 +1,29 @@
// 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 // key-generation seed length, k
GetDefaultStringToKeyParams() string // default string-to-key parameters (s2kparams)
StringToKey(string, salt, s2kparams string) ([]byte, error) // string-to-key (UTF-8 string, UTF-8 string, opaque)->(protocol-key)
RandomToKey(b []byte) []byte // random-to-key (bitstring[K])->(protocol-key)
GetHMACBitLength() int // HMAC output size, h
GetMessageBlockByteSize() int // message block size, m
EncryptData(key, data []byte) ([]byte, []byte, error) // E function - encrypt (specific-key, state, octet string)->(state, octet string)
EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error)
DecryptData(key, data []byte) ([]byte, error) // D function
DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error)
GetCypherBlockBitLength() int // cipher block size, c
GetConfounderByteSize() int // This is the same as the cipher block size but in bytes.
DeriveKey(protocolKey, usage []byte) ([]byte, error) // DK key-derivation (protocol-key, integer)->(specific-key)
DeriveRandom(protocolKey, usage []byte) ([]byte, error) // DR pseudo-random (protocol-key, octet-string)->(octet-string)
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
}

135
vendor/github.com/hashicorp/gokrb5/crypto/rc4-hmac.go generated vendored Normal file
View File

@@ -0,0 +1,135 @@
package crypto
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"hash"
"io"
"github.com/hashicorp/gokrb5/crypto/rfc3961"
"github.com/hashicorp/gokrb5/crypto/rfc4757"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/iana/etypeID"
"golang.org/x/crypto/md4"
)
//http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/security/krb5/internal/crypto/dk/ArcFourCrypto.java#ArcFourCrypto.encrypt%28byte%5B%5D%2Cint%2Cbyte%5B%5D%2Cbyte%5B%5D%2Cbyte%5B%5D%2Cint%2Cint%29
// RC4HMAC implements Kerberos encryption type aes256-cts-hmac-sha1-96
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)
}

View File

@@ -0,0 +1,125 @@
// 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/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/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 {
//The ciphertext output is the concatenation of the output of the basic
//encryption function E and a (possibly truncated) HMAC using the
//specified hash function H, both applied to the plaintext with a
//random confounder prefix and sufficient padding to bring it to a
//multiple of the message block size. When the HMAC is computed, the
//key is used in the protocol key form.
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)
}

View File

@@ -0,0 +1,178 @@
package rfc3961
import (
"bytes"
"github.com/hashicorp/gokrb5/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)
/*If the output of E is shorter than k bits, it is fed back into the encryption as many times as necessary.
The construct is as follows (where | indicates concatenation):
K1 = E(Key, n-fold(Constant), initial-cipher-state)
K2 = E(Key, K1, initial-cipher-state)
K3 = E(Key, K2, initial-cipher-state)
K4 = ...
DR(Key, Constant) = k-truncate(K1 | K2 | K3 | K4 ...)*/
_, 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
}

View File

@@ -0,0 +1,128 @@
package rfc3961
/*
Implementation of the n-fold algorithm as defined in RFC 3961.
n-fold is an algorithm that takes m input bits and "stretches" them
to form n output bits with equal contribution from each input bit to
the output, as described in [Blumenthal96]:
We first define a primitive called n-folding, which takes a
variable-length input block and produces a fixed-length output
sequence. The intent is to give each input bit approximately
equal weight in determining the value of each output bit. Note
that whenever we need to treat a string of octets as a number, the
assumed representation is Big-Endian -- Most Significant Byte
first.
To n-fold a number X, replicate the input value to a length that
is the least common multiple of n and the length of X. Before
each repetition, the input is rotated to the right by 13 bit
positions. The successive n-bit chunks are added together using
1's-complement addition (that is, with end-around carry) to yield
a n-bit result....
*/
/* 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
}

View File

@@ -0,0 +1,89 @@
// Package rfc3962 provides encryption and checksum methods as specified in RFC 3962
package rfc3962
import (
"crypto/rand"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/etype"
"gopkg.in/jcmturner/aescts.v1"
)
// 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
}

View File

@@ -0,0 +1,58 @@
package rfc3962
import (
"encoding/binary"
"encoding/hex"
"errors"
"github.com/hashicorp/gokrb5/crypto/etype"
"github.com/jcmturner/gofork/x/crypto/pbkdf2"
)
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) {
//process s2kparams string
//The parameter string is four octets indicating an unsigned
//number in big-endian order. This is the number of iterations to be
//performed. If the value is 00 00 00 00, the number of iterations to
//be performed is 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)
//buf := bytes.NewBuffer(b)
//err = binary.Read(buf, binary.BigEndian, &i)
if err != nil {
return int64(s2kParamsZero), errors.New("invalid s2kparams, cannot convert to big endian int32")
}
return int64(i), nil
}

View File

@@ -0,0 +1,40 @@
package 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)
}

View File

@@ -0,0 +1,80 @@
// 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/hashicorp/gokrb5/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])
}

View File

@@ -0,0 +1,55 @@
package 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) {
//if export {
// L40 := make([]byte, 14, 14)
// copy(L40, []byte(`fortybits`))
// k1 = HMAC(key, L40)
//} else {
// tb := MessageTypeBytes(usage)
// k1 = HMAC(key, tb)
//}
//k2 = k1[:16]
//if export {
// mask := []byte{0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB}
// copy(k1[7:16], mask)
//}
//k3 = HMAC(k1, checksum)
//return
k1 = key
k2 = HMAC(k1, UsageToMSMsgType(usage))
k3 = HMAC(k2, checksum)
return
}

View File

@@ -0,0 +1,20 @@
package 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
}

View File

@@ -0,0 +1,128 @@
// 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/hashicorp/gokrb5/crypto/common"
"github.com/hashicorp/gokrb5/crypto/etype"
"github.com/hashicorp/gokrb5/iana/etypeID"
"gopkg.in/jcmturner/aescts.v1"
)
// 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
// The HMAC is calculated over the cipher state concatenated with the
// AES output, instead of being calculated over the confounder and
// plaintext. This allows the message receiver to verify the
// integrity of the message before decrypting the message.
// H = HMAC(Ki, IV | C)
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)
}

View File

@@ -0,0 +1,144 @@
package rfc8009
import (
"crypto/hmac"
"encoding/binary"
"encoding/hex"
"errors"
"github.com/hashicorp/gokrb5/crypto/etype"
"github.com/hashicorp/gokrb5/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
//
// If the enctype is aes128-cts-hmac-sha256-128:
// Kc = KDF-HMAC-SHA2(base-key, usage | 0x99, 128)
// Ke = KDF-HMAC-SHA2(base-key, usage | 0xAA, 128)
// Ki = KDF-HMAC-SHA2(base-key, usage | 0x55, 128)
//
// If the enctype is aes256-cts-hmac-sha384-192:
// Kc = KDF-HMAC-SHA2(base-key, usage | 0x99, 192)
// Ke = KDF-HMAC-SHA2(base-key, usage | 0xAA, 256)
// Ki = KDF-HMAC-SHA2(base-key, usage | 0x55, 192)
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
}

202
vendor/github.com/hashicorp/gokrb5/gssapi/MICToken.go generated vendored Normal file
View File

@@ -0,0 +1,202 @@
package gssapi
import (
"bytes"
"crypto/hmac"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/types"
)
/*
From RFC 4121, section 4.2.6.1:
Use of the GSS_GetMIC() call yields a token (referred as the MIC
token in this document), separate from the user data being protected,
which can be used to verify the integrity of that data as received.
The token has the following format:
Octet no Name Description
--------------------------------------------------------------
0..1 TOK_ID Identification field. Tokens emitted by
GSS_GetMIC() contain the hex value 04 04
expressed in big-endian order in this
field.
2 Flags Attributes field, as described in section
4.2.2.
3..7 Filler Contains five octets of hex value FF.
8..15 SND_SEQ Sequence number field in clear text,
expressed in big-endian order.
16..last SGN_CKSUM Checksum of the "to-be-signed" data and
octet 0..15, as described in section 4.2.4.
The Filler field is included in the checksum calculation for
simplicity.
*/
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.
// Confirms to RFC 4121 in that the checksum will be computed over { body | header }.
// In the context of Kerberos MIC tokens, mostly keyusage GSSAPI_ACCEPTOR_SIGN (=23)
// and GSSAPI_INITIATOR_SIGN (=25) will be used.
// 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
}

20
vendor/github.com/hashicorp/gokrb5/gssapi/README.md generated vendored Normal file
View File

@@ -0,0 +1,20 @@
# 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 |

View File

@@ -0,0 +1,25 @@
package 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
}

199
vendor/github.com/hashicorp/gokrb5/gssapi/gssapi.go generated vendored Normal file
View File

@@ -0,0 +1,199 @@
// 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
}

235
vendor/github.com/hashicorp/gokrb5/gssapi/wrapToken.go generated vendored Normal file
View File

@@ -0,0 +1,235 @@
package gssapi
import (
"bytes"
"crypto/hmac"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/types"
)
/*
From RFC 4121, section 4.2.6.2:
Use of the GSS_Wrap() call yields a token (referred as the Wrap token
in this document), which consists of a descriptive header, followed
by a body portion that contains either the input user data in
plaintext concatenated with the checksum, or the input user data
encrypted. The GSS_Wrap() token SHALL have the following format:
Octet no Name Description
--------------------------------------------------------------
0..1 TOK_ID Identification field. Tokens emitted by
GSS_Wrap() contain the hex value 05 04
expressed in big-endian order in this
field.
2 Flags Attributes field, as described in section
4.2.2.
3 Filler Contains the hex value FF.
4..5 EC Contains the "extra count" field, in big-
endian order as described in section 4.2.3.
6..7 RRC Contains the "right rotation count" in big-
endian order, as described in section
4.2.5.
8..15 SndSeqNum Sequence number field in clear text,
expressed in big-endian order.
16..last Data Encrypted data for Wrap tokens with
confidentiality, or plaintext data followed
by the checksum for Wrap tokens without
confidentiality, as described in section
4.2.4.
Quick notes:
- "EC" or "Extra Count" refers to the length of the checksum.
- "Flags" (complete details in section 4.2.2) is a set of bits:
- if bit 0 is set, it means the token was sent by the acceptor (generally the kerberized service).
- bit 1 indicates that the token's payload is encrypted
- bit 2 indicates if the message is protected using a subkey defined by the acceptor.
- When computing checksums, EC and RRC MUST be set to 0.
- Wrap Tokens are not ASN.1 encoded.
*/
const (
HdrLen = 16 // Length of the Wrap Token's header
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.
// Conforms to RFC 4121 in that the checksum will be computed over { body | header },
// with the EC and RRC flags zeroed out.
// In the context of Kerberos Wrap tokens, mostly keyusage GSSAPI_ACCEPTOR_SEAL (=22)
// and GSSAPI_INITIATOR_SEAL (=24) will be used.
// 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
}

View File

@@ -0,0 +1,15 @@
// 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
)

View File

@@ -0,0 +1,23 @@
// 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
)

View File

@@ -0,0 +1,24 @@
// 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
)

View File

@@ -0,0 +1,32 @@
// 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
)

5
vendor/github.com/hashicorp/gokrb5/iana/constants.go generated vendored Normal file
View File

@@ -0,0 +1,5 @@
// Package iana provides Kerberos 5 assigned numbers.
package iana
// PVNO is the Protocol Version Number.
const PVNO = 5

View File

@@ -0,0 +1,155 @@
// 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",
}

View File

@@ -0,0 +1,101 @@
// 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
}

View File

@@ -0,0 +1,36 @@
// 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.
)

View File

@@ -0,0 +1,42 @@
// 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.
)

View File

@@ -0,0 +1,18 @@
// 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
)

View File

@@ -0,0 +1,15 @@
// 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
)

View File

@@ -0,0 +1,77 @@
// 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
)

View File

@@ -0,0 +1,23 @@
package kadmin
import (
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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
}

114
vendor/github.com/hashicorp/gokrb5/kadmin/message.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package kadmin
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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
}

68
vendor/github.com/hashicorp/gokrb5/kadmin/passwd.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// Package kadmin provides Kerberos administration capabilities.
package kadmin
import (
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/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
}

466
vendor/github.com/hashicorp/gokrb5/keytab/keytab.go generated vendored Normal file
View File

@@ -0,0 +1,466 @@
// 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"
"github.com/hashicorp/gokrb5/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
}
/*
After the two-byte version indicator, the file contains a sequence of signed 32-bit record lengths followed by key records or holes.
A positive record length indicates a valid key entry whose size is equal to or less than the record length.
A negative length indicates a zero-filled hole whose size is the inverse of the length.
A length of 0 indicates the end of the file.
*/
// 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 {
//fmt.Printf("Bytes for entry: %v\n", b[n:n+int(l)])
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.
// To determine if it is present, the implementation must check that at least 4 bytes remain in the record after the other fields are read,
// and that the value of the 32-bit integer contained in those bytes is non-zero.
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
}

67
vendor/github.com/hashicorp/gokrb5/krberror/error.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
// 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},
}
}

64
vendor/github.com/hashicorp/gokrb5/messages/APRep.go generated vendored Normal file
View File

@@ -0,0 +1,64 @@
package messages
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
/*
AP-REP ::= [APPLICATION 15] SEQUENCE {
pvno [0] INTEGER (5),
msg-type [1] INTEGER (15),
enc-part [2] EncryptedData -- EncAPRepPart
}
EncAPRepPart ::= [APPLICATION 27] SEQUENCE {
ctime [0] KerberosTime,
cusec [1] Microseconds,
subkey [2] EncryptionKey OPTIONAL,
seq-number [3] UInt32 OPTIONAL
}
*/
// 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
}

224
vendor/github.com/hashicorp/gokrb5/messages/APReq.go generated vendored Normal file
View File

@@ -0,0 +1,224 @@
package messages
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
/*AP-REQ ::= [APPLICATION 14] SEQUENCE {
pvno [0] INTEGER (5),
msg-type [1] INTEGER (14),
ap-options [2] APOptions,
ticket [3] Ticket,
authenticator [4] EncryptedData -- Authenticator
}
APOptions ::= KerberosFlags
-- reserved(0),
-- use-session-key(1),
-- mutual-required(2)*/
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) {
// //If the USE-SESSION-KEY flag is set in the ap-options field, it indicates to
// //the server that user-to-user authentication is in use, and that the ticket
// //is encrypted in the session key from the server's TGT rather than in the server's secret key.
// 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 {
// // Because it is possible for the server to be registered in multiple
// // realms, with different keys in each, the srealm field in the
// // unencrypted portion of the ticket in the KRB_AP_REQ is used to
// // specify which secret key the server should use to decrypt that
// // ticket.The KRB_AP_ERR_NOKEY error code is returned if the server
// // doesn't have the proper key to decipher the ticket.
// // The ticket is decrypted using the version of the server's key
// // specified by the ticket.
// 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 {
//The addresses in the ticket (if any) are then searched for an address matching the operating-system reported
//address of the client. If no match is found or the server insists on ticket addresses but none are present in
//the ticket, the KRB_AP_ERR_BADADDR error is returned.
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
}

312
vendor/github.com/hashicorp/gokrb5/messages/KDCRep.go generated vendored Normal file
View File

@@ -0,0 +1,312 @@
package messages
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.4.2
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/config"
"github.com/hashicorp/gokrb5/credentials"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/flags"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/iana/patype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
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
Compatibility note: Some implementations unconditionally send an
encrypted EncTGSRepPart (application tag number 26) in this field
regardless of whether the reply is a AS-REP or a TGS-REP. In the
interest of compatibility, implementors MAY relax the check on the
tag number of the decrypted ENC-PART.*/
_, 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
}

432
vendor/github.com/hashicorp/gokrb5/messages/KDCReq.go generated vendored Normal file
View File

@@ -0,0 +1,432 @@
package messages
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.4.1
import (
"crypto/rand"
"fmt"
"math"
"math/big"
"time"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/config"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/flags"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/iana/nametype"
"github.com/hashicorp/gokrb5/iana/patype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
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
}

102
vendor/github.com/hashicorp/gokrb5/messages/KRBCred.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
package messages
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
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
}

View File

@@ -0,0 +1,83 @@
// Package messages implements Kerberos 5 message types and methods.
package messages
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/iana"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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")
}
}

108
vendor/github.com/hashicorp/gokrb5/messages/KRBPriv.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
package messages
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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
}

61
vendor/github.com/hashicorp/gokrb5/messages/KRBSafe.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
package messages
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
/*
KRB-SAFE ::= [APPLICATION 20] SEQUENCE {
pvno [0] INTEGER (5),
msg-type [1] INTEGER (20),
safe-body [2] KRB-SAFE-BODY,
cksum [3] Checksum
}
KRB-SAFE-BODY ::= SEQUENCE {
user-data [0] OCTET STRING,
timestamp [1] KerberosTime OPTIONAL,
usec [2] Microseconds OPTIONAL,
seq-number [3] UInt32 OPTIONAL,
s-address [4] HostAddress,
r-address [5] HostAddress OPTIONAL
}
*/
// 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
}

265
vendor/github.com/hashicorp/gokrb5/messages/Ticket.go generated vendored Normal file
View File

@@ -0,0 +1,265 @@
package messages
import (
"crypto/rand"
"fmt"
"log"
"time"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana"
"github.com/hashicorp/gokrb5/iana/adtype"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/iana/flags"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/pac"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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
}

View File

@@ -0,0 +1,33 @@
package 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)
}
k.ClaimsSetMetadata = *m
k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet()
if err != nil {
err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err)
}
return
}

31
vendor/github.com/hashicorp/gokrb5/pac/client_info.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package 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
}

View File

@@ -0,0 +1,86 @@
package pac
import (
"bytes"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/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)
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
// This structure is encrypted prior to being encoded in any other structures.
// Encryption is performed by first serializing the data structure via Network Data Representation (NDR) encoding, as specified in [MS-RPCE].
// Once serialized, the data is encrypted using the key and cryptographic system selected through the AS protocol and the KRB_AS_REP message
// Fields (for capturing this information) and cryptographic parameters are specified in PAC_CREDENTIAL_INFO (section 2.6.1).
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
}

View File

@@ -0,0 +1,33 @@
package 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)
}
k.ClaimsSetMetadata = *m
k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet()
if err != nil {
err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err)
}
return
}

32
vendor/github.com/hashicorp/gokrb5/pac/device_info.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package 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
}

View File

@@ -0,0 +1,115 @@
// 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
// The KERB_VALIDATION_INFO structure defines the user's logon and authorization information
// provided by the DC. The KERB_VALIDATION_INFO structure is a subset of the
// NETLOGON_VALIDATION_SAM_INFO4 structure ([MS-NRPC] section 2.2.1.4.13).
// It is a subset due to historical reasons and to the use of the common Active Directory to generate this information.
// The KERB_VALIDATION_INFO structure is marshaled by RPC [MS-RPCE].
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
}

251
vendor/github.com/hashicorp/gokrb5/pac/pac_type.go generated vendored Normal file
View File

@@ -0,0 +1,251 @@
package pac
import (
"bytes"
"errors"
"fmt"
"log"
"github.com/hashicorp/gokrb5/crypto"
"github.com/hashicorp/gokrb5/iana/keyusage"
"github.com/hashicorp/gokrb5/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
}

View File

@@ -0,0 +1,26 @@
package 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
}

View File

@@ -0,0 +1,79 @@
package pac
import (
"bytes"
"github.com/hashicorp/gokrb5/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
The KDC will use the long-term key that the KDC shares with the server, so that the server can verify this signature on receiving a PAC.
The server signature is a keyed hash [RFC4757] of the entire PAC message, with the Signature fields of both PAC_SIGNATURE_DATA structures set to zero.
The key used to protect the ciphertext part of the response is used.
The checksum type corresponds to the key unless the key is DES, in which case the KERB_CHECKSUM_HMAC_MD5 key is used.
The resulting hash value is then placed in the Signature field of the server's PAC_SIGNATURE_DATA structure.
KDC Signature (SignatureType = 0x00000007)
https://msdn.microsoft.com/en-us/library/dd357117.aspx
The KDC will use KDC (krbtgt) key [RFC4120], so that other KDCs can verify this signature on receiving a PAC.
The KDC signature is a keyed hash [RFC4757] of the Server Signature field in the PAC message.
The cryptographic system that is used to calculate the checksum depends on which system the KDC supports, as defined below:
- Supports RC4-HMAC --> KERB_CHECKSUM_HMAC_MD5
- Does not support RC4-HMAC and supports AES256 --> HMAC_SHA1_96_AES256
- Does not support RC4-HMAC or AES256-CTS-HMAC-SHA1-96, and supports AES128-CTS-HMAC-SHA1-96 --> HMAC_SHA1_96_AES128
- Does not support RC4-HMAC, AES128-CTS-HMAC-SHA1-96 or AES256-CTS-HMAC-SHA1-96 --> None. The checksum operation will fail.
*/
// 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
}

View File

@@ -0,0 +1,90 @@
package 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<<p) != 0 {
return true
}
return false
}
// SECPKGSupplementalCred implements https://msdn.microsoft.com/en-us/library/cc237956.aspx
// The SECPKG_SUPPLEMENTAL_CRED structure defines the name of the security package that requires
// supplemental credentials and the credential buffer for that package.
// The SECPKG_SUPPLEMENTAL_CRED structure is marshaled by RPC.
type SECPKGSupplementalCred struct {
PackageName mstypes.RPCUnicodeString
CredentialSize uint32
Credentials []uint8 `ndr:"pointer,conformant"` // Is a ptr. Size is the value of CredentialSize
}
// Unmarshal converts the bytes provided into a SECPKGSupplementalCred.
func (c *SECPKGSupplementalCred) Unmarshal(b []byte) (err error) {
dec := ndr.NewDecoder(bytes.NewReader(b))
err = dec.Decode(c)
if err != nil {
err = fmt.Errorf("error unmarshaling SECPKGSupplementalCred: %v", err)
}
return
}

73
vendor/github.com/hashicorp/gokrb5/pac/upn_dns_info.go generated vendored Normal file
View File

@@ -0,0 +1,73 @@
package pac
import (
"bytes"
"gopkg.in/jcmturner/rpc.v1/mstypes"
)
// UPNDNSInfo implements https://msdn.microsoft.com/en-us/library/dd240468.aspx
type UPNDNSInfo struct {
UPNLength uint16 // An unsigned 16-bit integer in little-endian format that specifies the length, in bytes, of the UPN field.
UPNOffset uint16 // An unsigned 16-bit integer in little-endian format that contains the offset to the beginning of the buffer, in bytes, from the beginning of the UPN_DNS_INFO structure.
DNSDomainNameLength uint16
DNSDomainNameOffset uint16
Flags uint32
UPN string
DNSDomain string
}
const (
upnNoUPNAttr = 31 // The user account object does not have the userPrincipalName attribute ([MS-ADA3] section 2.349) set. A UPN constructed by concatenating the user name with the DNS domain name of the account domain is provided.
)
// Unmarshal bytes into the UPN_DNSInfo struct
func (k *UPNDNSInfo) Unmarshal(b []byte) (err error) {
//The UPN_DNS_INFO structure is a simple structure that is not NDR-encoded.
r := mstypes.NewReader(bytes.NewReader(b))
k.UPNLength, err = r.Uint16()
if err != nil {
return
}
k.UPNOffset, err = r.Uint16()
if err != nil {
return
}
k.DNSDomainNameLength, err = r.Uint16()
if err != nil {
return
}
k.DNSDomainNameOffset, err = r.Uint16()
if err != nil {
return
}
k.Flags, err = r.Uint32()
if err != nil {
return
}
ub := mstypes.NewReader(bytes.NewReader(b[k.UPNOffset : k.UPNOffset+k.UPNLength]))
db := mstypes.NewReader(bytes.NewReader(b[k.DNSDomainNameOffset : k.DNSDomainNameOffset+k.DNSDomainNameLength]))
u := make([]rune, k.UPNLength/2, k.UPNLength/2)
for i := 0; i < len(u); i++ {
var r uint16
r, err = ub.Uint16()
if err != nil {
return
}
u[i] = rune(r)
}
k.UPN = string(u)
d := make([]rune, k.DNSDomainNameLength/2, k.DNSDomainNameLength/2)
for i := 0; i < len(d); i++ {
var r uint16
r, err = db.Uint16()
if err != nil {
return
}
d[i] = rune(r)
}
k.DNSDomain = string(d)
return
}

View File

@@ -0,0 +1,62 @@
package service
import (
"time"
"github.com/hashicorp/gokrb5/credentials"
"github.com/hashicorp/gokrb5/iana/errorcode"
"github.com/hashicorp/gokrb5/messages"
)
// VerifyAPREQ verifies an AP_REQ sent to the service. Returns a boolean for if the AP_REQ is valid and the client's principal name and realm.
func VerifyAPREQ(APReq messages.APReq, s *Settings) (bool, *credentials.Credentials, error) {
var creds *credentials.Credentials
ok, err := APReq.Verify(s.Keytab, s.MaxClockSkew(), s.ClientAddress(), s.KeytabPrincipal())
if err != nil || !ok {
return false, creds, err
}
if s.RequireHostAddr() && len(APReq.Ticket.DecryptedEncPart.CAddr) < 1 {
return false, creds,
messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "ticket does not contain HostAddress values required")
}
// Check for replay
rc := GetReplayCache(s.MaxClockSkew())
if rc.IsReplay(APReq.Ticket.SName, APReq.Authenticator) {
return false, creds,
messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_REPEAT, "replay detected")
}
c := credentials.NewFromPrincipalName(APReq.Authenticator.CName, APReq.Authenticator.CRealm)
creds = c
creds.SetAuthTime(time.Now().UTC())
creds.SetAuthenticated(true)
creds.SetValidUntil(APReq.Ticket.DecryptedEncPart.EndTime)
//PAC decoding
if !s.disablePACDecoding {
isPAC, pac, err := APReq.Ticket.GetPACType(s.Keytab, s.KeytabPrincipal(), s.Logger())
if isPAC && err != nil {
return false, creds, err
}
if isPAC {
// There is a valid PAC. Adding attributes to creds
creds.SetADCredentials(credentials.ADCredentials{
GroupMembershipSIDs: pac.KerbValidationInfo.GetGroupMembershipSIDs(),
LogOnTime: pac.KerbValidationInfo.LogOnTime.Time(),
LogOffTime: pac.KerbValidationInfo.LogOffTime.Time(),
PasswordLastSet: pac.KerbValidationInfo.PasswordLastSet.Time(),
EffectiveName: pac.KerbValidationInfo.EffectiveName.Value,
FullName: pac.KerbValidationInfo.FullName.Value,
UserID: int(pac.KerbValidationInfo.UserID),
PrimaryGroupID: int(pac.KerbValidationInfo.PrimaryGroupID),
LogonServer: pac.KerbValidationInfo.LogonServer.Value,
LogonDomainName: pac.KerbValidationInfo.LogonDomainName.Value,
LogonDomainID: pac.KerbValidationInfo.LogonDomainID.String(),
})
}
}
return true, creds, nil
}

View File

@@ -0,0 +1,118 @@
package service
import (
"encoding/base64"
"fmt"
"strings"
"time"
"github.com/hashicorp/gokrb5/client"
"github.com/hashicorp/gokrb5/config"
"github.com/hashicorp/gokrb5/credentials"
goidentity "gopkg.in/jcmturner/goidentity.v3"
)
// NewKRB5BasicAuthenticator creates a new NewKRB5BasicAuthenticator
func NewKRB5BasicAuthenticator(headerVal string, krb5conf *config.Config, serviceSettings *Settings, clientSettings *client.Settings) KRB5BasicAuthenticator {
return KRB5BasicAuthenticator{
BasicHeaderValue: headerVal,
clientConfig: krb5conf,
serviceSettings: serviceSettings,
clientSettings: clientSettings,
}
}
// KRB5BasicAuthenticator implements gopkg.in/jcmturner/goidentity.v3.Authenticator interface.
// It takes username and password so can be used for basic authentication.
type KRB5BasicAuthenticator struct {
BasicHeaderValue string
serviceSettings *Settings
clientSettings *client.Settings
clientConfig *config.Config
realm string
username string
password string
}
// Authenticate and return the identity. The boolean indicates if the authentication was successful.
func (a KRB5BasicAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err error) {
a.realm, a.username, a.password, err = parseBasicHeaderValue(a.BasicHeaderValue)
if err != nil {
err = fmt.Errorf("could not parse basic authentication header: %v", err)
return
}
cl := client.NewClientWithPassword(a.username, a.realm, a.password, a.clientConfig)
err = cl.Login()
if err != nil {
// Username and/or password could be wrong
err = fmt.Errorf("error with user credentials during login: %v", err)
return
}
tkt, _, err := cl.GetServiceTicket(a.serviceSettings.SName())
if err != nil {
err = fmt.Errorf("could not get service ticket: %v", err)
return
}
err = tkt.DecryptEncPart(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal())
if err != nil {
err = fmt.Errorf("could not decrypt service ticket: %v", err)
return
}
cl.Credentials.SetAuthTime(time.Now().UTC())
cl.Credentials.SetAuthenticated(true)
isPAC, pac, err := tkt.GetPACType(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal(), a.serviceSettings.Logger())
if isPAC && err != nil {
err = fmt.Errorf("error processing PAC: %v", err)
return
}
if isPAC {
// There is a valid PAC. Adding attributes to creds
cl.Credentials.SetADCredentials(credentials.ADCredentials{
GroupMembershipSIDs: pac.KerbValidationInfo.GetGroupMembershipSIDs(),
LogOnTime: pac.KerbValidationInfo.LogOnTime.Time(),
LogOffTime: pac.KerbValidationInfo.LogOffTime.Time(),
PasswordLastSet: pac.KerbValidationInfo.PasswordLastSet.Time(),
EffectiveName: pac.KerbValidationInfo.EffectiveName.Value,
FullName: pac.KerbValidationInfo.FullName.Value,
UserID: int(pac.KerbValidationInfo.UserID),
PrimaryGroupID: int(pac.KerbValidationInfo.PrimaryGroupID),
LogonServer: pac.KerbValidationInfo.LogonServer.Value,
LogonDomainName: pac.KerbValidationInfo.LogonDomainName.Value,
LogonDomainID: pac.KerbValidationInfo.LogonDomainID.String(),
})
}
ok = true
i = cl.Credentials
return
}
// Mechanism returns the authentication mechanism.
func (a KRB5BasicAuthenticator) Mechanism() string {
return "Kerberos Basic"
}
func parseBasicHeaderValue(s string) (domain, username, password string, err error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return
}
v := string(b)
vc := strings.SplitN(v, ":", 2)
password = vc[1]
// Domain and username can be specified in 2 formats:
// <Username> - no domain specified
// <Domain>\<Username>
// <Username>@<Domain>
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
}

148
vendor/github.com/hashicorp/gokrb5/service/cache.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
// Package service provides server side integrations for Kerberos authentication.
package service
import (
"github.com/hashicorp/gokrb5/types"
"sync"
"time"
)
/*The server MUST utilize a replay cache to remember any authenticator
presented within the allowable clock skew.
The replay cache will store at least the server name, along with the
client name, time, and microsecond fields from the recently-seen
authenticators, and if a matching tuple is found, the
KRB_AP_ERR_REPEAT error is returned. Note that the rejection here is
restricted to authenticators from the same principal to the same
server. Other client principals communicating with the same server
principal should not have their authenticators rejected if the time
and microsecond fields happen to match some other client's
authenticator.
If a server loses track of authenticators presented within the
allowable clock skew, it MUST reject all requests until the clock
skew interval has passed, providing assurance that any lost or
replayed authenticators will fall outside the allowable clock skew
and can no longer be successfully replayed. If this were not done,
an attacker could subvert the authentication by recording the ticket
and authenticator sent over the network to a server and replaying
them following an event that caused the server to lose track of
recently seen authenticators.*/
// 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
}

136
vendor/github.com/hashicorp/gokrb5/service/settings.go generated vendored Normal file
View File

@@ -0,0 +1,136 @@
package service
import (
"log"
"time"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/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
}

293
vendor/github.com/hashicorp/gokrb5/spnego/http.go generated vendored Normal file
View File

@@ -0,0 +1,293 @@
package spnego
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"github.com/hashicorp/gokrb5/client"
"github.com/hashicorp/gokrb5/gssapi"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/service"
"github.com/hashicorp/gokrb5/types"
"gopkg.in/jcmturner/goidentity.v3"
)
// 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/hashicorp/gokrb5/CTXKeyAuthenticated"
// CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v2/Identity object.
CTXKeyCredentials ctxKey = "github.com/hashicorp/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)
}

239
vendor/github.com/hashicorp/gokrb5/spnego/krb5Token.go generated vendored Normal file
View File

@@ -0,0 +1,239 @@
package spnego
import (
"context"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/client"
"github.com/hashicorp/gokrb5/credentials"
"github.com/hashicorp/gokrb5/gssapi"
"github.com/hashicorp/gokrb5/iana/chksumtype"
"github.com/hashicorp/gokrb5/iana/msgtype"
"github.com/hashicorp/gokrb5/krberror"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/service"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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)
}
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
}
/*
The authenticator checksum field SHALL have the following format:
Octet Name Description
-----------------------------------------------------------------
0..3 Lgth Number of octets in Bnd field; Represented
in little-endian order; Currently contains
hex value 10 00 00 00 (16).
4..19 Bnd Channel binding information, as described in
section 4.1.1.2.
20..23 Flags Four-octet context-establishment flags in
little-endian order as described in section
4.1.1.1.
24..25 DlgOpt The delegation option identifier (=1) in
little-endian order [optional]. This field
and the next two fields are present if and
only if GSS_C_DELEG_FLAG is set as described
in section 4.1.1.1.
26..27 Dlgth The length of the Deleg field in little-endian order [optional].
28..(n-1) Deleg A KRB_CRED message (n = Dlgth + 28) [optional].
n..last Exts Extensions [optional].
*/

View File

@@ -0,0 +1,338 @@
package spnego
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/client"
"github.com/hashicorp/gokrb5/gssapi"
"github.com/hashicorp/gokrb5/messages"
"github.com/hashicorp/gokrb5/service"
"github.com/hashicorp/gokrb5/types"
"github.com/jcmturner/gofork/encoding/asn1"
)
/*
https://msdn.microsoft.com/en-us/library/ms995330.aspx
NegotiationToken ::= CHOICE {
negTokenInit [0] NegTokenInit, This is the Negotiation token sent from the client to the server.
negTokenResp [1] NegTokenResp
}
NegTokenInit ::= SEQUENCE {
mechTypes [0] MechTypeList,
reqFlags [1] ContextFlags OPTIONAL,
-- inherited from RFC 2478 for backward compatibility,
-- RECOMMENDED to be left out
mechToken [2] OCTET STRING OPTIONAL,
mechListMIC [3] OCTET STRING OPTIONAL,
...
}
NegTokenResp ::= SEQUENCE {
negState [0] ENUMERATED {
accept-completed (0),
accept-incomplete (1),
reject (2),
request-mic (3)
} OPTIONAL,
-- REQUIRED in the first reply from the target
supportedMech [1] MechType OPTIONAL,
-- present only in the first reply from the target
responseToken [2] OCTET STRING OPTIONAL,
mechListMIC [3] OCTET STRING OPTIONAL,
...
}
*/
// 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"}
}
}
// RFC4178 states that the initial negotiation message can optionally contain the initial mechanism token for the preferred mechanism of the client.
if !mt.OID.Equal(n.MechTypes[0]) {
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "OID of MechToken does not match the first in the MechTypeList"}
}
// 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
}

203
vendor/github.com/hashicorp/gokrb5/spnego/spnego.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication.
package spnego
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/client"
"github.com/hashicorp/gokrb5/gssapi"
"github.com/hashicorp/gokrb5/keytab"
"github.com/hashicorp/gokrb5/service"
"github.com/jcmturner/gofork/encoding/asn1"
)
// 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.Login()
}
// 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
}

View File

@@ -0,0 +1,100 @@
// Package types provides Kerberos 5 data types.
package types
import (
"crypto/rand"
"fmt"
"math"
"math/big"
"time"
"github.com/hashicorp/gokrb5/asn1tools"
"github.com/hashicorp/gokrb5/iana"
"github.com/hashicorp/gokrb5/iana/asnAppTag"
"github.com/jcmturner/gofork/encoding/asn1"
)
/*Authenticator ::= [APPLICATION 2] SEQUENCE {
authenticator-vno [0] INTEGER (5),
crealm [1] Realm,
cname [2] PrincipalName,
cksum [3] Checksum OPTIONAL,
cusec [4] Microseconds,
ctime [5] KerberosTime,
subkey [6] EncryptionKey OPTIONAL,
seq-number [7] UInt32 OPTIONAL,
authorization-data [8] AuthorizationData OPTIONAL
}
cksum
This field contains a checksum of the application data that
accompanies the KRB_AP_REQ, computed using a key usage value of 10
in normal application exchanges, or 6 when used in the TGS-REQ
PA-TGS-REQ AP-DATA field.
*/
// 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
}

View File

@@ -0,0 +1,123 @@
package types
import (
"github.com/jcmturner/gofork/encoding/asn1"
)
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.2.6
/*
AuthorizationData
-- NOTE: AuthorizationData is always used as an OPTIONAL field and
-- should not be empty.
AuthorizationData ::= SEQUENCE OF SEQUENCE {
ad-type [0] Int32,
ad-data [1] OCTET STRING
}
ad-data
This field contains authorization data to be interpreted according
to the value of the corresponding ad-type field.
ad-type
This field specifies the format for the ad-data subfield. All
negative values are reserved for local use. Non-negative values
are reserved for registered use.
Each sequence of type and data is referred to as an authorization
element. Elements MAY be application specific; however, there is a
common set of recursive elements that should be understood by all
implementations. These elements contain other elements embedded
within them, and the interpretation of the encapsulating element
determines which of the embedded elements must be interpreted, and
which may be ignored.
These common authorization data elements are recursively defined,
meaning that the ad-data for these types will itself contain a
sequence of authorization data whose interpretation is affected by
the encapsulating element. Depending on the meaning of the
encapsulating element, the encapsulated elements may be ignored,
might be interpreted as issued directly by the KDC, or might be
stored in a separate plaintext part of the ticket. The types of the
encapsulating elements are specified as part of the Kerberos
specification because the behavior based on these values should be
understood across implementations, whereas other elements need only
be understood by the applications that they affect.
Authorization data elements are considered critical if present in a
ticket or authenticator. If an unknown authorization data element
type is received by a server either in an AP-REQ or in a ticket
contained in an AP-REQ, then, unless it is encapsulated in a known
authorization data element amending the criticality of the elements
it contains, authentication MUST fail. Authorization data is
intended to restrict the use of a ticket. If the service cannot
determine whether the restriction applies to that service, then a
security weakness may result if the ticket can be used for that
service. Authorization elements that are optional can be enclosed in
an AD-IF-RELEVANT element.
In the definitions that follow, the value of the ad-type for the
element will be specified as the least significant part of the
subsection number, and the value of the ad-data will be as shown in
the ASN.1 structure that follows the subsection heading.
Contents of ad-data ad-type
DER encoding of AD-IF-RELEVANT 1
DER encoding of AD-KDCIssued 4
DER encoding of AD-AND-OR 5
DER encoding of AD-MANDATORY-FOR-KDC 8
*/
// 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
}

View File

@@ -0,0 +1,55 @@
package 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
}

204
vendor/github.com/hashicorp/gokrb5/types/HostAddress.go generated vendored Normal file
View File

@@ -0,0 +1,204 @@
package types
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.2.5
import (
"bytes"
"fmt"
"net"
"github.com/hashicorp/gokrb5/iana/addrtype"
"github.com/jcmturner/gofork/encoding/asn1"
)
/*
HostAddress and HostAddresses
HostAddress ::= SEQUENCE {
addr-type [0] Int32,
address [1] OCTET STRING
}
-- NOTE: HostAddresses is always used as an OPTIONAL field and
-- should not be empty.
HostAddresses -- NOTE: subtly different from rfc1510,
-- but has a value mapping and encodes the same
::= SEQUENCE OF HostAddress
The host address encodings consist of two fields:
addr-type
This field specifies the type of address that follows. Pre-
defined values for this field are specified in Section 7.5.3.
address
This field encodes a single address of type addr-type.
*/
// 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 <hostname>:<port>
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
}

View File

@@ -0,0 +1,124 @@
package types
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.2.8
import (
"github.com/jcmturner/gofork/encoding/asn1"
)
/*
KerberosFlags
For several message types, a specific constrained bit string type,
KerberosFlags, is used.
KerberosFlags ::= BIT STRING (SIZE (32..MAX))
-- minimum number of bits shall be sent,
-- but no fewer than 32
Compatibility note: The following paragraphs describe a change from
the RFC 1510 description of bit strings that would result in
incompatility in the case of an implementation that strictly
conformed to ASN.1 DER and RFC 1510.
ASN.1 bit strings have multiple uses. The simplest use of a bit
string is to contain a vector of bits, with no particular meaning
attached to individual bits. This vector of bits is not necessarily
a multiple of eight bits long. The use in Kerberos of a bit string
as a compact boolean vector wherein each element has a distinct
meaning poses some problems. The natural notation for a compact
boolean vector is the ASN.1 "NamedBit" notation, and the DER require
that encodings of a bit string using "NamedBit" notation exclude any
trailing zero bits. This truncation is easy to neglect, especially
given C language implementations that naturally choose to store
boolean vectors as 32-bit integers.
For example, if the notation for KDCOptions were to include the
"NamedBit" notation, as in RFC 1510, and a KDCOptions value to be
encoded had only the "forwardable" (bit number one) bit set, the DER
encoding MUST include only two bits: the first reserved bit
("reserved", bit number zero, value zero) and the one-valued bit (bit
number one) for "forwardable".
Most existing implementations of Kerberos unconditionally send 32
bits on the wire when encoding bit strings used as boolean vectors.
This behavior violates the ASN.1 syntax used for flag values in RFC
1510, but it occurs on such a widely installed base that the protocol
description is being modified to accommodate it.
Consequently, this document removes the "NamedBit" notations for
individual bits, relegating them to comments. The size constraint on
the KerberosFlags type requires that at least 32 bits be encoded at
all times, though a lenient implementation MAY choose to accept fewer
than 32 bits and to treat the missing bits as set to zero.
Currently, no uses of KerberosFlags specify more than 32 bits' worth
of flags, although future revisions of this document may do so. When
more than 32 bits are to be transmitted in a KerberosFlags value,
future revisions to this document will likely specify that the
smallest number of bits needed to encode the highest-numbered one-
valued bit should be sent. This is somewhat similar to the DER
encoding of a bit string that is declared with the "NamedBit"
notation.
*/
// 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<<p) != 0 {
return true
}
return false
}

155
vendor/github.com/hashicorp/gokrb5/types/PAData.go generated vendored Normal file
View File

@@ -0,0 +1,155 @@
package types
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.2.7
import (
"fmt"
"time"
"github.com/hashicorp/gokrb5/iana/patype"
"github.com/jcmturner/gofork/encoding/asn1"
)
// PAData implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7
type PAData struct {
PADataType int32 `asn1:"explicit,tag:1"`
PADataValue []byte `asn1:"explicit,tag:2"`
}
// PADataSequence implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7
type PADataSequence []PAData
// MethodData implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.9.1
type MethodData []PAData
// PAEncTimestamp implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.2
type PAEncTimestamp EncryptedData
// PAEncTSEnc implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.2
type PAEncTSEnc struct {
PATimestamp time.Time `asn1:"generalized,explicit,tag:0"`
PAUSec int `asn1:"explicit,optional,tag:1"`
}
// Contains tests if a PADataSequence contains PA Data of a certain type.
func (pas *PADataSequence) Contains(patype int32) bool {
for _, pa := range *pas {
if pa.PADataType == patype {
return true
}
}
return false
}
// GetPAEncTSEncAsnMarshalled returns the bytes of a PAEncTSEnc.
func GetPAEncTSEncAsnMarshalled() ([]byte, error) {
t := time.Now().UTC()
p := PAEncTSEnc{
PATimestamp: t,
PAUSec: int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
}
b, err := asn1.Marshal(p)
if err != nil {
return b, fmt.Errorf("error mashaling PAEncTSEnc: %v", err)
}
return b, nil
}
// ETypeInfoEntry implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.4
type ETypeInfoEntry struct {
EType int32 `asn1:"explicit,tag:0"`
Salt []byte `asn1:"explicit,optional,tag:1"`
}
// ETypeInfo implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.4
type ETypeInfo []ETypeInfoEntry
// ETypeInfo2Entry implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.5
type ETypeInfo2Entry struct {
EType int32 `asn1:"explicit,tag:0"`
Salt string `asn1:"explicit,optional,generalstring,tag:1"`
S2KParams []byte `asn1:"explicit,optional,tag:2"`
}
// ETypeInfo2 implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.5
type ETypeInfo2 []ETypeInfo2Entry
// PAReqEncPARep PA Data Type
type PAReqEncPARep struct {
ChksumType int32 `asn1:"explicit,tag:0"`
Chksum []byte `asn1:"explicit,tag:1"`
}
// Unmarshal bytes into the PAData
func (pa *PAData) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, pa)
return err
}
// Unmarshal bytes into the PADataSequence
func (pas *PADataSequence) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, pas)
return err
}
// Unmarshal bytes into the PAReqEncPARep
func (pa *PAReqEncPARep) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, pa)
return err
}
// Unmarshal bytes into the PAEncTimestamp
func (pa *PAEncTimestamp) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, pa)
return err
}
// Unmarshal bytes into the PAEncTSEnc
func (pa *PAEncTSEnc) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, pa)
return err
}
// Unmarshal bytes into the ETypeInfo
func (a *ETypeInfo) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, a)
return err
}
// Unmarshal bytes into the ETypeInfoEntry
func (a *ETypeInfoEntry) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, a)
return err
}
// Unmarshal bytes into the ETypeInfo2
func (a *ETypeInfo2) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, a)
return err
}
// Unmarshal bytes into the ETypeInfo2Entry
func (a *ETypeInfo2Entry) Unmarshal(b []byte) error {
_, err := asn1.Unmarshal(b, a)
return err
}
// GetETypeInfo returns an ETypeInfo from the PAData.
func (pa *PAData) GetETypeInfo() (d ETypeInfo, err error) {
if pa.PADataType != patype.PA_ETYPE_INFO {
err = fmt.Errorf("PAData does not contain PA EType Info data. TypeID Expected: %v; Actual: %v", patype.PA_ETYPE_INFO, pa.PADataType)
return
}
_, err = asn1.Unmarshal(pa.PADataValue, &d)
return
}
// GetETypeInfo2 returns an ETypeInfo2 from the PAData.
func (pa *PAData) GetETypeInfo2() (d ETypeInfo2, err error) {
if pa.PADataType != patype.PA_ETYPE_INFO2 {
err = fmt.Errorf("PAData does not contain PA EType Info 2 data. TypeID Expected: %v; Actual: %v", patype.PA_ETYPE_INFO2, pa.PADataType)
return
}
_, err = asn1.Unmarshal(pa.PADataValue, &d)
return
}

View File

@@ -0,0 +1,64 @@
package types
import (
"strings"
"github.com/hashicorp/gokrb5/iana/nametype"
)
// Reference: https://www.ietf.org/rfc/rfc4120.txt
// Section: 5.2.2
// PrincipalName implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.2
type PrincipalName struct {
NameType int32 `asn1:"explicit,tag:0"`
NameString []string `asn1:"generalstring,explicit,tag:1"`
}
// NewPrincipalName creates a new PrincipalName from the name type int32 and name string provided.
func NewPrincipalName(ntype int32, spn string) PrincipalName {
return PrincipalName{
NameType: ntype,
NameString: strings.Split(spn, "/"),
}
}
// GetSalt returns a salt derived from the PrincipalName.
func (pn PrincipalName) GetSalt(realm string) string {
var sb []byte
sb = append(sb, realm...)
for _, n := range pn.NameString {
sb = append(sb, n...)
}
return string(sb)
}
// Equal tests if the PrincipalName is equal to the one provided.
func (pn PrincipalName) Equal(n PrincipalName) bool {
//https://tools.ietf.org/html/rfc4120#section-6.2 - the name type is not significant when checking for equivalence
for i, s := range pn.NameString {
if n.NameString[i] != s {
return false
}
}
return true
}
// PrincipalNameString returns the PrincipalName in string form.
func (pn PrincipalName) PrincipalNameString() string {
return strings.Join(pn.NameString, "/")
}
// ParseSPNString will parse a string in the format <service>/<name>@<realm>
// 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 "@<realm>" 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
}

18
vendor/github.com/hashicorp/gokrb5/types/TypedData.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
package 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
}

View File

@@ -0,0 +1,10 @@
/vault-plugin-auth-kerberos
/bin
/pkg
# JetBrains IDE
.idea*
# Any keytabs generated by tests
*.base64
*.keytab

View File

@@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

Some files were not shown because too many files have changed in this diff Show More