David K. Bainbridge | 215e024 | 2017-09-05 23:18:24 -0700 | [diff] [blame] | 1 | package libtrust |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "encoding/pem" |
| 6 | "errors" |
| 7 | "fmt" |
| 8 | "io/ioutil" |
| 9 | "os" |
| 10 | "strings" |
| 11 | ) |
| 12 | |
| 13 | var ( |
| 14 | // ErrKeyFileDoesNotExist indicates that the private key file does not exist. |
| 15 | ErrKeyFileDoesNotExist = errors.New("key file does not exist") |
| 16 | ) |
| 17 | |
| 18 | func readKeyFileBytes(filename string) ([]byte, error) { |
| 19 | data, err := ioutil.ReadFile(filename) |
| 20 | if err != nil { |
| 21 | if os.IsNotExist(err) { |
| 22 | err = ErrKeyFileDoesNotExist |
| 23 | } else { |
| 24 | err = fmt.Errorf("unable to read key file %s: %s", filename, err) |
| 25 | } |
| 26 | |
| 27 | return nil, err |
| 28 | } |
| 29 | |
| 30 | return data, nil |
| 31 | } |
| 32 | |
| 33 | /* |
| 34 | Loading and Saving of Public and Private Keys in either PEM or JWK format. |
| 35 | */ |
| 36 | |
| 37 | // LoadKeyFile opens the given filename and attempts to read a Private Key |
| 38 | // encoded in either PEM or JWK format (if .json or .jwk file extension). |
| 39 | func LoadKeyFile(filename string) (PrivateKey, error) { |
| 40 | contents, err := readKeyFileBytes(filename) |
| 41 | if err != nil { |
| 42 | return nil, err |
| 43 | } |
| 44 | |
| 45 | var key PrivateKey |
| 46 | |
| 47 | if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { |
| 48 | key, err = UnmarshalPrivateKeyJWK(contents) |
| 49 | if err != nil { |
| 50 | return nil, fmt.Errorf("unable to decode private key JWK: %s", err) |
| 51 | } |
| 52 | } else { |
| 53 | key, err = UnmarshalPrivateKeyPEM(contents) |
| 54 | if err != nil { |
| 55 | return nil, fmt.Errorf("unable to decode private key PEM: %s", err) |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | return key, nil |
| 60 | } |
| 61 | |
| 62 | // LoadPublicKeyFile opens the given filename and attempts to read a Public Key |
| 63 | // encoded in either PEM or JWK format (if .json or .jwk file extension). |
| 64 | func LoadPublicKeyFile(filename string) (PublicKey, error) { |
| 65 | contents, err := readKeyFileBytes(filename) |
| 66 | if err != nil { |
| 67 | return nil, err |
| 68 | } |
| 69 | |
| 70 | var key PublicKey |
| 71 | |
| 72 | if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { |
| 73 | key, err = UnmarshalPublicKeyJWK(contents) |
| 74 | if err != nil { |
| 75 | return nil, fmt.Errorf("unable to decode public key JWK: %s", err) |
| 76 | } |
| 77 | } else { |
| 78 | key, err = UnmarshalPublicKeyPEM(contents) |
| 79 | if err != nil { |
| 80 | return nil, fmt.Errorf("unable to decode public key PEM: %s", err) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | return key, nil |
| 85 | } |
| 86 | |
| 87 | // SaveKey saves the given key to a file using the provided filename. |
| 88 | // This process will overwrite any existing file at the provided location. |
| 89 | func SaveKey(filename string, key PrivateKey) error { |
| 90 | var encodedKey []byte |
| 91 | var err error |
| 92 | |
| 93 | if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { |
| 94 | // Encode in JSON Web Key format. |
| 95 | encodedKey, err = json.MarshalIndent(key, "", " ") |
| 96 | if err != nil { |
| 97 | return fmt.Errorf("unable to encode private key JWK: %s", err) |
| 98 | } |
| 99 | } else { |
| 100 | // Encode in PEM format. |
| 101 | pemBlock, err := key.PEMBlock() |
| 102 | if err != nil { |
| 103 | return fmt.Errorf("unable to encode private key PEM: %s", err) |
| 104 | } |
| 105 | encodedKey = pem.EncodeToMemory(pemBlock) |
| 106 | } |
| 107 | |
| 108 | err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600)) |
| 109 | if err != nil { |
| 110 | return fmt.Errorf("unable to write private key file %s: %s", filename, err) |
| 111 | } |
| 112 | |
| 113 | return nil |
| 114 | } |
| 115 | |
| 116 | // SavePublicKey saves the given public key to the file. |
| 117 | func SavePublicKey(filename string, key PublicKey) error { |
| 118 | var encodedKey []byte |
| 119 | var err error |
| 120 | |
| 121 | if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { |
| 122 | // Encode in JSON Web Key format. |
| 123 | encodedKey, err = json.MarshalIndent(key, "", " ") |
| 124 | if err != nil { |
| 125 | return fmt.Errorf("unable to encode public key JWK: %s", err) |
| 126 | } |
| 127 | } else { |
| 128 | // Encode in PEM format. |
| 129 | pemBlock, err := key.PEMBlock() |
| 130 | if err != nil { |
| 131 | return fmt.Errorf("unable to encode public key PEM: %s", err) |
| 132 | } |
| 133 | encodedKey = pem.EncodeToMemory(pemBlock) |
| 134 | } |
| 135 | |
| 136 | err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644)) |
| 137 | if err != nil { |
| 138 | return fmt.Errorf("unable to write public key file %s: %s", filename, err) |
| 139 | } |
| 140 | |
| 141 | return nil |
| 142 | } |
| 143 | |
| 144 | // Public Key Set files |
| 145 | |
| 146 | type jwkSet struct { |
| 147 | Keys []json.RawMessage `json:"keys"` |
| 148 | } |
| 149 | |
| 150 | // LoadKeySetFile loads a key set |
| 151 | func LoadKeySetFile(filename string) ([]PublicKey, error) { |
| 152 | if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { |
| 153 | return loadJSONKeySetFile(filename) |
| 154 | } |
| 155 | |
| 156 | // Must be a PEM format file |
| 157 | return loadPEMKeySetFile(filename) |
| 158 | } |
| 159 | |
| 160 | func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) { |
| 161 | if len(data) == 0 { |
| 162 | // This is okay, just return an empty slice. |
| 163 | return []json.RawMessage{}, nil |
| 164 | } |
| 165 | |
| 166 | keySet := jwkSet{} |
| 167 | |
| 168 | err := json.Unmarshal(data, &keySet) |
| 169 | if err != nil { |
| 170 | return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err) |
| 171 | } |
| 172 | |
| 173 | return keySet.Keys, nil |
| 174 | } |
| 175 | |
| 176 | func loadJSONKeySetFile(filename string) ([]PublicKey, error) { |
| 177 | contents, err := readKeyFileBytes(filename) |
| 178 | if err != nil && err != ErrKeyFileDoesNotExist { |
| 179 | return nil, err |
| 180 | } |
| 181 | |
| 182 | return UnmarshalPublicKeyJWKSet(contents) |
| 183 | } |
| 184 | |
| 185 | func loadPEMKeySetFile(filename string) ([]PublicKey, error) { |
| 186 | data, err := readKeyFileBytes(filename) |
| 187 | if err != nil && err != ErrKeyFileDoesNotExist { |
| 188 | return nil, err |
| 189 | } |
| 190 | |
| 191 | return UnmarshalPublicKeyPEMBundle(data) |
| 192 | } |
| 193 | |
| 194 | // AddKeySetFile adds a key to a key set |
| 195 | func AddKeySetFile(filename string, key PublicKey) error { |
| 196 | if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { |
| 197 | return addKeySetJSONFile(filename, key) |
| 198 | } |
| 199 | |
| 200 | // Must be a PEM format file |
| 201 | return addKeySetPEMFile(filename, key) |
| 202 | } |
| 203 | |
| 204 | func addKeySetJSONFile(filename string, key PublicKey) error { |
| 205 | encodedKey, err := json.Marshal(key) |
| 206 | if err != nil { |
| 207 | return fmt.Errorf("unable to encode trusted client key: %s", err) |
| 208 | } |
| 209 | |
| 210 | contents, err := readKeyFileBytes(filename) |
| 211 | if err != nil && err != ErrKeyFileDoesNotExist { |
| 212 | return err |
| 213 | } |
| 214 | |
| 215 | rawEntries, err := loadJSONKeySetRaw(contents) |
| 216 | if err != nil { |
| 217 | return err |
| 218 | } |
| 219 | |
| 220 | rawEntries = append(rawEntries, json.RawMessage(encodedKey)) |
| 221 | entriesWrapper := jwkSet{Keys: rawEntries} |
| 222 | |
| 223 | encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ") |
| 224 | if err != nil { |
| 225 | return fmt.Errorf("unable to encode trusted client keys: %s", err) |
| 226 | } |
| 227 | |
| 228 | err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644)) |
| 229 | if err != nil { |
| 230 | return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err) |
| 231 | } |
| 232 | |
| 233 | return nil |
| 234 | } |
| 235 | |
| 236 | func addKeySetPEMFile(filename string, key PublicKey) error { |
| 237 | // Encode to PEM, open file for appending, write PEM. |
| 238 | file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644)) |
| 239 | if err != nil { |
| 240 | return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err) |
| 241 | } |
| 242 | defer file.Close() |
| 243 | |
| 244 | pemBlock, err := key.PEMBlock() |
| 245 | if err != nil { |
| 246 | return fmt.Errorf("unable to encoded trusted key: %s", err) |
| 247 | } |
| 248 | |
| 249 | _, err = file.Write(pem.EncodeToMemory(pemBlock)) |
| 250 | if err != nil { |
| 251 | return fmt.Errorf("unable to write trusted keys file: %s", err) |
| 252 | } |
| 253 | |
| 254 | return nil |
| 255 | } |