@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 188 lines 4.77 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddyauth
16
17 import (
18 "crypto/rand"
19 "crypto/subtle"
20 "encoding/base64"
21 "fmt"
22 "strconv"
23 "strings"
24
25 "golang.org/x/crypto/argon2"
26
27 "github.com/caddyserver/caddy/v2"
28 )
29
30 func init() {
31 caddy.RegisterModule(Argon2idHash{})
32 }
33
34 const (
35 argon2idName = "argon2id"
36 defaultArgon2idTime = 1
37 defaultArgon2idMemory = 46 * 1024
38 defaultArgon2idThreads = 1
39 defaultArgon2idKeylen = 32
40 defaultSaltLength = 16
41 )
42
43 // Argon2idHash implements the Argon2id password hashing.
44 type Argon2idHash struct {
45 salt []byte
46 time uint32
47 memory uint32
48 threads uint8
49 keyLen uint32
50 }
51
52 // CaddyModule returns the Caddy module information.
53 func (Argon2idHash) CaddyModule() caddy.ModuleInfo {
54 return caddy.ModuleInfo{
55 ID: "http.authentication.hashes.argon2id",
56 New: func() caddy.Module { return new(Argon2idHash) },
57 }
58 }
59
60 // Compare checks if the plaintext password matches the given Argon2id hash.
61 func (Argon2idHash) Compare(hashed, plaintext []byte) (bool, error) {
62 argHash, storedKey, err := DecodeHash(hashed)
63 if err != nil {
64 return false, err
65 }
66
67 computedKey := argon2.IDKey(
68 plaintext,
69 argHash.salt,
70 argHash.time,
71 argHash.memory,
72 argHash.threads,
73 argHash.keyLen,
74 )
75
76 return subtle.ConstantTimeCompare(storedKey, computedKey) == 1, nil
77 }
78
79 // Hash generates an Argon2id hash of the given plaintext using the configured parameters and salt.
80 func (b Argon2idHash) Hash(plaintext []byte) ([]byte, error) {
81 if b.salt == nil {
82 s, err := generateSalt(defaultSaltLength)
83 if err != nil {
84 return nil, err
85 }
86 b.salt = s
87 }
88
89 key := argon2.IDKey(
90 plaintext,
91 b.salt,
92 b.time,
93 b.memory,
94 b.threads,
95 b.keyLen,
96 )
97
98 hash := fmt.Sprintf(
99 "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
100 argon2.Version,
101 b.memory,
102 b.time,
103 b.threads,
104 base64.RawStdEncoding.EncodeToString(b.salt),
105 base64.RawStdEncoding.EncodeToString(key),
106 )
107
108 return []byte(hash), nil
109 }
110
111 // DecodeHash parses an Argon2id PHC string into an Argon2idHash struct and returns the struct along with the derived key.
112 func DecodeHash(hash []byte) (*Argon2idHash, []byte, error) {
113 parts := strings.Split(string(hash), "$")
114 if len(parts) != 6 {
115 return nil, nil, fmt.Errorf("invalid hash format")
116 }
117
118 if parts[1] != argon2idName {
119 return nil, nil, fmt.Errorf("unsupported variant: %s", parts[1])
120 }
121
122 version, err := strconv.Atoi(strings.TrimPrefix(parts[2], "v="))
123 if err != nil {
124 return nil, nil, fmt.Errorf("invalid version: %w", err)
125 }
126 if version != argon2.Version {
127 return nil, nil, fmt.Errorf("incompatible version: %d", version)
128 }
129
130 params := strings.Split(parts[3], ",")
131 if len(params) != 3 {
132 return nil, nil, fmt.Errorf("invalid parameters")
133 }
134
135 mem, err := strconv.ParseUint(strings.TrimPrefix(params[0], "m="), 10, 32)
136 if err != nil {
137 return nil, nil, fmt.Errorf("invalid memory parameter: %w", err)
138 }
139
140 iter, err := strconv.ParseUint(strings.TrimPrefix(params[1], "t="), 10, 32)
141 if err != nil {
142 return nil, nil, fmt.Errorf("invalid iterations parameter: %w", err)
143 }
144
145 threads, err := strconv.ParseUint(strings.TrimPrefix(params[2], "p="), 10, 8)
146 if err != nil {
147 return nil, nil, fmt.Errorf("invalid parallelism parameter: %w", err)
148 }
149
150 salt, err := base64.RawStdEncoding.Strict().DecodeString(parts[4])
151 if err != nil {
152 return nil, nil, fmt.Errorf("decode salt: %w", err)
153 }
154
155 key, err := base64.RawStdEncoding.Strict().DecodeString(parts[5])
156 if err != nil {
157 return nil, nil, fmt.Errorf("decode key: %w", err)
158 }
159
160 return &Argon2idHash{
161 salt: salt,
162 time: uint32(iter),
163 memory: uint32(mem),
164 threads: uint8(threads),
165 keyLen: uint32(len(key)),
166 }, key, nil
167 }
168
169 // FakeHash returns a constant fake hash for timing attacks mitigation.
170 func (Argon2idHash) FakeHash() []byte {
171 // hashed with the following command:
172 // caddy hash-password --plaintext "antitiming" --algorithm "argon2id"
173 return []byte("$argon2id$v=19$m=47104,t=1,p=1$P2nzckEdTZ3bxCiBCkRTyA$xQL3Z32eo5jKl7u5tcIsnEKObYiyNZQQf5/4sAau6Pg")
174 }
175
176 // Interface guards
177 var (
178 _ Comparer = (*Argon2idHash)(nil)
179 _ Hasher = (*Argon2idHash)(nil)
180 )
181
182 func generateSalt(length int) ([]byte, error) {
183 salt := make([]byte, length)
184 if _, err := rand.Read(salt); err != nil {
185 return nil, fmt.Errorf("failed to generate salt: %w", err)
186 }
187 return salt, nil
188 }