| /*- |
| * Copyright 2014 Square Inc. |
| * |
| * 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. |
| */ |
| |
| package main |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "os" |
| |
| "gopkg.in/alecthomas/kingpin.v2" |
| "gopkg.in/square/go-jose.v2" |
| ) |
| |
| var ( |
| app = kingpin.New("jose-util", "A command-line utility for dealing with JOSE objects.") |
| |
| keyFile = app.Flag("key", "Path to key file (PEM or DER-encoded)").ExistingFile() |
| inFile = app.Flag("in", "Path to input file (stdin if missing)").ExistingFile() |
| outFile = app.Flag("out", "Path to output file (stdout if missing)").ExistingFile() |
| |
| encryptCommand = app.Command("encrypt", "Encrypt a plaintext, output ciphertext.") |
| algFlag = encryptCommand.Flag("alg", "Key management algorithm (e.g. RSA-OAEP)").Required().String() |
| encFlag = encryptCommand.Flag("enc", "Content encryption algorithm (e.g. A128GCM)").Required().String() |
| |
| decryptCommand = app.Command("decrypt", "Decrypt a ciphertext, output plaintext.") |
| |
| signCommand = app.Command("sign", "Sign a payload, output signed message.") |
| sigAlgFlag = signCommand.Flag("alg", "Key management algorithm (e.g. RSA-OAEP)").Required().String() |
| |
| verifyCommand = app.Command("verify", "Verify a signed message, output payload.") |
| |
| expandCommand = app.Command("expand", "Expand JOSE object to full serialization format.") |
| formatFlag = expandCommand.Flag("format", "Type of message to expand (JWS or JWE, defaults to JWE)").String() |
| |
| full = app.Flag("full", "Use full serialization format (instead of compact)").Bool() |
| ) |
| |
| func main() { |
| app.Version("v2") |
| |
| command := kingpin.MustParse(app.Parse(os.Args[1:])) |
| |
| var keyBytes []byte |
| var err error |
| if command != "expand" { |
| keyBytes, err = ioutil.ReadFile(*keyFile) |
| exitOnError(err, "unable to read key file") |
| } |
| |
| switch command { |
| case "encrypt": |
| pub, err := LoadPublicKey(keyBytes) |
| exitOnError(err, "unable to read public key") |
| |
| alg := jose.KeyAlgorithm(*algFlag) |
| enc := jose.ContentEncryption(*encFlag) |
| |
| crypter, err := jose.NewEncrypter(enc, jose.Recipient{Algorithm: alg, Key: pub}, nil) |
| exitOnError(err, "unable to instantiate encrypter") |
| |
| obj, err := crypter.Encrypt(readInput(*inFile)) |
| exitOnError(err, "unable to encrypt") |
| |
| var msg string |
| if *full { |
| msg = obj.FullSerialize() |
| } else { |
| msg, err = obj.CompactSerialize() |
| exitOnError(err, "unable to serialize message") |
| } |
| |
| writeOutput(*outFile, []byte(msg)) |
| case "decrypt": |
| priv, err := LoadPrivateKey(keyBytes) |
| exitOnError(err, "unable to read private key") |
| |
| obj, err := jose.ParseEncrypted(string(readInput(*inFile))) |
| exitOnError(err, "unable to parse message") |
| |
| plaintext, err := obj.Decrypt(priv) |
| exitOnError(err, "unable to decrypt message") |
| |
| writeOutput(*outFile, plaintext) |
| case "sign": |
| signingKey, err := LoadPrivateKey(keyBytes) |
| exitOnError(err, "unable to read private key") |
| |
| alg := jose.SignatureAlgorithm(*sigAlgFlag) |
| signer, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: signingKey}, nil) |
| exitOnError(err, "unable to make signer") |
| |
| obj, err := signer.Sign(readInput(*inFile)) |
| exitOnError(err, "unable to sign") |
| |
| var msg string |
| if *full { |
| msg = obj.FullSerialize() |
| } else { |
| msg, err = obj.CompactSerialize() |
| exitOnError(err, "unable to serialize message") |
| } |
| |
| writeOutput(*outFile, []byte(msg)) |
| case "verify": |
| verificationKey, err := LoadPublicKey(keyBytes) |
| exitOnError(err, "unable to read public key") |
| |
| obj, err := jose.ParseSigned(string(readInput(*inFile))) |
| exitOnError(err, "unable to parse message") |
| |
| plaintext, err := obj.Verify(verificationKey) |
| exitOnError(err, "invalid signature") |
| |
| writeOutput(*outFile, plaintext) |
| case "expand": |
| input := string(readInput(*inFile)) |
| |
| var serialized string |
| var err error |
| switch *formatFlag { |
| case "", "JWE": |
| var jwe *jose.JSONWebEncryption |
| jwe, err = jose.ParseEncrypted(input) |
| if err == nil { |
| serialized = jwe.FullSerialize() |
| } |
| case "JWS": |
| var jws *jose.JSONWebSignature |
| jws, err = jose.ParseSigned(input) |
| if err == nil { |
| serialized = jws.FullSerialize() |
| } |
| } |
| |
| exitOnError(err, "unable to expand message") |
| writeOutput(*outFile, []byte(serialized)) |
| writeOutput(*outFile, []byte("\n")) |
| } |
| } |
| |
| // Exit and print error message if we encountered a problem |
| func exitOnError(err error, msg string) { |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err) |
| os.Exit(1) |
| } |
| } |
| |
| // Read input from file or stdin |
| func readInput(path string) []byte { |
| var bytes []byte |
| var err error |
| |
| if path != "" { |
| bytes, err = ioutil.ReadFile(path) |
| } else { |
| bytes, err = ioutil.ReadAll(os.Stdin) |
| } |
| |
| exitOnError(err, "unable to read input") |
| return bytes |
| } |
| |
| // Write output to file or stdin |
| func writeOutput(path string, data []byte) { |
| var err error |
| |
| if path != "" { |
| err = ioutil.WriteFile(path, data, 0644) |
| } else { |
| _, err = os.Stdout.Write(data) |
| } |
| |
| exitOnError(err, "unable to write output") |
| } |