Don Newton | 379ae25 | 2019-04-01 12:17:06 -0400 | [diff] [blame^] | 1 | // Copyright 2018 by David A. Golden. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | // not use this file except in compliance with the License. You may obtain |
| 5 | // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| 6 | |
| 7 | package scram |
| 8 | |
| 9 | import ( |
| 10 | "crypto/sha1" |
| 11 | "crypto/sha256" |
| 12 | "fmt" |
| 13 | "hash" |
| 14 | |
| 15 | "github.com/xdg/stringprep" |
| 16 | ) |
| 17 | |
| 18 | // HashGeneratorFcn abstracts a factory function that returns a hash.Hash |
| 19 | // value to be used for SCRAM operations. Generally, one would use the |
| 20 | // provided package variables, `scram.SHA1` and `scram.SHA256`, for the most |
| 21 | // common forms of SCRAM. |
| 22 | type HashGeneratorFcn func() hash.Hash |
| 23 | |
| 24 | // SHA1 is a function that returns a crypto/sha1 hasher and should be used to |
| 25 | // create Client objects configured for SHA-1 hashing. |
| 26 | var SHA1 HashGeneratorFcn = func() hash.Hash { return sha1.New() } |
| 27 | |
| 28 | // SHA256 is a function that returns a crypto/sha256 hasher and should be used |
| 29 | // to create Client objects configured for SHA-256 hashing. |
| 30 | var SHA256 HashGeneratorFcn = func() hash.Hash { return sha256.New() } |
| 31 | |
| 32 | // NewClient constructs a SCRAM client component based on a given hash.Hash |
| 33 | // factory receiver. This constructor will normalize the username, password |
| 34 | // and authzID via the SASLprep algorithm, as recommended by RFC-5802. If |
| 35 | // SASLprep fails, the method returns an error. |
| 36 | func (f HashGeneratorFcn) NewClient(username, password, authzID string) (*Client, error) { |
| 37 | var userprep, passprep, authprep string |
| 38 | var err error |
| 39 | |
| 40 | if userprep, err = stringprep.SASLprep.Prepare(username); err != nil { |
| 41 | return nil, fmt.Errorf("Error SASLprepping username '%s': %v", username, err) |
| 42 | } |
| 43 | if passprep, err = stringprep.SASLprep.Prepare(password); err != nil { |
| 44 | return nil, fmt.Errorf("Error SASLprepping password '%s': %v", password, err) |
| 45 | } |
| 46 | if authprep, err = stringprep.SASLprep.Prepare(authzID); err != nil { |
| 47 | return nil, fmt.Errorf("Error SASLprepping authzID '%s': %v", authzID, err) |
| 48 | } |
| 49 | |
| 50 | return newClient(userprep, passprep, authprep, f), nil |
| 51 | } |
| 52 | |
| 53 | // NewClientUnprepped acts like NewClient, except none of the arguments will |
| 54 | // be normalized via SASLprep. This is not generally recommended, but is |
| 55 | // provided for users that may have custom normalization needs. |
| 56 | func (f HashGeneratorFcn) NewClientUnprepped(username, password, authzID string) (*Client, error) { |
| 57 | return newClient(username, password, authzID, f), nil |
| 58 | } |
| 59 | |
| 60 | // NewServer constructs a SCRAM server component based on a given hash.Hash |
| 61 | // factory receiver. To be maximally generic, it uses dependency injection to |
| 62 | // handle credential lookup, which is the process of turning a username string |
| 63 | // into a struct with stored credentials for authentication. |
| 64 | func (f HashGeneratorFcn) NewServer(cl CredentialLookup) (*Server, error) { |
| 65 | return newServer(cl, f) |
| 66 | } |