1/*- 2 * Copyright 2014 Square Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package main 18 19import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 24 "gopkg.in/alecthomas/kingpin.v2" 25 "gopkg.in/square/go-jose.v2" 26) 27 28var ( 29 app = kingpin.New("jose-util", "A command-line utility for dealing with JOSE objects.") 30 31 keyFile = app.Flag("key", "Path to key file (PEM or DER-encoded)").ExistingFile() 32 inFile = app.Flag("in", "Path to input file (stdin if missing)").ExistingFile() 33 outFile = app.Flag("out", "Path to output file (stdout if missing)").ExistingFile() 34 35 encryptCommand = app.Command("encrypt", "Encrypt a plaintext, output ciphertext.") 36 algFlag = encryptCommand.Flag("alg", "Key management algorithm (e.g. RSA-OAEP)").Required().String() 37 encFlag = encryptCommand.Flag("enc", "Content encryption algorithm (e.g. A128GCM)").Required().String() 38 39 decryptCommand = app.Command("decrypt", "Decrypt a ciphertext, output plaintext.") 40 41 signCommand = app.Command("sign", "Sign a payload, output signed message.") 42 sigAlgFlag = signCommand.Flag("alg", "Key management algorithm (e.g. RSA-OAEP)").Required().String() 43 44 verifyCommand = app.Command("verify", "Verify a signed message, output payload.") 45 46 expandCommand = app.Command("expand", "Expand JOSE object to full serialization format.") 47 formatFlag = expandCommand.Flag("format", "Type of message to expand (JWS or JWE, defaults to JWE)").String() 48 49 full = app.Flag("full", "Use full serialization format (instead of compact)").Bool() 50) 51 52func main() { 53 app.Version("v2") 54 55 command := kingpin.MustParse(app.Parse(os.Args[1:])) 56 57 var keyBytes []byte 58 var err error 59 if command != "expand" { 60 keyBytes, err = ioutil.ReadFile(*keyFile) 61 exitOnError(err, "unable to read key file") 62 } 63 64 switch command { 65 case "encrypt": 66 pub, err := LoadPublicKey(keyBytes) 67 exitOnError(err, "unable to read public key") 68 69 alg := jose.KeyAlgorithm(*algFlag) 70 enc := jose.ContentEncryption(*encFlag) 71 72 crypter, err := jose.NewEncrypter(enc, jose.Recipient{Algorithm: alg, Key: pub}, nil) 73 exitOnError(err, "unable to instantiate encrypter") 74 75 obj, err := crypter.Encrypt(readInput(*inFile)) 76 exitOnError(err, "unable to encrypt") 77 78 var msg string 79 if *full { 80 msg = obj.FullSerialize() 81 } else { 82 msg, err = obj.CompactSerialize() 83 exitOnError(err, "unable to serialize message") 84 } 85 86 writeOutput(*outFile, []byte(msg)) 87 case "decrypt": 88 priv, err := LoadPrivateKey(keyBytes) 89 exitOnError(err, "unable to read private key") 90 91 obj, err := jose.ParseEncrypted(string(readInput(*inFile))) 92 exitOnError(err, "unable to parse message") 93 94 plaintext, err := obj.Decrypt(priv) 95 exitOnError(err, "unable to decrypt message") 96 97 writeOutput(*outFile, plaintext) 98 case "sign": 99 signingKey, err := LoadPrivateKey(keyBytes) 100 exitOnError(err, "unable to read private key") 101 102 alg := jose.SignatureAlgorithm(*sigAlgFlag) 103 signer, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: signingKey}, nil) 104 exitOnError(err, "unable to make signer") 105 106 obj, err := signer.Sign(readInput(*inFile)) 107 exitOnError(err, "unable to sign") 108 109 var msg string 110 if *full { 111 msg = obj.FullSerialize() 112 } else { 113 msg, err = obj.CompactSerialize() 114 exitOnError(err, "unable to serialize message") 115 } 116 117 writeOutput(*outFile, []byte(msg)) 118 case "verify": 119 verificationKey, err := LoadPublicKey(keyBytes) 120 exitOnError(err, "unable to read public key") 121 122 obj, err := jose.ParseSigned(string(readInput(*inFile))) 123 exitOnError(err, "unable to parse message") 124 125 plaintext, err := obj.Verify(verificationKey) 126 exitOnError(err, "invalid signature") 127 128 writeOutput(*outFile, plaintext) 129 case "expand": 130 input := string(readInput(*inFile)) 131 132 var serialized string 133 var err error 134 switch *formatFlag { 135 case "", "JWE": 136 var jwe *jose.JSONWebEncryption 137 jwe, err = jose.ParseEncrypted(input) 138 if err == nil { 139 serialized = jwe.FullSerialize() 140 } 141 case "JWS": 142 var jws *jose.JSONWebSignature 143 jws, err = jose.ParseSigned(input) 144 if err == nil { 145 serialized = jws.FullSerialize() 146 } 147 } 148 149 exitOnError(err, "unable to expand message") 150 writeOutput(*outFile, []byte(serialized)) 151 writeOutput(*outFile, []byte("\n")) 152 } 153} 154 155// Exit and print error message if we encountered a problem 156func exitOnError(err error, msg string) { 157 if err != nil { 158 fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err) 159 os.Exit(1) 160 } 161} 162 163// Read input from file or stdin 164func readInput(path string) []byte { 165 var bytes []byte 166 var err error 167 168 if path != "" { 169 bytes, err = ioutil.ReadFile(path) 170 } else { 171 bytes, err = ioutil.ReadAll(os.Stdin) 172 } 173 174 exitOnError(err, "unable to read input") 175 return bytes 176} 177 178// Write output to file or stdin 179func writeOutput(path string, data []byte) { 180 var err error 181 182 if path != "" { 183 err = ioutil.WriteFile(path, data, 0644) 184 } else { 185 _, err = os.Stdout.Write(data) 186 } 187 188 exitOnError(err, "unable to write output") 189} 190