From 06548d06ca4a71ae2eda88c50ce36340f074f47b Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 25 Aug 2016 21:50:20 +0100 Subject: [PATCH] Add --keypass option to CLI Up to now we only supported the default key pass (the same as the store password). This change adds a new option to "encrypt" and "decrypt" commands. Fixes gh-26 --- .../encrypt/BaseEncryptOptionHandler.java | 38 ++++++++++++------ .../command/encrypt/DecryptCommandTests.java | 14 +++++++ .../src/test/resources/keystore.jks | Bin 0 -> 2236 bytes 3 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 spring-cloud-cli/src/test/resources/keystore.jks diff --git a/spring-cloud-cli/src/main/java/org/springframework/cloud/cli/command/encrypt/BaseEncryptOptionHandler.java b/spring-cloud-cli/src/main/java/org/springframework/cloud/cli/command/encrypt/BaseEncryptOptionHandler.java index 39c1961..f26b8e5 100644 --- a/spring-cloud-cli/src/main/java/org/springframework/cloud/cli/command/encrypt/BaseEncryptOptionHandler.java +++ b/spring-cloud-cli/src/main/java/org/springframework/cloud/cli/command/encrypt/BaseEncryptOptionHandler.java @@ -40,6 +40,8 @@ class BaseEncryptOptionHandler extends OptionHandler { private OptionSpec passwordOption; + private OptionSpec keyPassOption; + private Charset charset; { @@ -48,16 +50,18 @@ class BaseEncryptOptionHandler extends OptionHandler { @Override protected final void options() { - this.keyOption = option( - asList("key", "k"), + this.keyOption = option(asList("key", "k"), "Specify key (symmetric secret, or pem-encoded key). If the value starts with @ it is interpreted as a file location.") - .withRequiredArg(); + .withRequiredArg(); this.passwordOption = option("password", - "A password for the keyfile (assuming the --key option is a KetStore file).") - .withRequiredArg(); + "A password for the keyfile (assuming the --key option is a KeyStore file).") + .withRequiredArg(); + this.keyPassOption = option("keypass", + "A password for the key, defaults to the same as the store password (assuming the --key option is a KeyStore file).") + .withRequiredArg(); this.aliasOption = option("alias", - "An alias for the the key in a keyfile (assuming the --key option is a KetStore file).") - .withRequiredArg(); + "An alias for the the key in a keyfile (assuming the --key option is a KeyStore file).") + .withRequiredArg(); doOptions(); } @@ -66,17 +70,25 @@ class BaseEncryptOptionHandler extends OptionHandler { protected TextEncryptor createEncryptor(OptionSet options) { String value = keyOption.value(options); - if (value==null) { + if (value == null) { throw new MissingKeyException(); } if (options.has(passwordOption)) { // it's a keystore String password = options.valueOf(passwordOption); String alias = options.valueOf(aliasOption); - KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new FileSystemResource( - value), password.toCharArray()); - RsaSecretEncryptor encryptor = new RsaSecretEncryptor( - factory.getKeyPair(alias)); - return encryptor; + KeyStoreKeyFactory factory = new KeyStoreKeyFactory( + new FileSystemResource(value), password.toCharArray()); + if (options.has(keyPassOption)) { + String keypass = options.valueOf(keyPassOption); + RsaSecretEncryptor encryptor = new RsaSecretEncryptor( + factory.getKeyPair(alias, keypass.toCharArray())); + return encryptor; + } + else { + RsaSecretEncryptor encryptor = new RsaSecretEncryptor( + factory.getKeyPair(alias)); + return encryptor; + } } boolean verbose = Boolean.getBoolean("debug"); if (value.startsWith("@")) { diff --git a/spring-cloud-cli/src/test/java/org/springframework/cloud/cli/command/encrypt/DecryptCommandTests.java b/spring-cloud-cli/src/test/java/org/springframework/cloud/cli/command/encrypt/DecryptCommandTests.java index d0ea7a7..3e33e85 100644 --- a/spring-cloud-cli/src/test/java/org/springframework/cloud/cli/command/encrypt/DecryptCommandTests.java +++ b/spring-cloud-cli/src/test/java/org/springframework/cloud/cli/command/encrypt/DecryptCommandTests.java @@ -22,6 +22,7 @@ import java.nio.charset.Charset; import org.junit.Test; import org.springframework.boot.cli.command.status.ExitStatus; import org.springframework.core.io.ClassPathResource; +import org.springframework.security.rsa.crypto.KeyStoreKeyFactory; import org.springframework.security.rsa.crypto.RsaSecretEncryptor; import org.springframework.util.StreamUtils; @@ -49,6 +50,19 @@ public class DecryptCommandTests { command.run("-k", "@src/test/resources/private.pem", cipher)); } + @Test + public void decryptsFromRsaKeyWithKeyStore() throws Exception { + KeyStoreKeyFactory factory = new KeyStoreKeyFactory( + new ClassPathResource("keystore.jks"), "letmein".toCharArray()); + RsaSecretEncryptor encryptor = new RsaSecretEncryptor( + factory.getKeyPair("mytestkey", "changeme".toCharArray())); + String cipher = encryptor.encrypt("foo"); + assertEquals(ExitStatus.OK, + command.run("-k", "src/test/resources/keystore.jks", "--password", + "letmein", "--keypass", "changeme", "--alias", "mytestkey", + cipher)); + } + @Test(expected = IllegalArgumentException.class) public void failsWithPlainText() throws Exception { assertEquals(ExitStatus.OK, command.run("-k", "deadbeef", "foo")); diff --git a/spring-cloud-cli/src/test/resources/keystore.jks b/spring-cloud-cli/src/test/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..8cd9b5a16af313e008501514e2c263b8aa6f1727 GIT binary patch literal 2236 zcmchY*H@E?7RB?WfKmb>^roP+(2~%kc`pMPdJhDp2na|?D2WtBkRZJ%prS(Pp(8Me zB2@@oiXcUhDvCpq5=Zn>2WQ>8*8BzcVL$xN+7D;#bJjUWUyr_mKp^mmfWLK`7*6pd zQ~W%`Pb!w;E1$1|KrjGJg&v~eENt>D02Dxh5dZ)KL8;JHFE5>ZaMQT$xhyB$qt_XJ z%=!y@%^o)0QOPs60xLFe=QWUD;s+kW!v+#ssw4U3H(Q1=En~2-*QbTGjC$_}Yt_7+2+5=qwU4UYE1=s`A=hkQ6M zf8qCdE0!#IV%VsDjJOtQ%=&vOGrJr^dE#O&7yCKR-@0}%>)8@1*;m7iY7`u;i)HRZnpC3k*bxbOD@lGHYox^@FiX3>5Gdv$DBBQt9JKOrwPAW0A$H@D| z*%TYzMuYTTC3Tmr*@Ido0^C|@VC@NU;AZ{RT*bAi^E&F4L-wbWY2u!>en#3u3Ti^_ zNo+V%i4oHNh~QoPJ!3`yZ(Z8v4};J`$FYkb2tHO!y(`-Mqs&{Ly~=3E`n6q6{MR$n z>Aaq8qIxT|Qs@THz82WQ)DSDW`MZqHmtLGu4byKa>!WzVD1juH)o5yEqR(x|qK;xP z%HOAtw@yd+w9+kIvCK|d$lJ@|*Dc?ZD$YsI;}n)Fb@_^o1o@w|JkYzqomja7E|#2h z2sd6NUnL#U^2jU?tA_$7IlNTVzF>WOc2nL;^dG5SrbxF`h!mtG5|2yj7Ms1^eU89c z6qXFd!)y2BTqLJE;-r;%#sk{Jd0aY6#$H7+-zB**uevgP^p_dx6m#Kf4IkM}y263K zBTCFD{Rv+MagzgE?cb}5denkgS}~#J5?87$!0~n={OC7vZVE@IRO=u)K{v*iN(Y|I zPtjV&V0zKy>G_PNRfmDJ+`VlrdQzg~V98c&*++UdmhXomI5pCr)JlvfzT z(;b&NEL&A-fkvE%GOL9&OfKCU`QEnbLhs3=TS4rN)zPjrp^P$sV-r{j*whEdS*#<~81GMoYuXV0G6H8Rv&5qeLI?4JQ{VK>L)5=-u&uFoyy4hDYtIjAsvslO4BN*?q#B+?%LCo!w4 zJKrQ*%DyLODw+|B|JiViYjD55y#4LsFnP!|L^wvZM!?Sb({Tnrx1WB&ZcqX#f_&#& zPzda^#mp3^X6D&st=|wxe2|<=VTzM#>m|(Ft7S7i+Bs15zI?m94HKkAu>HXTZFVA( zdXJv4W{0r1Zx7S2l$;b7NP%SN+1PM$9ePn=ow_ePdrb`cus|!WFIV#K+Xy2lsi8GN zAV?6J3L&DYV0=CV41mEXs5qmzADjTd4g;aRG3@BmED$`D1qsI6V&G_w zUvLTuv!%FEJTaVT_Fv43gzEcH!ZCbk0-rNdI%OBcXVb4+Von zBYwFcNHzyg_p`R1!6BZ(XmJ5fj2ij^27|?_sH$Nv|3VA~t@&U4|20AdB>x%0$%}wc z0WJ`l3b28x002tUCFHV(iWb+u(W)!|cRxXDwq~SOd12y`jT^11Q0~sqEX^bE9)v@y z?Wm@R2Uvd*!`3E2cmZtNwtO=a61ZU1{ob zHvVC@weG{NjAcI+ozH)(P6oF+c_m7iWUhPO7(sO!HNM7wrHzNYJC-2uvhmiArZ=*~ zN#y7)*o^EYG~H<pyqJ=vZWX*H`r+Ez z%Q!2F=$Y0lGM2^!k!&MQp|x=@(9t z$AVBCa;@Kw_FM-jplz2ZF9>um40XA{+jGl8)gDFM-RU()!QkIp3H6`kQ|(mGv3qTo zRIHzo*Itl)4bR(VaA%7?D)l*j6^n~l)OO@N#XvAOXiGV1<*v!a^x`Gk!=&9o53!ey zGHNDUGvvs@eGmR)sSvF{R5|HM!&0o+c!N#Tj;#$+Lzdj1F8H2qzNvx`W%`!EI(zN* z;r;3T$x4x!aYn44)zO_%5#?PE9@9VwaSq+Lv$iA1#n|NrH^9FD&n3#|+$)_8ShL%^ VZkYQnRT-t*oz}jH@Reeu{srR%*l+*< literal 0 HcmV?d00001