blob: 272d9f08ff9e9ce50240980d8c63088d25ec6690 [file] [log] [blame]
/*-
* 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")
}