github.com/greenpau/go-authcrunch@v1.1.4/pkg/kms/key_test.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 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 kms 16 17 import ( 18 "fmt" 19 "github.com/google/go-cmp/cmp" 20 "github.com/greenpau/go-authcrunch/internal/tests" 21 "github.com/greenpau/go-authcrunch/pkg/errors" 22 "github.com/greenpau/go-authcrunch/pkg/requests" 23 "github.com/greenpau/go-authcrunch/pkg/user" 24 "os" 25 "strings" 26 "testing" 27 "time" 28 ) 29 30 func newTestUser() *user.User { 31 cfg := `{ 32 "exp": ` + fmt.Sprintf("%d", time.Now().Add(10*time.Minute).Unix()) + `, 33 "iat": ` + fmt.Sprintf("%d", time.Now().Add(10*time.Minute*-1).Unix()) + `, 34 "nbf": ` + fmt.Sprintf("%d", time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix()) + `, 35 "name": "Smith, John", 36 "email": "smithj@outlook.com", 37 "origin": "localhost", 38 "sub": "smithj@outlook.com", 39 "roles": "anonymous guest" 40 }` 41 usr, err := user.NewUser(cfg) 42 if err != nil { 43 panic(err) 44 } 45 return usr 46 } 47 48 func TestGetKeysFromConfig(t *testing.T) { 49 var testcases = []struct { 50 name string 51 config string 52 env map[string]string 53 want map[string]interface{} 54 log bool 55 // keyPair indicates which keys are being used for sign/verification. 56 keyPair []int 57 shouldErr bool 58 err error 59 }{ 60 { 61 name: "default shared key in default context", 62 config: ` 63 crypto key token name "foobar token" 64 crypto key sign-verify foobar 65 `, 66 keyPair: []int{0, 0}, 67 want: map[string]interface{}{ 68 "config_count": 1, 69 "key_count": 1, 70 "keys": []string{ 71 "0: sign 0: []uint8", 72 "0: verify 0: []uint8", 73 }, 74 }, 75 }, 76 { 77 name: "shared secret embedded in environment variable", 78 config: ` 79 crypto key cb315f43c868 sign-verify from env JWT_SHARED_SECRET 80 `, 81 env: map[string]string{ 82 "JWT_TOKEN_LIFETIME": "3600", 83 "JWT_SHARED_SECRET": "foobar", 84 }, 85 keyPair: []int{0, 0}, 86 want: map[string]interface{}{ 87 "config_count": 1, 88 "key_count": 1, 89 "keys": []string{ 90 "0: sign cb315f43c868: []uint8", 91 "0: verify cb315f43c868: []uint8", 92 }, 93 }, 94 }, 95 { 96 name: "rsa key embedded in environment variable", 97 config: ` 98 crypto key cb315f43c868 sign-verify from env JWT_SHARED_SECRET 99 `, 100 env: map[string]string{ 101 "JWT_TOKEN_LIFETIME": "3600", 102 "JWT_SHARED_SECRET": "file:./../../testdata/rskeys/test_2_pri.pem", 103 }, 104 keyPair: []int{0, 0}, 105 want: map[string]interface{}{ 106 "config_count": 1, 107 "key_count": 1, 108 "keys": []string{ 109 "0: sign cb315f43c868: *rsa.PrivateKey", 110 "0: verify cb315f43c868: *rsa.PublicKey", 111 }, 112 }, 113 }, 114 { 115 name: "bad rsa key embedded in environment variable", 116 // log: true, 117 config: ` 118 crypto key cb315f43c868 sign-verify from env JWT_SHARED_SECRET 119 `, 120 env: map[string]string{ 121 "JWT_SHARED_SECRET": "-----BEGIN PRIVATE", 122 }, 123 shouldErr: true, 124 err: errors.ErrNotPEMEncodedKey, 125 }, 126 { 127 name: "bad rsa key embedded in environment variable", 128 // log: true, 129 config: ` 130 crypto key cb315f43c868 sign-verify from env JWT_SHARED_SECRET 131 `, 132 env: map[string]string{ 133 "JWT_SHARED_SECRET": "-----BEGIN PRIVATE ---END PRIVATE", 134 }, 135 shouldErr: true, 136 err: errors.ErrNotPEMEncodedKey, 137 }, 138 { 139 name: "load private and public rsa keys from file path", 140 config: ` 141 crypto key k9738a405e99 sign from file ./../../testdata/rskeys/test_2_pri.pem 142 crypto key k9738a405e99 verify from file ./../../testdata/rskeys/test_2_pub.pem 143 `, 144 keyPair: []int{0, 1}, 145 want: map[string]interface{}{ 146 "config_count": 2, 147 "key_count": 2, 148 "keys": []string{ 149 "0: sign k9738a405e99: *rsa.PrivateKey", 150 "1: verify k9738a405e99: *rsa.PublicKey", 151 }, 152 }, 153 }, 154 { 155 name: "load private and public rsa and ecdsa keys from file path", 156 config: ` 157 crypto key k9738a405e99 sign-verify from file ./../../testdata/misckeys/rsa_test_2_pri.pem 158 crypto key k9738a405e11 sign-verify from file ./../../testdata/misckeys/ecdsa_test_2_pri.pem 159 `, 160 keyPair: []int{0, 0}, 161 want: map[string]interface{}{ 162 "config_count": 2, 163 "key_count": 2, 164 "keys": []string{ 165 "0: sign k9738a405e99: *rsa.PrivateKey", 166 "0: verify k9738a405e99: *rsa.PublicKey", 167 "1: sign k9738a405e11: *ecdsa.PrivateKey", 168 "1: verify k9738a405e11: *ecdsa.PublicKey", 169 }, 170 }, 171 }, 172 { 173 name: "load private rsa key from file path for both sign and verify", 174 config: ` 175 crypto key k9738a405e99 sign-verify from file ./../../testdata/rskeys/test_1_pri.pem 176 `, 177 keyPair: []int{0, 0}, 178 want: map[string]interface{}{ 179 "config_count": 1, 180 "key_count": 1, 181 "keys": []string{ 182 "0: sign k9738a405e99: *rsa.PrivateKey", 183 "0: verify k9738a405e99: *rsa.PublicKey", 184 }, 185 }, 186 }, 187 { 188 name: "load private and public ecdsa keys from file path", 189 config: ` 190 crypto key k9738a405e99 sign from file ./../../testdata/ecdsakeys/test_2_pri.pem 191 crypto key k9738a405e99 verify from file ./../../testdata/ecdsakeys/test_2_pub.pem 192 `, 193 keyPair: []int{0, 1}, 194 want: map[string]interface{}{ 195 "config_count": 2, 196 "key_count": 2, 197 "keys": []string{ 198 "0: sign k9738a405e99: *ecdsa.PrivateKey", 199 "1: verify k9738a405e99: *ecdsa.PublicKey", 200 }, 201 }, 202 }, 203 { 204 name: "load private ecdsa key from file path for both sign and verify", 205 config: ` 206 crypto key k9738a405e99 sign-verify from file ./../../testdata/ecdsakeys/test_1_pri.pem 207 `, 208 keyPair: []int{0, 0}, 209 want: map[string]interface{}{ 210 "config_count": 1, 211 "key_count": 1, 212 "keys": []string{ 213 "0: sign k9738a405e99: *ecdsa.PrivateKey", 214 "0: verify k9738a405e99: *ecdsa.PublicKey", 215 }, 216 }, 217 }, 218 { 219 name: "load private ecdsa key from environment variable with file path for both sign and verify", 220 config: ` 221 crypto key cb315f43c868 sign-verify from env JWT_SECRET_FILE as file 222 `, 223 env: map[string]string{ 224 "JWT_SECRET_FILE": "./../../testdata/rskeys/test_1_pri.pem", 225 }, 226 keyPair: []int{0, 0}, 227 want: map[string]interface{}{ 228 "config_count": 1, 229 "key_count": 1, 230 "keys": []string{ 231 "0: sign cb315f43c868: *rsa.PrivateKey", 232 "0: verify cb315f43c868: *rsa.PublicKey", 233 }, 234 }, 235 }, 236 { 237 name: "load keys from rsa directory path", 238 config: ` 239 crypto key k9738a405e99 verify from directory ./../../testdata/rskeys 240 `, 241 want: map[string]interface{}{ 242 "config_count": 1, 243 "key_count": 4, 244 "keys": []string{ 245 "0: sign test_1_pri: *rsa.PrivateKey", 246 "0: verify test_1_pri: *rsa.PublicKey", 247 "1: sign test_2_pri: *rsa.PrivateKey", 248 "1: verify test_2_pri: *rsa.PublicKey", 249 "2: verify test_2_pub: *rsa.PublicKey", 250 "3: sign private: *rsa.PrivateKey", 251 "3: verify private: *rsa.PublicKey", 252 }, 253 }, 254 }, 255 256 { 257 name: "load keys from rsa directory path via env vars", 258 config: ` 259 crypto key cb315f43c868 sign-verify from env JWT_SECRET_DIR as directory 260 `, 261 env: map[string]string{ 262 "JWT_SECRET_DIR": "./../../testdata/rskeys", 263 }, 264 want: map[string]interface{}{ 265 "config_count": 1, 266 "key_count": 4, 267 "keys": []string{ 268 "0: sign test_1_pri: *rsa.PrivateKey", 269 "0: verify test_1_pri: *rsa.PublicKey", 270 "1: sign test_2_pri: *rsa.PrivateKey", 271 "1: verify test_2_pri: *rsa.PublicKey", 272 "2: verify test_2_pub: *rsa.PublicKey", 273 "3: sign private: *rsa.PrivateKey", 274 "3: verify private: *rsa.PublicKey", 275 }, 276 }, 277 }, 278 { 279 name: "load keys from rsa directory path", 280 config: ` 281 crypto key k9738a405e99 verify from directory ./../../testdata/nokeys/docs 282 `, 283 shouldErr: true, 284 err: errors.ErrWalkDir.WithArgs("no crypto keys found"), 285 }, 286 { 287 name: "load keys from rsa directory path", 288 config: ` 289 crypto key k9738a405e99 verify from directory ./../../testdata/nokeys/bad 290 `, 291 shouldErr: true, 292 err: errors.ErrWalkDir.WithArgs( 293 errors.ErrCryptoKeyConfigReadFile.WithArgs( 294 "../../testdata/nokeys/bad/bad_begin_only.key", 295 errors.ErrNotPEMEncodedKey, 296 ), 297 ), 298 }, 299 { 300 name: "load keys from ecdsa directory path", 301 config: ` 302 crypto key k9738a405e99 verify from directory ./../../testdata/ecdsakeys 303 `, 304 want: map[string]interface{}{ 305 "config_count": 1, 306 "key_count": 6, 307 "keys": []string{ 308 "0: sign test_1_pri: *ecdsa.PrivateKey", 309 "0: verify test_1_pri: *ecdsa.PublicKey", 310 "1: sign test_2_pri: *ecdsa.PrivateKey", 311 "1: verify test_2_pri: *ecdsa.PublicKey", 312 "2: verify test_2_pub: *ecdsa.PublicKey", 313 "3: sign test_3_pri: *ecdsa.PrivateKey", 314 "3: verify test_3_pri: *ecdsa.PublicKey", 315 "4: sign test_4_pri: *ecdsa.PrivateKey", 316 "4: verify test_4_pri: *ecdsa.PublicKey", 317 "5: sign private: *ecdsa.PrivateKey", 318 "5: verify private: *ecdsa.PublicKey", 319 }, 320 }, 321 }, 322 { 323 name: "private rsa key wrapped in ec header", 324 config: ` 325 crypto key k9738a405e99 sign-verify from file ./../../testdata/malformed/ec_header_rsa_pri.pem 326 `, 327 shouldErr: true, 328 err: errors.ErrCryptoKeyConfigReadFile.WithArgs( 329 "./../../testdata/malformed/ec_header_rsa_pri.pem", 330 `x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)`, 331 ), 332 }, 333 { 334 name: "private ec key wrapped in rsa header", 335 config: ` 336 crypto key k9738a405e99 sign-verify from file ./../../testdata/malformed/rsa_header_ec_pri.pem 337 `, 338 shouldErr: true, 339 err: errors.ErrCryptoKeyConfigReadFile.WithArgs( 340 "./../../testdata/malformed/rsa_header_ec_pri.pem", 341 `x509: failed to parse private key (use ParseECPrivateKey instead for this key format)`, 342 ), 343 }, 344 { 345 name: "public key passed as private", 346 config: ` 347 crypto key k9738a405e99 sign-verify from file ./../../testdata/malformed/rsa_pub_as_pri.pem 348 `, 349 shouldErr: true, 350 351 err: errors.ErrCryptoKeyConfigReadFile.WithArgs( 352 "./../../testdata/malformed/rsa_pub_as_pri.pem", 353 `asn1: structure error: tags don't match (2 vs {class:0 tag:16 length:19 isCompound:true}) `+ 354 `{optional:false explicit:false application:false private:false defaultValue:<nil> `+ 355 `tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} int @2`, 356 ), 357 }, 358 { 359 name: "private key passed as public", 360 config: ` 361 crypto key k9738a405e99 sign-verify from file ./../../testdata/malformed/rsa_pri_as_pub.pem 362 `, 363 shouldErr: true, 364 err: errors.ErrCryptoKeyConfigReadFile.WithArgs( 365 "./../../testdata/malformed/rsa_pri_as_pub.pem", 366 `asn1: structure error: tags don't match (16 vs {class:0 tag:2 length:1 isCompound:false}) `+ 367 `{optional:false explicit:false application:false private:false defaultValue:<nil> `+ 368 `tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} AlgorithmIdentifier @2`, 369 ), 370 }, 371 { 372 name: "cert passed as private key", 373 config: ` 374 crypto key k9738a405e99 sign-verify from file ./../../testdata/malformed/cert.pem 375 `, 376 shouldErr: true, 377 err: errors.ErrCryptoKeyConfigReadFile.WithArgs( 378 "./../../testdata/malformed/cert.pem", 379 errors.ErrNotPEMEncodedKey, 380 ), 381 }, 382 } 383 for _, tc := range testcases { 384 t.Run(tc.name, func(t *testing.T) { 385 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 386 msgs = append(msgs, fmt.Sprintf("config: %s", tc.config)) 387 for k, v := range tc.env { 388 if strings.HasPrefix(v, "file:") { 389 b, err := extractBytesFromFile(strings.TrimPrefix(v, "file:")) 390 if err != nil { 391 t.Fatal(err) 392 } 393 v = string(b) 394 } 395 msgs = append(msgs, fmt.Sprintf("env: %s = %s", k, v)) 396 os.Setenv(k, v) 397 defer os.Unsetenv(k) 398 } 399 400 configs, err := ParseCryptoKeyConfigs(tc.config) 401 if err != nil { 402 t.Fatal(err) 403 } 404 405 if tc.log { 406 for _, c := range configs { 407 t.Logf("%s", c.ToString()) 408 } 409 } 410 411 keys, err := GetKeysFromConfigs(configs) 412 if tests.EvalErrWithLog(t, err, "keys", tc.shouldErr, tc.err, msgs) { 413 return 414 } 415 416 got := make(map[string]interface{}) 417 got["config_count"] = len(configs) 418 got["key_count"] = len(keys) 419 420 var km []string 421 for i, k := range keys { 422 if k.Sign.Token.Capable { 423 km = append(km, fmt.Sprintf("%d: sign %s: %T", i, k.Sign.Token.ID, k.Sign.Secret)) 424 } 425 if k.Verify.Token.Capable { 426 km = append(km, fmt.Sprintf("%d: verify %s: %T", i, k.Verify.Token.ID, k.Verify.Secret)) 427 } 428 } 429 got["keys"] = km 430 431 if tc.log { 432 t.Logf("crypto configs:\n%s", cmp.Diff(nil, configs)) 433 for i, key := range keys { 434 t.Logf("crypto key %d:\n%s", i, cmp.Diff(nil, key)) 435 } 436 } 437 438 if diff := cmp.Diff(tc.want, got, cmp.AllowUnexported(CryptoKeyConfig{})); diff != "" { 439 tests.WriteLog(t, msgs) 440 t.Fatalf("output mismatch (-want +got):\n%s", diff) 441 } 442 443 if len(tc.keyPair) != 2 { 444 return 445 } 446 447 var privKey, pubKey *CryptoKey 448 for i, j := range tc.keyPair { 449 if j >= len(keys) { 450 break 451 } 452 if i == 0 { 453 privKey = keys[j] 454 continue 455 } 456 pubKey = keys[j] 457 } 458 459 ks := NewCryptoKeyStore() 460 if err := ks.AddKeys([]*CryptoKey{privKey, pubKey}); err != nil { 461 t.Fatal(err) 462 } 463 usr := newTestUser() 464 if tc.log { 465 t.Logf("%v", usr) 466 } 467 468 if err := ks.SignToken(privKey.Sign.Token.Name, privKey.Sign.Token.DefaultMethod, usr); err != nil { 469 t.Fatal(err) 470 } 471 472 if tc.log { 473 t.Logf("token %v: %s", privKey.Sign.Token.Name, usr.Token) 474 } 475 476 ar := requests.NewAuthorizationRequest() 477 ar.ID = "TEST_REQUEST_ID" 478 ar.SessionID = "TEST_SESSION_ID" 479 ar.Token.Name = pubKey.Verify.Token.Name 480 ar.Token.Payload = usr.Token 481 tokenUser, err := ks.ParseToken(ar) 482 if err != nil { 483 t.Fatal(err) 484 } 485 if tc.log { 486 t.Logf("user:\n%s", cmp.Diff(nil, tokenUser)) 487 } 488 }) 489 } 490 } 491 492 func TestGetKeysFromCryptoKeyConfigs(t *testing.T) { 493 var testcases = []struct { 494 name string 495 config *CryptoKeyConfig 496 shouldErr bool 497 err error 498 }{ 499 { 500 name: "bad config file path", 501 config: &CryptoKeyConfig{ 502 Source: "config", 503 FilePath: "foo", 504 }, 505 shouldErr: true, 506 err: fmt.Errorf(`kms: file "foo" is not supported due to extension type`), 507 }, 508 { 509 name: "bad config dir path", 510 config: &CryptoKeyConfig{ 511 Source: "config", 512 DirPath: "foo", 513 }, 514 shouldErr: true, 515 err: fmt.Errorf(`walking directory: lstat foo: no such file or directory`), 516 }, 517 { 518 name: "bad config without file dir path", 519 config: &CryptoKeyConfig{ 520 Source: "config", 521 }, 522 shouldErr: true, 523 err: fmt.Errorf(`unsupported config`), 524 }, 525 { 526 name: "bad env file path", 527 config: &CryptoKeyConfig{ 528 Source: "env", 529 EnvVarType: "file", 530 EnvVarValue: "foo", 531 }, 532 shouldErr: true, 533 err: fmt.Errorf(`kms: file "foo" is not supported due to extension type`), 534 }, 535 { 536 name: "bad env dir path", 537 config: &CryptoKeyConfig{ 538 Source: "env", 539 EnvVarType: "directory", 540 EnvVarValue: "foo", 541 }, 542 shouldErr: true, 543 err: fmt.Errorf(`walking directory: lstat foo: no such file or directory`), 544 }, 545 { 546 name: "bad env without file dir path", 547 config: &CryptoKeyConfig{ 548 Source: "env", 549 EnvVarType: "foo", 550 EnvVarValue: "foo", 551 }, 552 shouldErr: true, 553 err: fmt.Errorf(`unsupported env config type foo`), 554 }, 555 } 556 for _, tc := range testcases { 557 t.Run(tc.name, func(t *testing.T) { 558 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 559 msgs = append(msgs, fmt.Sprintf("config: %v", tc.config)) 560 _, err := GetKeysFromConfigs([]*CryptoKeyConfig{tc.config}) 561 if tests.EvalErrWithLog(t, err, nil, tc.shouldErr, tc.err, msgs) { 562 return 563 } 564 }) 565 } 566 }