├── .gitignore ├── LICENSE ├── README.md ├── hsmsim-akka ├── pom.xml └── src │ ├── main │ └── scala │ │ └── org │ │ └── leachbj │ │ └── hsmsim │ │ ├── Sim.scala │ │ ├── akka │ │ └── HsmSimulator.scala │ │ ├── commands │ │ ├── GenerateIBMPinOffset.scala │ │ ├── GenerateMac.scala │ │ ├── GenerateRSAKeySet.scala │ │ ├── GenerateZpk.scala │ │ ├── HsmMessageEncoding.scala │ │ ├── ImportDesKey.scala │ │ ├── TranslatePinZpkToAnother.scala │ │ ├── TranslatePinZpkToLmk.scala │ │ ├── TranslateZpkFromZmkToLmk.scala │ │ └── VerifyInterchangePinIBM.scala │ │ ├── crypto │ │ ├── DES.scala │ │ ├── IBMPinValidation.scala │ │ └── LMK.scala │ │ └── util │ │ └── HexConverter.scala │ └── test │ └── scala │ └── org │ └── leachbj │ └── hsmsim │ ├── commands │ ├── GenerateIBMPinOffsetSuite.scala │ ├── GenerateMacSuite.scala │ ├── GenerateRSAKeySetSuite.scala │ ├── GenerateZpkSuite.scala │ ├── HsmMessageEncodingSuite.scala │ ├── ImportDesKeySuite.scala │ ├── TranslatePinZpkToAnotherSuite.scala │ ├── TranslatePinZpkToLmkSuite.scala │ ├── TranslateZpkFromZmkToLmkSuite.scala │ └── VerifyInterchangePinIBMSuite.scala │ ├── crypto │ ├── DESSuite.scala │ ├── IBMPinValidationSuite.scala │ └── LMKSuite.scala │ └── util │ └── HexConverterSuite.scala ├── hsmsim-war ├── pom.xml └── src │ └── main │ ├── resources │ └── akka.conf │ ├── scala │ └── org │ │ └── leachbj │ │ └── hsmsim │ │ └── servlet │ │ └── ContextListener.scala │ └── webapp │ ├── WEB-INF │ └── web.xml │ └── index.jsp └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | target/ 5 | .idea/ 6 | *.iml 7 | *.iws 8 | .DS_Store 9 | .cache 10 | .vscode 11 | hsmsim-akka/bin/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Please note this should be read in the same way as the MIT license [1]. 2 | 3 | Copyright (c) 2013 Bernard Leach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | 1. http://opensource.org/licenses/MIT -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [hsmsim](http://github.com/leachbj/hsmsim) is a simple HSM simulator providing a number of commands compatible 2 | with a Thales 8000/9000 HSM. The simulator only supports a small number of commands and can only use test LMKs 3 | so should not be considered a replacement for a real HSM however it may be useful during a development of software 4 | that interacts with a HSM. 5 | 6 | ## Quick start 7 | 8 | The simulator runs as a java process. 9 | 10 | Apache Maven is required to compile the code for this project. 11 | 12 | ### Compile 13 | `mvn package` 14 | 15 | ### Run the packaged jar 16 | `java -jar target/hsmsim-akka/hsmsim.jar` 17 | 18 | Alternatively the simulator can be deployed as a web application, deploy the target/hsmsim-war/hsmsim.war file to a suitable 19 | servlet container. 20 | 21 | ## Contributing 22 | 23 | The simulator supports a very small number of commands and only supports the test LMKs. Contributions of 24 | additional command support welcomed. 25 | 26 | ## License 27 | Copyright 2013 Bernard Leach 28 | 29 | Licensed under the MIT license [http://opensource.org/licenses/MIT] 30 | -------------------------------------------------------------------------------- /hsmsim-akka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.leachbj 6 | hsmsim 7 | 0.0.1-SNAPSHOT 8 | 9 | hsmsim-akka 10 | jar 11 | hsmsim-akka 12 | 13 | 14 | com.typesafe.akka 15 | akka-actor_2.10 16 | 17 | 18 | junit 19 | junit 20 | test 21 | 22 | 23 | org.bouncycastle 24 | bcprov-ext-jdk16 25 | 26 | 27 | org.scala-lang 28 | scala-library 29 | 30 | 31 | org.scalatest 32 | scalatest_2.10 33 | test 34 | 35 | 36 | 37 | hsmsim 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 43 | 1.8 44 | 1.8 45 | org.leachbj.hsmsim.Sim 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-dependency-plugin 51 | 52 | 53 | copy-dependencies 54 | 55 | copy-dependencies 56 | 57 | package 58 | 59 | runtime 60 | ${project.build.directory}/lib 61 | true 62 | true 63 | true 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-jar-plugin 71 | 72 | 73 | true 74 | 75 | lib/ 76 | true 77 | org.leachbj.hsmsim.Sim 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-surefire-plugin 85 | 86 | 87 | org.scala-tools 88 | maven-scala-plugin 89 | 90 | 91 | 92 | 93 | org.scalatest 94 | scalatest-maven-plugin 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/Sim.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim 23 | 24 | import _root_.akka.Main 25 | 26 | /** 27 | * Simple [[App]] version of the simulator. 28 | */ 29 | object Sim extends App { 30 | Main.main(Array("org.leachbj.hsmsim.akka.HsmSimulator")) 31 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/akka/HsmSimulator.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.akka 23 | 24 | import java.net.InetSocketAddress 25 | 26 | import org.leachbj.hsmsim.commands.ErrorResponse 27 | import org.leachbj.hsmsim.commands.GenerateIBMPinOffsetRequest 28 | import org.leachbj.hsmsim.commands.GenerateIBMPinOffsetResponse 29 | import org.leachbj.hsmsim.commands.GenerateMacRequest 30 | import org.leachbj.hsmsim.commands.GenerateMacResponse 31 | import org.leachbj.hsmsim.commands.GenerateRSAKeySetRequest 32 | import org.leachbj.hsmsim.commands.GenerateRSAKeySetResponse 33 | import org.leachbj.hsmsim.commands.GenerateRandomPinRequest 34 | import org.leachbj.hsmsim.commands.GenerateRandomPinResponse 35 | import org.leachbj.hsmsim.commands.GenerateZpkRequest 36 | import org.leachbj.hsmsim.commands.GenerateZpkResponse 37 | import org.leachbj.hsmsim.commands.HsmMessageEncoding 38 | import org.leachbj.hsmsim.commands.HsmRequest 39 | import org.leachbj.hsmsim.commands.HsmResponse 40 | import org.leachbj.hsmsim.commands.ImportDesKeyRequest 41 | import org.leachbj.hsmsim.commands.ImportDesKeyResponse 42 | import org.leachbj.hsmsim.commands.TranslatePinZpkToAnotherRequest 43 | import org.leachbj.hsmsim.commands.TranslatePinZpkToAnotherResponse 44 | import org.leachbj.hsmsim.commands.TranslatePinZpkToLmkRequest 45 | import org.leachbj.hsmsim.commands.TranslatePinZpkToLmkResponse 46 | import org.leachbj.hsmsim.commands.TranslateZpkFromZmkToLmkRequest 47 | import org.leachbj.hsmsim.commands.TranslateZpkFromZmkToLmkResponse 48 | import org.leachbj.hsmsim.commands.UnknownHsmRequest 49 | import org.leachbj.hsmsim.commands.VerifyInterchangePinIBMRequest 50 | import org.leachbj.hsmsim.commands.VerifyInterchangePinIBMResponse 51 | 52 | import akka.actor.Actor 53 | import akka.actor.ActorLogging 54 | import akka.actor.ActorRef 55 | import akka.actor.Deploy 56 | import akka.actor.Props 57 | import akka.actor.SupervisorStrategy 58 | import akka.actor.actorRef2Scala 59 | import akka.io.BackpressureBuffer 60 | import akka.io.IO 61 | import akka.io.LengthFieldFrame 62 | import akka.io.PipePair 63 | import akka.io.PipelineContext 64 | import akka.io.PipelineStage 65 | import akka.io.Tcp 66 | import akka.io.Tcp.Bind 67 | import akka.io.Tcp.Bound 68 | import akka.io.Tcp.CommandFailed 69 | import akka.io.Tcp.Connected 70 | import akka.io.TcpPipelineHandler 71 | import akka.io.TcpPipelineHandler.Init 72 | import akka.io.TcpPipelineHandler.WithinActorContext 73 | import akka.io.TcpReadWriteAdapter 74 | import akka.util.ByteString 75 | 76 | /** 77 | * The simulator [[Actor]] responsible for listening to connections on port 1501 78 | * and then creating [[HsmHandler]] [[Actor]] instances to process the client requests. 79 | */ 80 | class HsmSimulator extends Actor with ActorLogging { 81 | import akka.io.Tcp._ 82 | import context.system 83 | 84 | // there is not recovery for broken connections 85 | override val supervisorStrategy = SupervisorStrategy.stoppingStrategy 86 | 87 | // bind to the listen port; the port will automatically be closed once this actor dies 88 | override def preStart(): Unit = { 89 | IO(Tcp) ! Bind(self, new InetSocketAddress("localhost", 1501)) 90 | } 91 | 92 | // do not restart 93 | override def postRestart(thr: Throwable): Unit = context stop self 94 | 95 | def receive: Receive = { 96 | case b @ Bound(localAddress) => 97 | log.info("listening on port {}", localAddress.getPort) 98 | context.become(bound(sender)) 99 | case CommandFailed(Bind(_, local, _, _)) => 100 | log.warning(s"cannot bind to [$local]") 101 | context stop self 102 | } 103 | 104 | def bound(listener: ActorRef): Receive = { 105 | case Connected(remote, local) => 106 | log.info("received connection from {}", remote) 107 | 108 | val init = TcpPipelineHandler.withLogger(log, 109 | new HsmMessageStage >> 110 | new LengthFieldFrame(2048, headerSize = 2, lengthIncludesHeader = false) >> 111 | new TcpReadWriteAdapter >> 112 | new BackpressureBuffer(lowBytes = 100, highBytes = 1000, maxBytes = 1000000)) 113 | 114 | val connection = sender 115 | val handler = context.actorOf(Props(new HsmHandler(init)).withDeploy(Deploy.local)) 116 | val pipeline = context.actorOf(TcpPipelineHandler.props( 117 | init, connection, handler).withDeploy(Deploy.local)) 118 | 119 | connection ! Tcp.Register(pipeline) 120 | } 121 | } 122 | 123 | /** 124 | * Receives [[HsmRequest]] commands and responds with [[HsmResponse]] responses back to the sender. 125 | */ 126 | class HsmHandler(init: Init[WithinActorContext, HsmResponse, HsmRequest]) extends Actor with ActorLogging { 127 | def receive: Receive = { 128 | case init.Event(default) => 129 | log.debug("Received command: {}", default) 130 | val processor = context.actorOf(Props(new RequestProcessor(init))) 131 | processor.forward(default) 132 | case _ => 133 | log.error("Unhandled message!") 134 | } 135 | } 136 | 137 | class RequestProcessor(init: Init[WithinActorContext, HsmResponse, HsmRequest]) extends Actor with ActorLogging { 138 | def receive: Receive = { 139 | case t: TranslatePinZpkToAnotherRequest => 140 | sender ! init.Command(TranslatePinZpkToAnotherResponse.createResponse(t)) 141 | case i: ImportDesKeyRequest => 142 | sender ! init.Command(ImportDesKeyResponse.createResponse(i)) 143 | case g: GenerateRandomPinRequest => 144 | sender ! init.Command(GenerateRandomPinResponse("00", "12345")) 145 | case v: VerifyInterchangePinIBMRequest => 146 | sender ! init.Command(VerifyInterchangePinIBMResponse.createResponse(v)) 147 | case translatePin: TranslatePinZpkToLmkRequest => 148 | sender ! init.Command(TranslatePinZpkToLmkResponse.createResponse(translatePin)) 149 | case generateOffset: GenerateIBMPinOffsetRequest => 150 | sender ! init.Command(GenerateIBMPinOffsetResponse.createResponse(generateOffset)) 151 | case translateZpk: TranslateZpkFromZmkToLmkRequest => 152 | sender ! init.Command(TranslateZpkFromZmkToLmkResponse.createResponse(translateZpk)) 153 | case generateZpk: GenerateZpkRequest => 154 | sender ! init.Command(GenerateZpkResponse.createResponse(generateZpk)) 155 | case generateMac: GenerateMacRequest => 156 | sender ! init.Command(GenerateMacResponse.createResponse(generateMac)) 157 | case generateRsa: GenerateRSAKeySetRequest => 158 | sender ! init.Command(GenerateRSAKeySetResponse.createResponse(generateRsa)) 159 | case unknown: UnknownHsmRequest => 160 | log.error("Unknown command type {}", unknown.cmd) 161 | val responseCode = "" + unknown.cmd.charAt(0) + (unknown.cmd.charAt(1) + 1) 162 | sender ! init.Command(ErrorResponse(responseCode, "99")) 163 | case _ => 164 | log.error("Unhandled message!") 165 | } 166 | } 167 | 168 | /** 169 | * HsmMessageStage converts between arriving events (as ByteString) to HsmRequest instances 170 | * and commands (as HsmResponse) to ByteString 171 | */ 172 | class HsmMessageStage extends PipelineStage[PipelineContext, HsmResponse, ByteString, HsmRequest, ByteString] { 173 | override def apply(ctx: PipelineContext) = new PipePair[HsmResponse, ByteString, HsmRequest, ByteString] { 174 | // command arrives as a HsmResponse and is transformed to a ByteString 175 | override val commandPipeline = { msg: HsmResponse => 176 | println(" Encoding " + msg) 177 | ctx.singleCommand(HsmMessageEncoding.encode(msg)) 178 | } 179 | 180 | // event arrives from TCP as a ByteString and is transformed to the HsmRequest 181 | override val eventPipeline = { bs: ByteString => 182 | println("processing event: " + bs) 183 | ctx.singleEvent(HsmMessageEncoding.decode(bs)) 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/GenerateIBMPinOffset.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.crypto.DES 25 | import org.leachbj.hsmsim.crypto.LMK 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import akka.util.ByteString 28 | import org.leachbj.hsmsim.crypto.IBMPinValidation 29 | 30 | /** 31 | * DE: Generate IBM PIN offset from an LMK encrypted pin 32 | * 33 | * @constructor create the request 34 | * @param pvk 16H, 1A+32H or 1A+48H - PVK encrypted under LMK pair 14-15; used to generate the offset. 35 | * @parm pinUnderLmk LN or LH - The PIN for which an offset is required; encrypted under LMK pair 02-03. 36 | * @param checkLength 2N - The minimum PIN length. 37 | * @param accountNumber 12N - The 12 right-most digits of the account number, excluding check digit. 38 | * @param decimalization 16N - The table for converting hexadecimal values to decimal. 39 | * @param pinValidation 12A - User-defined data consisting of hexadecimal characters and the character N, which indicates to the HSM where to insert the last 5 digits of the account number. 40 | * 41 | */ 42 | case class GenerateIBMPinOffsetRequest(pvk: Array[Byte], pinUnderLmk: Array[Byte], checkLength: Int, accountNumber: String, decimalisation: Array[Byte], pinValidation: String) extends HsmRequest { 43 | override def toString() = { 44 | "GenerateIBMPinOffsetRequest(" + HexConverter.toHex(ByteString(pvk)) + ", " + HexConverter.toHex(ByteString(pinUnderLmk)) + "," + checkLength + "," + accountNumber + "," + HexConverter.toHex(ByteString(decimalisation)) + "," + pinValidation + ")" 45 | } 46 | } 47 | 48 | /** 49 | * DF: Generate IBM PIN offset response 50 | * 51 | * @constructor create a successful response 52 | * @param offset 12N - the PIN offset 53 | */ 54 | case class GenerateIBMPinOffsetResponse(offset: String) extends HsmResponse { 55 | val errorCode = "00" 56 | val responseCode = "DF" 57 | } 58 | 59 | object GenerateIBMPinOffsetResponse { 60 | def createResponse(req: GenerateIBMPinOffsetRequest): HsmResponse = { 61 | val pinBlock = DES.tripleDesDecrypt(LMK.lmkVariant("02-03", 0), req.pinUnderLmk) 62 | println(HexConverter.toHex(ByteString(pinBlock))) 63 | 64 | // pin block format is NPP..P000 where N is pin length and PP..P is the actual PIN 65 | def extractedPin = { 66 | val pinAsString = HexConverter.toHex(ByteString(pinBlock)) 67 | val pinLength = pinAsString.charAt(1) & 0xf 68 | pinAsString.substring(2, 2 + pinLength) 69 | } 70 | 71 | println("Extracted: " + extractedPin) 72 | 73 | val pvk = DES.tripleDesDecryptVariant(LMK.lmkVariant("14-15", 0), req.pvk) 74 | val validationBlock = IBMPinValidation.validationBlock(req.pinValidation, req.accountNumber) 75 | val encryptedValidation = DES.tripleDesEncrypt(pvk, HexConverter.fromHex(validationBlock).toArray) 76 | val naturalPin = IBMPinValidation.digitReplacement(encryptedValidation, req.decimalisation, req.checkLength) 77 | 78 | val offset = IBMPinValidation.deriveOffsetFromPin(naturalPin, extractedPin) 79 | GenerateIBMPinOffsetResponse(offset) 80 | } 81 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/GenerateMac.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.util.HexConverter 25 | import org.leachbj.hsmsim.crypto.DES 26 | import org.leachbj.hsmsim.crypto.LMK 27 | import akka.util.ByteString 28 | 29 | case class GenerateMacRequest(blockNumber: Int, keyType: Int, keyLength: Int, macKey: Array[Byte], iv: Option[Array[Byte]], message: Array[Byte]) extends HsmRequest 30 | 31 | case class GenerateMacResponse(errorCode: String, mac: Array[Byte]) extends HsmResponse { 32 | val responseCode = "MT" 33 | } 34 | 35 | object GenerateMacResponse { 36 | private val (onlyBlock, firstBlock, middleBlock, lastBlock) = (0, 1, 2, 3) 37 | private val (takKeyType, zakKeyType) = (0, 1) 38 | private val (singleKeyLen, doubleKeyLen) = (0, 1) 39 | private val (binaryMessage, hexMessage) = (0, 1) 40 | 41 | def createResponse(req: GenerateMacRequest): HsmResponse = { 42 | if (req.blockNumber != onlyBlock) return ErrorResponse("MT", "05") 43 | if (req.keyType != takKeyType && req.keyType != zakKeyType) return ErrorResponse("MT", "04") 44 | if (req.keyLength != doubleKeyLen) return ErrorResponse("MT", "06") 45 | 46 | val macKey = req.keyType match { 47 | case `takKeyType` => 48 | DES.tripleDesDecryptVariant(LMK.lmkVariant("16-17", 0), req.macKey) 49 | case `zakKeyType` => 50 | DES.tripleDesDecryptVariant(LMK.lmkVariant("26-27", 0), req.macKey) 51 | } 52 | 53 | println("mac key: " + HexConverter.toHex(ByteString(macKey))) 54 | if (!DES.isParityAdjusted(macKey)) return ErrorResponse("MT", "10") 55 | 56 | GenerateMacResponse("00", DES.mac(macKey, req.message)) 57 | } 58 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/GenerateRSAKeySet.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import java.security.KeyPairGenerator 25 | import java.security.interfaces.RSAPrivateCrtKey 26 | import java.security.interfaces.RSAPublicKey 27 | import java.security.spec.RSAKeyGenParameterSpec 28 | import org.bouncycastle.asn1.x509.RSAPublicKeyStructure 29 | import org.leachbj.hsmsim.crypto.LMK 30 | import org.leachbj.hsmsim.util.HexConverter 31 | import akka.util.ByteString 32 | import javax.crypto.Cipher 33 | import javax.crypto.SecretKeyFactory 34 | import javax.crypto.spec.DESKeySpec 35 | import javax.crypto.spec.IvParameterSpec 36 | import scala.math.BigInt._ 37 | import java.math.BigInteger 38 | 39 | case class GenerateRSAKeySetRequest(keyType: Int, keyLength: Int, publicKeyEncoding: Int, publicExponent: Int) extends HsmRequest 40 | 41 | case class GenerateRSAKeySetResponse(publicKey: Array[Byte], privateKey: Array[Byte]) extends HsmResponse { 42 | val errorCode = "00" 43 | val responseCode = "SB" 44 | } 45 | 46 | object GenerateRSAKeySetResponse { 47 | def createResponse(req: GenerateRSAKeySetRequest): HsmResponse = { 48 | val flags = req.keyType.toByte 49 | 50 | val (pub, priv) = generateRsaKeyPair(req.keyLength, BigInteger.valueOf(req.publicExponent)) 51 | val pubEncoded = encodePublicKey(pub) 52 | val privEncoded = encodePrivateKey(flags, priv) 53 | 54 | GenerateRSAKeySetResponse(pubEncoded, privEncoded) 55 | } 56 | 57 | private def generateRsaKeyPair(keyLength: Int, modulus: BigInteger) = { 58 | val keyPairGenerator = KeyPairGenerator.getInstance("RSA") 59 | keyPairGenerator.initialize(new RSAKeyGenParameterSpec(keyLength, modulus)) 60 | val keyPair = keyPairGenerator.generateKeyPair() 61 | (keyPair.getPublic.asInstanceOf[RSAPublicKey], keyPair.getPrivate.asInstanceOf[RSAPrivateCrtKey]) 62 | } 63 | 64 | private def encodePublicKey(pubKey: RSAPublicKey) = { 65 | println(pubKey.getFormat) 66 | 67 | new RSAPublicKeyStructure(pubKey.getModulus(), pubKey.getPublicExponent()).getEncoded() 68 | } 69 | 70 | private def encodePrivateKey(flags: Byte, privKey: RSAPrivateCrtKey) = { 71 | def ceil(x: Int): Int = { 72 | (Math.ceil(x / 24d)).asInstanceOf[Int] * 3 73 | } 74 | 75 | def padTo8(data: Array[Byte]) = data.padTo((data.length + 8 - 1) / 8 * 8, 0.toByte) 76 | 77 | def macEncodedKey(encodedKey: Array[Byte]) = { 78 | val skf = SecretKeyFactory.getInstance("DES") 79 | val key = LMK.lmk35.clone 80 | key(0) = (key(0) ^ 0xa6).toByte // variant(0) 81 | val lmk35Variant0 = skf.generateSecret(new DESKeySpec(key)) 82 | val cipher = Cipher.getInstance("DES/CBC/NoPadding") 83 | val iv = new IvParameterSpec(new Array[Byte](8)) 84 | 85 | // pad it out to a multiple of 8 with zeroes 86 | val dataToMac = padTo8(encodedKey) 87 | 88 | println(HexConverter.toHex(ByteString(dataToMac))) 89 | cipher.init(Cipher.ENCRYPT_MODE, lmk35Variant0, iv) 90 | cipher.doFinal(dataToMac).takeRight(8).take(4) 91 | } 92 | 93 | val bitLength = privKey.getModulus().bitLength() 94 | println("BitLength: " + bitLength) 95 | 96 | val components = Array(privKey.getPrimeP(), privKey.getPrimeQ(), privKey.getPrimeExponentP(), privKey.getPrimeExponentQ(), privKey.getCrtCoefficient()) 97 | val header = Array(flags, 0.toByte, ((bitLength / 256) & 0xff).toByte, (bitLength & 0xff).toByte) 98 | val componentLen = ceil(bitLength / 2) 99 | val encodedKey = components.map(comp => new Array[Byte](componentLen) ++ comp.toByteArray takeRight (componentLen)).flatten 100 | val mac = macEncodedKey(header ++ encodedKey) 101 | encryptKey(padTo8(header ++ encodedKey ++ mac)) 102 | } 103 | 104 | def encryptKey(secretKey: Array[Byte]) = { 105 | def reverse(input: Array[Byte]) = { 106 | require(input.length % 8 == 0, "Input length must be a multiple of 8.") 107 | 108 | // reverse the blocks of the input so that first block is last and last block is first 109 | input.grouped(8).toArray.reverse.flatten.toArray 110 | } 111 | 112 | val skf = SecretKeyFactory.getInstance("DES") 113 | val skey34 = skf.generateSecret(new DESKeySpec(LMK.lmk34)) 114 | val skey35 = skf.generateSecret(new DESKeySpec(LMK.lmk35)) 115 | val cipher = Cipher.getInstance("DES/CBC/NoPadding") 116 | val zeroIV = new IvParameterSpec(new Array[Byte](8)) 117 | 118 | // decrypt with LMK 34 left to right 119 | cipher.init(Cipher.ENCRYPT_MODE, skey34, zeroIV) 120 | val step6 = cipher.doFinal(secretKey) 121 | 122 | // encrypt with LMK 35 right to left (and reverse the result) 123 | cipher.init(Cipher.DECRYPT_MODE, skey35, zeroIV) 124 | val step7 = reverse(cipher.doFinal(reverse(step6))) 125 | 126 | // decrypt with LMK 34 left to right 127 | cipher.init(Cipher.ENCRYPT_MODE, skey34, zeroIV) 128 | cipher.doFinal(step7) 129 | } 130 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/GenerateZpk.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.crypto.LMK 25 | import org.leachbj.hsmsim.crypto.DES 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import akka.util.ByteString 28 | import java.security.SecureRandom 29 | 30 | case class GenerateZpkRequest(zmk: Array[Byte], isAtallaVariant: Boolean, keySchemeZmk: Byte, keySchemeLmk: Byte, keyCheckType: Byte) extends HsmRequest 31 | 32 | case class GenerateZpkResponse(errorCode: String, zpkZmk: Array[Byte], zpkLmk: Array[Byte], checkValue: Array[Byte]) extends HsmResponse { 33 | val responseCode = "IB" 34 | } 35 | 36 | object GenerateZpkResponse { 37 | def createResponse(req: GenerateZpkRequest): HsmResponse = { 38 | val zmk = DES.tripleDesDecryptVariant(LMK.lmkVariant("04-05", 0), req.zmk) 39 | val zpk = generateZpk 40 | val zpkUnderZmk = DES.tripleDesEncrypt(zmk, zpk) 41 | val zpkUnderLmk = DES.tripleDesEncryptVariant(LMK.lmkVariant("06-07", 0), zpk) 42 | val checkValue = DES.calculateCheckValue(zpk).take(3) 43 | GenerateZpkResponse("00", zpkUnderZmk, zpkUnderLmk, checkValue) 44 | } 45 | 46 | private def generateZpk = { 47 | val zpk = new Array[Byte](16) 48 | generator.nextBytes(zpk) 49 | DES.adjustParity(zpk) 50 | } 51 | 52 | private val generator = new SecureRandom 53 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/HsmMessageEncoding.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */package org.leachbj.hsmsim.commands 22 | 23 | import org.leachbj.hsmsim.util.HexConverter 24 | import akka.util.ByteString 25 | 26 | trait HsmRequest 27 | 28 | trait HsmResponse { 29 | val responseCode: String 30 | val errorCode: String 31 | } 32 | 33 | case class UnknownHsmRequest(cmd: String) extends HsmRequest 34 | 35 | case class ErrorResponse(responseCode: String, errorCode: String) extends HsmResponse 36 | 37 | case class NcHsmResponse(responseCode: String, errorCode: String, lmkCheckValue: String, firmwareVersion: String) extends HsmResponse 38 | 39 | case class GenerateRandomPinRequest(accountNumber: String, pinLength: Int) extends HsmRequest 40 | case class GenerateRandomPinResponse(errorCode: String, pin: String) extends HsmResponse { 41 | val responseCode = "JB" 42 | } 43 | 44 | object HsmMessageEncoding { 45 | def decode(bs: ByteString): HsmRequest = { 46 | val iter = bs.iterator 47 | 48 | def readStringAsBytes(length: Int) = { 49 | val bytes = new Array[Byte](length) 50 | iter getBytes bytes 51 | bytes 52 | } 53 | 54 | def readString(length: Int) = ByteString(readStringAsBytes(length)).utf8String 55 | 56 | def readLengthBytes = { 57 | val length = readNumeric(4) 58 | val bytes = new Array[Byte](length) 59 | iter getBytes bytes 60 | bytes 61 | } 62 | 63 | def readLmkType = readString(4) 64 | 65 | def readKey = { 66 | iter.head match { 67 | case 'U' => // double 68 | iter.drop(1) 69 | readHex(32) 70 | case 'X' => // double X9.17 71 | iter.drop(1) 72 | readHex(32) 73 | case 'T' => // triple 74 | iter.drop(1) 75 | readHex(48) 76 | case 'Y' => // double X9.17 77 | iter.drop(1) 78 | readHex(48) 79 | case _ => 80 | readHex(16) 81 | } 82 | } 83 | 84 | def readNumeric(len: Int) = readString(len).toInt 85 | 86 | def readHexNumeric(len: Int) = Integer.parseInt(readString(len), 16) 87 | 88 | def readHex(len: Int) = HexConverter.fromHex(readString(len)) 89 | 90 | iter.drop(4) // skip header 91 | val cmd = readString(2) 92 | println("got cmd: " + cmd) 93 | cmd match { 94 | case "CC" => 95 | val sourceZpk = readKey.toArray 96 | val destZpk = readKey.toArray 97 | val maxPinLength = readNumeric(2) 98 | val sourcePinBlock = readHex(16).toArray 99 | val sourcePinBlockFormat = readString(2) 100 | val destinationPinBlockFormat = readString(2) 101 | val accountNumber = readString(12) 102 | TranslatePinZpkToAnotherRequest(sourceZpk, destZpk, maxPinLength, sourcePinBlock, sourcePinBlockFormat, destinationPinBlockFormat, accountNumber) 103 | case "DE" => 104 | val pvk = readKey.toArray 105 | println(HexConverter.toHex(ByteString(pvk))) 106 | val pin = readHex(16).toArray // TODO this is the LMK encrypted PIN, should be LN or LH 107 | println(HexConverter.toHex(ByteString(pin))) 108 | val minPinLength = readNumeric(2) 109 | val accountNumber = readString(12) 110 | val decimalisation = readStringAsBytes(16) 111 | val pinValidationData = readString(12) 112 | GenerateIBMPinOffsetRequest(pvk, pin, minPinLength, accountNumber, decimalisation, pinValidationData) 113 | case "EA" => 114 | val zpk = readKey.toArray 115 | val pvk = readKey.toArray 116 | val maxPinLength = readNumeric(2) 117 | val pinBlock = readHex(16).toArray 118 | val pinBlockFormat = readString(2) 119 | val checkLength = readNumeric(2) 120 | val accountNumber = readString(12) 121 | val decimalisation = readStringAsBytes(16) 122 | val pinValidationData = readString(12) 123 | val offset = readString(12) 124 | VerifyInterchangePinIBMRequest(zpk, pvk, pinBlock, pinBlockFormat, checkLength, accountNumber, decimalisation, pinValidationData, offset) 125 | case "FA" => 126 | val zmk = readKey.toArray 127 | val zpk = readKey.toArray 128 | val delOrAtalla = iter.head 129 | val atalla = if (delOrAtalla != ';') readNumeric(1) == 1 else false 130 | val delimiter = if ((delOrAtalla == ';' || atalla) && iter.hasNext) iter.getByte == ';' else false 131 | val (keySchemeZmk, keySchemeLmk, checkValueType) = if (delimiter) (iter.getByte, iter.getByte, iter.getByte) else ('0'.toByte, '0'.toByte, '0'.toByte) 132 | TranslateZpkFromZmkToLmkRequest(zmk, zpk, atalla, keySchemeZmk, keySchemeLmk, checkValueType) 133 | case "IA" => 134 | val zmk = readKey.toArray 135 | val delOrAtalla = iter.head 136 | val atalla = if (delOrAtalla != ';') readNumeric(1) == 1 else false 137 | val delimiter = if ((delOrAtalla == ';' || atalla) && iter.hasNext) iter.getByte == ';' else false 138 | val (keySchemeZmk, keySchemeLmk, checkValueType) = if (delimiter) (iter.getByte, iter.getByte, iter.getByte) else ('0'.toByte, '0'.toByte, '0'.toByte) 139 | GenerateZpkRequest(zmk, atalla, keySchemeZmk, keySchemeLmk, checkValueType) 140 | case "GI" => 141 | val encryptionIdentifier = readString(2) 142 | val padModeIdentifier = readString(2) 143 | val desKeyType = readLmkType 144 | val deskey = readLengthBytes 145 | val delimiter = iter.getByte 146 | val secretKeyFlag = readString(2) 147 | val secretKey = readLengthBytes 148 | val delimiter2 = iter.getByte 149 | val keySchemeZmk = iter.getByte 150 | val keySchemeLmk = iter.getByte 151 | ImportDesKeyRequest(deskey, secretKey, keySchemeLmk) 152 | case "JE" => 153 | val zpk = readKey.toArray 154 | val pinBlock = readHex(16).toArray 155 | val pinBlockFormat = readString(2) 156 | val accountNumber = readString(12) 157 | TranslatePinZpkToLmkRequest(zpk, pinBlock, pinBlockFormat, accountNumber) 158 | case "JA" => 159 | GenerateRandomPinRequest(readString(12), readString(2).toInt) 160 | case "MS" => 161 | val messageBlockNumber = readNumeric(1) 162 | val keyType = readNumeric(1) 163 | val keyLength = readNumeric(1) 164 | val messageType = readNumeric(1) 165 | val macKey = readKey.toArray 166 | val iv = if (messageBlockNumber == 2 || messageBlockNumber == 3) Some(readHex(16).toArray) else None 167 | val messageLen = readHexNumeric(4) 168 | val message = if (messageType == 0) readStringAsBytes(messageLen) else readHex(messageLen * 2).toArray 169 | GenerateMacRequest(messageBlockNumber, keyType, keyLength, macKey, iv, message) 170 | case "SA" => 171 | val keyType = readNumeric(1) 172 | val keyLength = readNumeric(4) 173 | val publicKeyEncoding = readNumeric(2) 174 | val publicExponentLength = Math.ceil(readNumeric(4) / 8.toDouble).toInt // length is supplied in bits 175 | 176 | val publicExponent = readStringAsBytes(publicExponentLength) 177 | val exp = BigInt(1, publicExponent).toInt 178 | GenerateRSAKeySetRequest(keyType, keyLength, publicKeyEncoding, exp) 179 | case _ => 180 | UnknownHsmRequest(cmd) 181 | } 182 | } 183 | 184 | val messageHeader = " " 185 | 186 | def encode(msg: HsmResponse) = { 187 | val bs = ByteString.newBuilder 188 | 189 | def writeHex(b: ByteString) = bs ++= ByteString(HexConverter.toHex(b)) 190 | 191 | def writeKey(key: ByteString) = { 192 | bs putByte 'U' 193 | writeHex(key) 194 | } 195 | 196 | def writeIntLen(len: Int, v: Int) = bs ++= ByteString(Array.fill(len)('0').mkString + f"$v%d" takeRight(len)) 197 | 198 | def writeInt(v: Int) = bs ++= ByteString(f"$v%02d") 199 | 200 | def padString(s: String, len: Int, pad: Char) = s ++ Array.fill(len - s.length)(pad) 201 | 202 | bs ++= ByteString(messageHeader) 203 | bs ++= ByteString(msg.responseCode) 204 | bs ++= ByteString(msg.errorCode) 205 | 206 | msg match { 207 | case i: ImportDesKeyResponse => 208 | writeKey(ByteString(i.desKey)) 209 | writeHex(ByteString(i.keyCheckValue)) 210 | bs ++= ByteString("000000000000") 211 | case g: GenerateRandomPinResponse => 212 | bs ++= ByteString(g.pin) 213 | case n: NcHsmResponse => 214 | bs ++= ByteString(n.lmkCheckValue) 215 | bs ++= ByteString(n.firmwareVersion) 216 | case t: TranslatePinZpkToAnotherResponse => 217 | writeInt(t.pinLength) 218 | writeHex(ByteString(t.pinBlock)) 219 | bs ++= ByteString(t.pinBlockFormat) 220 | case translateLmk: TranslatePinZpkToLmkResponse => 221 | writeHex(ByteString(translateLmk.pin)) // length should be LN or LH depending on HSM configuration 222 | case translateZpk: TranslateZpkFromZmkToLmkResponse => 223 | bs += 'U' 224 | writeHex(ByteString(translateZpk.zpk)) 225 | writeHex(ByteString(translateZpk.checkValue)) 226 | bs ++= ByteString("0000000000") 227 | case generateZpk: GenerateZpkResponse => 228 | bs += 'X' 229 | writeHex(ByteString(generateZpk.zpkZmk)) 230 | bs += 'U' 231 | writeHex(ByteString(generateZpk.zpkLmk)) 232 | writeHex(ByteString(generateZpk.checkValue)) 233 | bs ++= ByteString("0000000000") 234 | case generateOffset: GenerateIBMPinOffsetResponse => 235 | bs ++= ByteString(padString(generateOffset.offset, 12, 'F')) 236 | case generateMac: GenerateMacResponse => 237 | writeHex(ByteString(generateMac.mac)) 238 | case generateRsa: GenerateRSAKeySetResponse => 239 | bs ++= ByteString(generateRsa.publicKey) 240 | writeIntLen(4, generateRsa.privateKey.length) 241 | bs ++= ByteString(generateRsa.privateKey) 242 | case _ => 243 | } 244 | 245 | bs.result 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/ImportDesKey.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import java.lang.Math 25 | import java.security.KeyFactory 26 | import java.security.interfaces.RSAPrivateKey 27 | import java.security.spec.RSAPrivateCrtKeySpec 28 | 29 | import scala.math.BigInt.int2bigInt 30 | 31 | import org.leachbj.hsmsim.crypto.DES 32 | import org.leachbj.hsmsim.crypto.LMK 33 | import org.leachbj.hsmsim.util.HexConverter 34 | 35 | import akka.util.ByteString 36 | import javax.crypto.Cipher 37 | import javax.crypto.SecretKeyFactory 38 | import javax.crypto.spec.DESKeySpec 39 | import javax.crypto.spec.IvParameterSpec 40 | 41 | case class ImportDesKeyRequest(desKey: Array[Byte], rsaPrivKey: Array[Byte], keySchemeLmk: Byte) extends HsmRequest { 42 | override def toString() = { 43 | "ImportDesKeyRequest(" + HexConverter.toHex(ByteString(desKey)) + ", " + HexConverter.toHex(ByteString(rsaPrivKey)) + "," + keySchemeLmk + ")" 44 | } 45 | } 46 | case class ImportDesKeyResponse(errorCode: String, desKey: Array[Byte], keyCheckValue: Array[Byte]) extends HsmResponse { 47 | val responseCode = "GJ" 48 | } 49 | object ImportDesKeyResponse { 50 | private def ceil(x: Int): Int = { 51 | (Math.ceil(x / 24d)).asInstanceOf[Int] * 3 52 | } 53 | 54 | def createResponse(req: ImportDesKeyRequest): HsmResponse = { 55 | def decryptHsmPrivateKey = { 56 | def reverse(input: Array[Byte]) = { 57 | require(input.length % 8 == 0, "Input length must be a multiple of 8.") 58 | 59 | // reverse the blocks of the input so that first block is last and last block is first 60 | input.grouped(8).toArray.reverse.flatten.toArray 61 | } 62 | 63 | val skf = SecretKeyFactory.getInstance("DES") 64 | val skey34 = skf.generateSecret(new DESKeySpec(LMK.lmk34)) 65 | val skey35 = skf.generateSecret(new DESKeySpec(LMK.lmk35)) 66 | val cipher = Cipher.getInstance("DES/CBC/NoPadding") 67 | val zeroIV = new IvParameterSpec(new Array[Byte](8)) 68 | 69 | // decrypt with LMK 34 left to right 70 | cipher.init(Cipher.DECRYPT_MODE, skey34, zeroIV) 71 | val step6 = cipher.doFinal(req.rsaPrivKey) 72 | 73 | // encrypt with LMK 35 right to left (and reverse the result) 74 | cipher.init(Cipher.ENCRYPT_MODE, skey35, zeroIV) 75 | val step7 = reverse(cipher.doFinal(reverse(step6))) 76 | 77 | // decrypt with LMK 34 left to right 78 | cipher.init(Cipher.DECRYPT_MODE, skey34, zeroIV) 79 | cipher.doFinal(step7) 80 | } 81 | 82 | def createRsaKey = { 83 | val decryptedKey: Array[Byte] = decryptHsmPrivateKey 84 | 85 | println(HexConverter.toHex(ByteString(decryptedKey))) 86 | 87 | val bitLen = (decryptedKey(2) & 0xff) * 256 + (decryptedKey(3) & 0xff) 88 | 89 | println("bitLen = " + bitLen) 90 | 91 | def getComponents = { 92 | val padLen = ceil(bitLen / 2) 93 | 94 | println("padLen = " + padLen) 95 | // skip the 4 byte header and then convert each padLen bytes into a BigInt 96 | val components = decryptedKey.drop(4).grouped(padLen).map(comp => BigInt(1, comp)).toArray 97 | 98 | (components(0), components(1), components(2), components(3), components(4)) 99 | } 100 | 101 | // 4 byte header plus the 5 components 102 | val data = decryptedKey.take(4 + 5 * ceil(bitLen / 2)) 103 | println(data.length) 104 | println((data.length + 8 - 1) / 8 * 8) 105 | 106 | // zero pad 107 | // pad it out to a multiple of 8 with zeroes 108 | val dataToMac = data.padTo((data.length + 8 - 1) / 8 * 8, 0.toByte) 109 | 110 | { 111 | val skf = SecretKeyFactory.getInstance("DES") 112 | val key = LMK.lmk35.clone 113 | key(0) = (key(0) ^ 0xa6).toByte // variant(0) 114 | val lmk35 = skf.generateSecret(new DESKeySpec(key)) 115 | val cipher = Cipher.getInstance("DES/CBC/NoPadding") 116 | val iv = new IvParameterSpec(new Array[Byte](8)) 117 | 118 | println(HexConverter.toHex(ByteString(dataToMac))) 119 | cipher.init(Cipher.ENCRYPT_MODE, lmk35, iv) 120 | val mac = cipher.doFinal(dataToMac).takeRight(8).take(4) 121 | println("mac: " + HexConverter.toHex(ByteString(mac))) 122 | println("org: " + HexConverter.toHex(ByteString(decryptedKey.drop(data.length).take(4)))) 123 | 124 | // TODO need to refactor to be able to do this 125 | // if (mac.deep != decryptedKey.drop(data.length).take(4).deep) return ErrorResponse("GJ", "84") 126 | assert(mac.deep == decryptedKey.drop(data.length).take(4).deep, "Invalid mac on decrypted key") 127 | } 128 | 129 | val (p, q, d1, d2, crt) = getComponents 130 | val mod = p * q 131 | val e = d1 modInverse (p - 1) 132 | List(p, q, d1, d2, crt, mod, e).foreach(b => println(b.toString(16))) 133 | val d = e modInverse ((p - 1) * (q - 1)) 134 | val rsaPrivateKeySpec = new RSAPrivateCrtKeySpec(mod.bigInteger, e.bigInteger, d.bigInteger, p.bigInteger, q.bigInteger, d1.bigInteger, d2.bigInteger, crt.bigInteger) 135 | 136 | KeyFactory.getInstance("RSA").generatePrivate(rsaPrivateKeySpec).asInstanceOf[RSAPrivateKey] 137 | } 138 | 139 | val key = createRsaKey 140 | 141 | println(req.desKey.length + " " + (key.getModulus().bitLength() / 8)) 142 | 143 | if (req.desKey.length > key.getModulus().bitLength() / 8) { 144 | println("Imported des key too long") 145 | return ErrorResponse("GJ", "76") 146 | } 147 | 148 | def decryptDesKey = { 149 | val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") 150 | cipher.init(Cipher.DECRYPT_MODE, key) 151 | try { 152 | Some(cipher.doFinal(req.desKey)) 153 | } catch { 154 | case e: Exception => 155 | e.printStackTrace() 156 | None 157 | } 158 | } 159 | 160 | val d = decryptDesKey 161 | if (d.isEmpty) return ErrorResponse("GJ", "15") 162 | val decryptedDesKeyNoParity = d.get 163 | 164 | println("deskey: " + HexConverter.toHex(ByteString(decryptedDesKeyNoParity))) 165 | //req.keySchemeLmk is 'T' 166 | 167 | val decryptedDesKey = DES.adjustParity(decryptedDesKeyNoParity) 168 | println("deskey: " + HexConverter.toHex(ByteString(decryptedDesKey))) 169 | 170 | ImportDesKeyResponse("00", DES.tripleDesEncryptVariant(LMK.lmkVariant("06-07", 0), decryptedDesKey), DES.calculateCheckValue(decryptedDesKey).take(6)) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/TranslatePinZpkToAnother.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.crypto.DES 25 | import org.leachbj.hsmsim.crypto.LMK 26 | import org.leachbj.hsmsim.util.HexConverter 27 | 28 | import akka.util.ByteString 29 | 30 | case class TranslatePinZpkToAnotherRequest(sourceZpk: Array[Byte], destZpk: Array[Byte], maxPinLength: Int, sourcePinBlock: Array[Byte], sourcePinBlockFormat: String, destinationPinBlockFormat: String, accountNumber: String) extends HsmRequest 31 | case class TranslatePinZpkToAnotherResponse(errorCode: String, pinLength: Int, pinBlock: Array[Byte], pinBlockFormat: String) extends HsmResponse { 32 | val responseCode = "CD" 33 | } 34 | 35 | object TranslatePinZpkToAnotherResponse { 36 | def createResponse(req: TranslatePinZpkToAnotherRequest): HsmResponse = { 37 | if (req.sourcePinBlockFormat != "05") return ErrorResponse("CD", "23") 38 | if (req.destinationPinBlockFormat != "01") return ErrorResponse("CD", "23") 39 | 40 | def decrypt(pin: Array[Byte]) = { 41 | val key = LMK.lmkVariant("06-07", 0) 42 | 43 | val sourceZpk = DES.tripleDesDecryptVariant(key, req.sourceZpk) 44 | val plainPinBlock = DES.tripleDesDecrypt(sourceZpk, pin) 45 | println(HexConverter.toHex(ByteString(plainPinBlock))) 46 | 47 | plainPinBlock 48 | } 49 | 50 | def encrypt(pin: Array[Byte]) = { 51 | val key = LMK.lmkVariant("06-07", 0) 52 | 53 | val sourceZpk = DES.tripleDesDecryptVariant(key, req.destZpk) 54 | val plainPinBlock = DES.tripleDesEncrypt(sourceZpk, pin) 55 | println("plain pin: " + HexConverter.toHex(ByteString(plainPinBlock))) 56 | 57 | plainPinBlock 58 | } 59 | 60 | def convertPinBlock(pin: Array[Byte], account: String): (Int, Array[Byte]) = { 61 | var pinAsString = HexConverter.toHex(ByteString(pin)).toCharArray 62 | val pinLength = pin(0) & 0xf 63 | 64 | // pin block format 01 - ISO95641 - format 0 => '0' || pin length || pin || padding with 'F' then xor with account number 65 | pinAsString(0) = '0' 66 | 67 | val paddedAccount = " " + account; 68 | for (i <- 2 + pinLength until pinAsString.length) { 69 | pinAsString(i) = "%X".format(Integer.parseInt("F", 16) ^ Integer.parseInt("" + paddedAccount.charAt(i), 16)).charAt(0) 70 | } 71 | 72 | 73 | println("convertPinBlock: " + new String(pinAsString)) 74 | 75 | // pin block format 05 - ISO 956401 Format 1 => '1' || pin length || pin || random padding 76 | 77 | // pinAsString = "0514789FFEFFFC89".toCharArray 78 | 79 | println(new String(pinAsString)) 80 | 81 | (pinLength, HexConverter.fromHex(new String(pinAsString)).toArray) 82 | } 83 | 84 | val oldPinBlock = decrypt(req.sourcePinBlock) 85 | val (pinLength, newPinBlock) = convertPinBlock(oldPinBlock, req.accountNumber) 86 | val encryptedNewPinBlock = encrypt(newPinBlock) 87 | TranslatePinZpkToAnotherResponse("00", pinLength, encryptedNewPinBlock, req.destinationPinBlockFormat) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/TranslatePinZpkToLmk.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.crypto.DES 25 | import org.leachbj.hsmsim.crypto.LMK 26 | import org.leachbj.hsmsim.util.HexConverter 27 | 28 | import akka.util.ByteString 29 | 30 | case class TranslatePinZpkToLmkRequest(zpk: Array[Byte], pinBlock: Array[Byte], pinBlockFormat: String, accountNumber: String) extends HsmRequest 31 | case class TranslatePinZpkToLmkResponse(errorCode: String, pin: Array[Byte]) extends HsmResponse { 32 | val responseCode = "JF" 33 | } 34 | object TranslatePinZpkToLmkResponse { 35 | def createResponse(req: TranslatePinZpkToLmkRequest): HsmResponse = { 36 | if (req.pinBlockFormat != "05") return ErrorResponse("JF", "23") 37 | 38 | val zpk = DES.tripleDesDecryptVariant(LMK.lmkVariant("06-07", 0), req.zpk) 39 | val plainPinBlock = DES.tripleDesDecrypt(zpk, req.pinBlock) 40 | println("plainPinBlock: " + HexConverter.toHex(ByteString(plainPinBlock))) 41 | 42 | val pinBlock = DES.tripleDesEncrypt(LMK.lmkVariant("02-03", 0), plainPinBlock) 43 | 44 | 45 | // val t = DES.tripleDesEncrypt(LMK.lmkVariant("02-03", 0), HexConverter.fromHex("6629123327410").toArray) 46 | // println(HexConverter.toHex(ByteString(t))) 47 | 48 | println("encryptedPinBlock: " + HexConverter.toHex(ByteString(pinBlock))) 49 | TranslatePinZpkToLmkResponse("00", pinBlock) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/TranslateZpkFromZmkToLmk.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.crypto.LMK 25 | import org.leachbj.hsmsim.crypto.DES 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import akka.util.ByteString 28 | 29 | case class TranslateZpkFromZmkToLmkRequest(zmk: Array[Byte], zpk: Array[Byte], isAtallaVariant: Boolean, keySchemeZmk: Byte, keySchemeLmk: Byte, keyCheckType: Byte) extends HsmRequest 30 | 31 | case class TranslateZpkFromZmkToLmkResponse(errorCode: String, zpk: Array[Byte], checkValue: Array[Byte]) extends HsmResponse { 32 | val responseCode = "FB" 33 | } 34 | 35 | object TranslateZpkFromZmkToLmkResponse { 36 | def createResponse(req: TranslateZpkFromZmkToLmkRequest): HsmResponse = { 37 | val zmk = DES.tripleDesDecryptVariant(LMK.lmkVariant("04-05", 0), req.zmk) 38 | val zpk = DES.tripleDesDecrypt(zmk, req.zpk) 39 | val adjustedZpk = DES.adjustParity(zpk) 40 | println("zmk: " + HexConverter.toHex(ByteString(zmk))) 41 | val zpkUnderLmk = DES.tripleDesEncryptVariant(LMK.lmkVariant("06-07", 0), adjustedZpk) 42 | val checkValue = DES.calculateCheckValue(adjustedZpk).take(3) 43 | // return 01 if the parity of the ZPK was invalid 44 | val errorCode = if (zpk == adjustedZpk) "00" else "01" 45 | TranslateZpkFromZmkToLmkResponse(errorCode, zpkUnderLmk, checkValue) 46 | } 47 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/commands/VerifyInterchangePinIBM.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.leachbj.hsmsim.crypto.DES 25 | import org.leachbj.hsmsim.crypto.LMK 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import akka.util.ByteString 28 | import org.leachbj.hsmsim.crypto.IBMPinValidation 29 | 30 | case class VerifyInterchangePinIBMRequest(zpk: Array[Byte], pvk: Array[Byte], pinBlock: Array[Byte], pinBlockFormat: String, checkLength: Int, accountNumber: String, decimalisation: Array[Byte], pinValidation: String, offset: String) extends HsmRequest 31 | case class VerifyInterchangePinIBMResponse(errorCode:String) extends HsmResponse { 32 | val responseCode = "EB" 33 | } 34 | 35 | object VerifyInterchangePinIBMResponse { 36 | def createResponse(req: VerifyInterchangePinIBMRequest): HsmResponse = { 37 | val zpk = DES.tripleDesDecryptVariant(LMK.lmkVariant("06-07", 0), req.zpk) 38 | val plainPinBlock = DES.tripleDesDecrypt(zpk, req.pinBlock) 39 | println("plainPinBlock: " + HexConverter.toHex(ByteString(plainPinBlock))) 40 | 41 | val pvk = DES.tripleDesDecryptVariant(LMK.lmkVariant("14-15", 0), req.pvk) 42 | val validationBlock = IBMPinValidation.validationBlock(req.pinValidation, req.accountNumber) 43 | val encryptedValidation = DES.tripleDesEncrypt(pvk, HexConverter.fromHex(validationBlock).toArray) 44 | 45 | val naturalPin = IBMPinValidation.digitReplacement(encryptedValidation, req.decimalisation, req.checkLength) 46 | 47 | val derivedPin = IBMPinValidation.derivePinFromOffset(naturalPin, req.offset) 48 | println("derivedPin" + derivedPin) 49 | 50 | def extractedPin = { 51 | val pinAsString = HexConverter.toHex(ByteString(plainPinBlock)) 52 | val pinLength = pinAsString.charAt(1) & 0xf 53 | pinAsString.substring(2, 2 + pinLength) 54 | } 55 | 56 | println("Extracted: " + extractedPin) 57 | 58 | if (derivedPin == extractedPin) VerifyInterchangePinIBMResponse("00") 59 | else VerifyInterchangePinIBMResponse("01") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/crypto/DES.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.crypto 23 | 24 | import scala.Array.canBuildFrom 25 | 26 | import org.leachbj.hsmsim.util.HexConverter 27 | 28 | import akka.util.ByteString 29 | import javax.crypto.Cipher 30 | import javax.crypto.SecretKeyFactory 31 | import javax.crypto.spec.DESKeySpec 32 | import javax.crypto.spec.DESedeKeySpec 33 | import javax.crypto.spec.IvParameterSpec 34 | 35 | object DES { 36 | private def bitcount(c: Byte) = { 37 | val s = (c & 0x11) + ((c >> 1) & 0x11) + ((c >> 2) & 0x11) + ((c >> 3) & 0x11) 38 | (s & 15) + (s >> 4) 39 | } 40 | 41 | private def isOddBitCount(c: Byte) = bitcount(c) % 2 == 1 42 | 43 | def isParityAdjusted(key: Array[Byte]): Boolean = { 44 | key.forall(isOddBitCount) 45 | } 46 | 47 | def adjustParity(key: Array[Byte]): Array[Byte] = { 48 | def setparity(c: Byte) = 49 | if (isOddBitCount(c)) c 50 | else (c ^ 1).toByte 51 | 52 | key.map(setparity(_)) 53 | } 54 | 55 | private def applyVariant(key: Array[Byte], variant: Int) = { 56 | val result = key.clone 57 | result(8) = (result(8) ^ variant.toByte).toByte 58 | println("des_v: " + HexConverter.toHex(ByteString(result))) 59 | result 60 | } 61 | 62 | private def cipher(mode: Int, key: Array[Byte], data: Array[Byte]) = { 63 | // Sun DESede provider requires triple DES keys to be 24 bytes 64 | val k = if (key.length == 16) key ++ key.slice(0, 8) else key 65 | 66 | val skey = SecretKeyFactory.getInstance("DESede").generateSecret(new DESedeKeySpec(k)) 67 | val cipher = Cipher.getInstance("DESede/ECB/NoPadding") 68 | cipher.init(mode, skey) 69 | val result = cipher.doFinal(data) 70 | println("DES(k,v): " + HexConverter.toHex(ByteString(k)) + ", " + HexConverter.toHex(ByteString(data)) + " -> " + HexConverter.toHex(ByteString(result))) 71 | result 72 | } 73 | 74 | def tripleDesEncrypt(key: Array[Byte], data: Array[Byte]): Array[Byte] = cipher(Cipher.ENCRYPT_MODE, key, data) 75 | 76 | def tripleDesDecrypt(key: Array[Byte], data: Array[Byte]): Array[Byte] = cipher(Cipher.DECRYPT_MODE, key, data) 77 | 78 | private val doubleVariants = Array(0xa6, 0x5a) 79 | private val tripleVariants = Array(0x6a, 0xde, 0x2b) 80 | 81 | private type CryptoOp = (Array[Byte], Array[Byte]) => Array[Byte] 82 | 83 | private def tripleDesWithVariant(cryptoOp: CryptoOp, lmkKey: Array[Byte], key: Array[Byte]): Array[Byte] = { 84 | val variant = if (key.length == 24) tripleVariants else doubleVariants 85 | 86 | val d = for ((key, index) <- key.grouped(8).zipWithIndex) 87 | yield cryptoOp(applyVariant(lmkKey, variant(index)), key) 88 | 89 | d.flatten.toArray 90 | } 91 | 92 | def tripleDesEncryptVariant(lmkKey: Array[Byte], key: Array[Byte]): Array[Byte] = 93 | tripleDesWithVariant(tripleDesEncrypt, lmkKey, key) 94 | 95 | def tripleDesDecryptVariant(lmkKey: Array[Byte], key: Array[Byte]): Array[Byte] = 96 | tripleDesWithVariant(tripleDesDecrypt, lmkKey, key) 97 | 98 | def mac(key: Array[Byte], data: Array[Byte]) = { 99 | // CBC encrypt the data and discard all but the final block 100 | val skf = SecretKeyFactory.getInstance("DES") 101 | val cbcCipher = Cipher.getInstance("DES/CBC/NoPadding") 102 | val iv = new IvParameterSpec(new Array[Byte](8)) 103 | cbcCipher.init(Cipher.ENCRYPT_MODE, skf.generateSecret(new DESKeySpec(key.take(8))), iv) 104 | val lastBlock = cbcCipher.doFinal(data).takeRight(8) 105 | 106 | // now decrypt & encrypt the final block with the 2nd and 1st keys respectively 107 | val cipher = Cipher.getInstance("DES/ECB/NoPadding") 108 | cipher.init(Cipher.DECRYPT_MODE, skf.generateSecret(new DESKeySpec(key.takeRight(8)))) 109 | val lastBlockDec = cipher.doFinal(lastBlock) 110 | cipher.init(Cipher.ENCRYPT_MODE, skf.generateSecret(new DESKeySpec(key.take(8)))) 111 | cipher.doFinal(lastBlockDec) 112 | } 113 | 114 | def calculateCheckValue(key: Array[Byte]) = tripleDesEncrypt(key, new Array[Byte](8)).take(6) 115 | } 116 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/crypto/IBMPinValidation.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.crypto 23 | 24 | import org.leachbj.hsmsim.util.HexConverter 25 | import akka.util.ByteString 26 | import java.lang.Math.abs 27 | 28 | object IBMPinValidation { 29 | // validation block is a PREFIX|N|POSTFIX where N is replaced by last 5 digits of account number 30 | def validationBlock(pinValidation: String, accountNumber: String): String = { 31 | val Account = "(.*)N(.*)".r 32 | val Account(prefix, postfix) = pinValidation 33 | val validation = prefix + accountNumber.takeRight(5) + postfix 34 | println("validationBlock: " + validation) 35 | validation 36 | } 37 | 38 | def digitReplacement(encryptedValidation: Array[Byte], decimalisation: Array[Byte], pinLen: Int) = { 39 | val block = HexConverter.toHex(ByteString(encryptedValidation)) 40 | val decamlized = block.map(ch => 41 | if (ch >= '0' && ch <= '9') ch 42 | else decimalisation(ch - 'A')) 43 | println("applyDecimalization: " + decamlized) 44 | decamlized.take(pinLen) 45 | } 46 | 47 | private def base10(b: Int): Int = { 48 | b - '0' 49 | } 50 | 51 | def derivePinFromOffset(naturalPin: Seq[Int], offset: String) = { 52 | val d = for (i <- 0 until naturalPin.size) yield ((base10(naturalPin(i)) + base10(offset(i))) % 10) 53 | 54 | d.mkString 55 | } 56 | 57 | def deriveOffsetFromPin(naturalPin: Seq[Int], pin: String) = { 58 | val d = for (i <- 0 until naturalPin.size) yield (((base10(pin(i)) + 10) - base10(naturalPin(i))) % 10) 59 | 60 | d.mkString 61 | } 62 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/crypto/LMK.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.crypto 23 | 24 | import org.leachbj.hsmsim.util.HexConverter 25 | 26 | object LMK { 27 | val lmk02_03 = HexConverter.fromHex("20202020202020203131313131313131").toArray 28 | val lmk04_05 = HexConverter.fromHex("40404040404040405151515151515151").toArray 29 | val lmk06_07 = HexConverter.fromHex("61616161616161617070707070707070").toArray 30 | val lmk14_15 = HexConverter.fromHex("E0E0010101010101F1F1010101010101").toArray 31 | val lmk16_17 = HexConverter.fromHex("1C587F1C13924FEF0101010101010101").toArray 32 | val lmk26_27 = HexConverter.fromHex("16161616161616161919191919191919").toArray 33 | 34 | val lmk34 = HexConverter.fromHex("2A2A2A2A2A2A2A2A").toArray 35 | val lmk35 = HexConverter.fromHex("2C2C2C2C2C2C2C2C").toArray 36 | 37 | private val keys = Map("02-03" -> lmk02_03, "04-05" -> lmk04_05, "06-07" -> lmk06_07, "14-15" -> lmk14_15, "16-17" -> lmk16_17, "26-27" -> lmk26_27) 38 | private val lmkVariants = Array(0x0, 0xa6, 0x5a, 0x6a, 0xde, 0x2b, 0x50, 0x74, 0x9c) 39 | 40 | def lmkVariant(keyName: String, variant: Int) = { 41 | require(variant < 9) 42 | def applyLmkVariant(key: Array[Byte]) = { 43 | val result = key.clone 44 | result(0) = (result(0) ^ variant.toByte).toByte 45 | result 46 | } 47 | 48 | keys.get(keyName) match { 49 | case None => throw new RuntimeException(s"Unknown LMK $keyName") 50 | case Some(x) => applyLmkVariant(x) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /hsmsim-akka/src/main/scala/org/leachbj/hsmsim/util/HexConverter.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.util 23 | 24 | import scala.Array.canBuildFrom 25 | 26 | import akka.util.ByteString 27 | import akka.util.CompactByteString 28 | 29 | object HexConverter { 30 | def toHex(b: ByteString, sep: Option[String] = None): String = { 31 | val bytes = b.toArray 32 | sep match { 33 | case None => bytes.map("%02X".format(_)).mkString 34 | case _ => bytes.map("%02X".format(_)).mkString(sep.get) 35 | } 36 | } 37 | 38 | def fromHex(b: String): ByteString = { 39 | CompactByteString(b.replaceAll("[^0-9A-Fa-f]", "").sliding(2, 2).toArray.map(Integer.parseInt(_, 16).toByte)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/GenerateIBMPinOffsetSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.scalatest.FunSuite 25 | import scala.collection.immutable.Map 26 | import org.junit.runner.RunWith 27 | import org.scalatest.junit.JUnitRunner 28 | import org.leachbj.hsmsim.util.HexConverter 29 | 30 | @RunWith(classOf[JUnitRunner]) 31 | class GenerateIBMPinOffsetSuite extends FunSuite { 32 | test("generate pin offset test vector") { 33 | val pvk = HexConverter.fromHex("AF9958474101D950930D1FC86F99447E10B3BADFAA10458E").toArray 34 | // this is not the right value, see TranslatePinZpkToLmkSuite 35 | val encryptedPin = HexConverter.fromHex("24698C68CF4FA4F9").toArray 36 | val minLength = 5 37 | val accountNumber = "000001000376" 38 | val decimalisation = "0123456789012345".getBytes("UTF-8") 39 | val pinValidation = "1234567890NF" 40 | val r = GenerateIBMPinOffsetRequest(pvk, encryptedPin, minLength, accountNumber, decimalisation, pinValidation) 41 | val resp = GenerateIBMPinOffsetResponse.createResponse(r) 42 | assert(resp.errorCode === "00") 43 | assert(resp.responseCode === "DF") 44 | assert(resp.isInstanceOf[GenerateIBMPinOffsetResponse]) 45 | val generateResponse = resp.asInstanceOf[GenerateIBMPinOffsetResponse] 46 | assert(generateResponse.offset === "17702") 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/GenerateMacSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.scalatest.FunSuite 25 | import org.leachbj.hsmsim.util.HexConverter 26 | import org.junit.runner.RunWith 27 | import org.scalatest.junit.JUnitRunner 28 | import org.scalatest.matchers.ShouldMatchers 29 | 30 | @RunWith(classOf[JUnitRunner]) 31 | class GenerateMacSuite extends FunSuite with ShouldMatchers { 32 | test("generateMac with TAK") { 33 | val generateReq = GenerateMacRequest(0, 0, 1, HexConverter.fromHex("90A17A99E4BA32475E692D7AAE177BBD").toArray, 34 | None, "1234567887654321".getBytes) 35 | val resp = GenerateMacResponse.createResponse(generateReq) 36 | assert(resp.errorCode === "00") 37 | assert(resp.responseCode === "MT") 38 | assert(resp.isInstanceOf[GenerateMacResponse]) 39 | val generateResponse = resp.asInstanceOf[GenerateMacResponse] 40 | generateResponse.mac should be (HexConverter.fromHex("934F05263310B6D3").toArray) 41 | } 42 | 43 | test("generateMac with ZAK") { 44 | val generateReq = GenerateMacRequest(0, 1, 1, HexConverter.fromHex("2549A1834EFDDC9629CBD8DC5C982D75").toArray, 45 | None, "1234567887654321".getBytes) 46 | val resp = GenerateMacResponse.createResponse(generateReq) 47 | assert(resp.errorCode === "00") 48 | assert(resp.responseCode === "MT") 49 | assert(resp.isInstanceOf[GenerateMacResponse]) 50 | val generateResponse = resp.asInstanceOf[GenerateMacResponse] 51 | generateResponse.mac should be (HexConverter.fromHex("A1C911EBA50563C9").toArray) 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/GenerateRSAKeySetSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.scalatest.FunSuite 25 | import org.scalatest.junit.JUnitRunner 26 | import org.junit.runner.RunWith 27 | import org.leachbj.hsmsim.util.HexConverter 28 | import akka.util.ByteString 29 | import java.security.spec.X509EncodedKeySpec 30 | import java.security.KeyFactory 31 | import org.bouncycastle.util.encoders.Hex 32 | import javax.crypto.Cipher 33 | import java.io.ByteArrayInputStream 34 | import org.bouncycastle.asn1.ASN1InputStream 35 | import org.bouncycastle.asn1.x509.RSAPublicKeyStructure 36 | import java.security.spec.RSAPublicKeySpec 37 | 38 | @RunWith(classOf[JUnitRunner]) 39 | class GenerateRSAKeySetSuite extends FunSuite { 40 | test("should generate random key") { 41 | val req = GenerateRSAKeySetRequest(0, 512, 1, 0x1001) 42 | val resp = GenerateRSAKeySetResponse.createResponse(req); 43 | assert(resp.errorCode === "00") 44 | assert(resp.responseCode === "SB") 45 | assert(resp.isInstanceOf[GenerateRSAKeySetResponse]) 46 | val generateResponse = resp.asInstanceOf[GenerateRSAKeySetResponse] 47 | println(HexConverter.toHex(ByteString(generateResponse.publicKey))) 48 | println(HexConverter.toHex(ByteString(generateResponse.privateKey))) 49 | 50 | checkKey(generateResponse.publicKey, generateResponse.privateKey) 51 | } 52 | 53 | def checkKey(publicKey: Array[Byte], privateKey: Array[Byte]) = { 54 | val des = encryptKeyUnderRsa(publicKey, HexConverter.fromHex("7AF4D50EE6587A767AF4D50EE6587A76").toArray) 55 | val desImportReq = ImportDesKeyRequest(des, privateKey, 'T') 56 | val importDesKeyResponse = ImportDesKeyResponse.createResponse(desImportReq).asInstanceOf[ImportDesKeyResponse] 57 | assert(importDesKeyResponse.desKey === HexConverter.fromHex("0157015564C146EB90920C60CAB2E8F6").toArray) 58 | assert(importDesKeyResponse.keyCheckValue === HexConverter.fromHex("FD7EC34F674D").toArray) 59 | } 60 | 61 | private def encryptKeyUnderRsa(key: Array[Byte], data: Array[Byte]) = { 62 | val keyFactory = KeyFactory.getInstance("RSA"); 63 | val rsaKey = RSAPublicKeyStructure.getInstance(toDERObject(key)) 64 | val keySpec = new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent()); 65 | val publicKey = keyFactory.generatePublic(keySpec); 66 | val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 67 | 68 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 69 | cipher.doFinal(data); 70 | } 71 | 72 | private def toDERObject(data: Array[Byte]) = { 73 | def inStream = new ByteArrayInputStream(data); 74 | def asnInputStream = new ASN1InputStream(inStream); 75 | 76 | asnInputStream.readObject(); 77 | } 78 | // BitLength: 512 79 | //00F308DB9DECE7437E86C90E5744CED460BC9FE083C5A6F224B9ECB1B15D32475100D1DB72C4E2B31377327BA006D04711DDD3F7D72F5CF34168BD8F9495BE0170EB00C951BD1AC4F38D6572217A4FB01FE4AD7D92B2587592256366E03B11C131B501009E1E0D8C610EF254B639C56A3CD5BF9577175F0FF5A5E71BA6F01ED0F0779F7F0072C58FF99338EBC7A44E3E86465211FA52F6703586AFE50F2CDEC72DB321407C000000 80 | //305C300D06092A864886F70D0101010500034B003048024100C73A90D4AA4EC3E7324E2DF3CA7793C2BA6F60681A43E40419ACEE019166E1DA79D2410A5981A924962D4813DBC05147A68912035E1D6C9D819EBADBF1ABE75B0203010001 81 | //45BFE93B6D92739ADF44679ACAF9292D65330CFEA1C1C17BC037C689679D6BDA52FE75098FB8957DE82D7401FB0234FDD49310AD61DCD9A6D750D1991115BCED7611A5A0CAE8721BC356442392E88521175F25176D293E77FB4E524DF99ADA06133FE2250C66308E54850B3F92FD901028ABB462ED9CCE0C4133C3F7A2066F92BE8D6B9CDD7C49AF2B3E190D578B051BE688BBF5B417A63111DE26B390D775E8C13A74CC2E2D8CE1348D054067120C98 82 | 83 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/GenerateZpkSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.junit.runner.RunWith 25 | import org.scalatest.FunSuite 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import org.scalatest.junit.JUnitRunner 28 | import org.leachbj.hsmsim.crypto.DES 29 | 30 | @RunWith(classOf[JUnitRunner]) 31 | class GenerateZpkSuite extends FunSuite { 32 | test("generate zpk") { 33 | val zmkUnderLmk = HexConverter.fromHex("E13D662B185F5F3B08594F89F1FF903A").toArray 34 | val req = GenerateZpkRequest(zmkUnderLmk, false, 'X', 'U', '0') 35 | val r = GenerateZpkResponse.createResponse(req) 36 | assert(r.errorCode === "00") 37 | assert(r.responseCode === "IB") 38 | assert(r.isInstanceOf[GenerateZpkResponse]) 39 | val generateResponse = r.asInstanceOf[GenerateZpkResponse] 40 | 41 | // the ZPK is random so decrypt it using the known ZMK and verify the check value 42 | val zmk = HexConverter.fromHex("8A7C04A1CD916464D6B361B05BBF6780").toArray 43 | val zpk = DES.tripleDesDecrypt(zmk, generateResponse.zpkZmk) 44 | val check = DES.calculateCheckValue(zpk).take(3) 45 | assert(generateResponse.checkValue === check) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/HsmMessageEncodingSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.scalatest.FunSuite 25 | import scala.collection.immutable.Map 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import akka.util.CompactByteString 28 | import org.junit.runner.RunWith 29 | import org.scalatest.junit.JUnitRunner 30 | import akka.util.ByteString 31 | import org.scalatest.matchers.ShouldMatchers 32 | import org.scalatest.matchers.HavePropertyMatcher 33 | 34 | @RunWith(classOf[JUnitRunner]) 35 | class HsmMessageEncodingSuite extends FunSuite with ShouldMatchers { 36 | // test("GenerateIBMPinOffset") { 37 | // val req = HsmMessageEncoding.decode(ByteString(" DETAF9958474101D950930D1FC86F99447E10B3BADFAA10458E0824698C68CF4FA4F90500000100037601234567890123451234567890NF")) 38 | // } 39 | test("TranslateZpkFromZmkToLmk for non-atalla variant") { 40 | val req = HsmMessageEncoding.decode(ByteString(" FAUE13D662B185F5F3B08594F89F1FF903AX7BC09407A015F72FC59C32147D2AAE57;UU0")) 41 | println(req) 42 | assert(req.isInstanceOf[TranslateZpkFromZmkToLmkRequest]) 43 | val translateReq = req.asInstanceOf[TranslateZpkFromZmkToLmkRequest] 44 | translateReq.zmk should be(HexConverter.fromHex("E13D662B185F5F3B08594F89F1FF903A").toArray) 45 | translateReq.zpk should be(HexConverter.fromHex("7BC09407A015F72FC59C32147D2AAE57").toArray) 46 | translateReq.isAtallaVariant should be(false) 47 | translateReq.keySchemeLmk should be('U'.toByte) 48 | translateReq.keySchemeZmk should be('U'.toByte) 49 | translateReq.keyCheckType should be('0'.toByte) 50 | } 51 | 52 | test("TranslateZpkFromZmkToLmk for atalla variant with key scheme") { 53 | val req = HsmMessageEncoding.decode(ByteString(" FAUE13D662B185F5F3B08594F89F1FF903AX7BC09407A015F72FC59C32147D2AAE571;UU0")) 54 | println(req) 55 | assert(req.isInstanceOf[TranslateZpkFromZmkToLmkRequest]) 56 | val translateReq = req.asInstanceOf[TranslateZpkFromZmkToLmkRequest] 57 | translateReq.zmk should be(HexConverter.fromHex("E13D662B185F5F3B08594F89F1FF903A").toArray) 58 | translateReq.zpk should be(HexConverter.fromHex("7BC09407A015F72FC59C32147D2AAE57").toArray) 59 | translateReq.isAtallaVariant should be(true) 60 | translateReq.keySchemeLmk should be('U'.toByte) 61 | translateReq.keySchemeZmk should be('U'.toByte) 62 | translateReq.keyCheckType should be('0'.toByte) 63 | } 64 | 65 | test("TranslateZpkFromZmkToLmk for atalla variant without key scheme") { 66 | val req = HsmMessageEncoding.decode(ByteString(" FAUE13D662B185F5F3B08594F89F1FF903AX7BC09407A015F72FC59C32147D2AAE571")) 67 | assert(req.isInstanceOf[TranslateZpkFromZmkToLmkRequest]) 68 | val translateReq = req.asInstanceOf[TranslateZpkFromZmkToLmkRequest] 69 | translateReq.zmk should be(HexConverter.fromHex("E13D662B185F5F3B08594F89F1FF903A").toArray) 70 | translateReq.zpk should be(HexConverter.fromHex("7BC09407A015F72FC59C32147D2AAE57").toArray) 71 | translateReq.isAtallaVariant should be(true) 72 | translateReq.keySchemeLmk should be('0'.toByte) 73 | translateReq.keySchemeZmk should be('0'.toByte) 74 | translateReq.keyCheckType should be('0'.toByte) 75 | } 76 | 77 | test("TranslateZpkFromZmkToLmk response") { 78 | val zpk = HexConverter.fromHex("0B7C6F68A38FA3C2CB42C2991576A986").toArray 79 | val checkValue = HexConverter.fromHex("DA24FF").toArray 80 | val resp = TranslateZpkFromZmkToLmkResponse("01", zpk, checkValue) 81 | val encoding = HsmMessageEncoding.encode(resp) 82 | encoding should be(ByteString(" FB01U0B7C6F68A38FA3C2CB42C2991576A986DA24FF0000000000")) 83 | } 84 | 85 | test("GenerateZpk decode") { 86 | val req = HsmMessageEncoding.decode(ByteString(" IAUE13D662B185F5F3B08594F89F1FF903A;XU0")) 87 | assert(req.isInstanceOf[GenerateZpkRequest]) 88 | val generateReq = req.asInstanceOf[GenerateZpkRequest] 89 | generateReq.zmk should be(HexConverter.fromHex("E13D662B185F5F3B08594F89F1FF903A").toArray) 90 | generateReq.isAtallaVariant should be(false) 91 | generateReq.keySchemeLmk should be('U'.toByte) 92 | generateReq.keySchemeZmk should be('X'.toByte) 93 | generateReq.keyCheckType should be('0'.toByte) 94 | } 95 | 96 | test("GenerateZpk encode") { 97 | val zpkUnderZmk = HexConverter.fromHex("1C6FE37B531D49B2D6D5B30D146D6CD6").toArray 98 | val zpkUnderLmk = HexConverter.fromHex("338120BA5C1B4911DD4CE73F960738DD").toArray 99 | val checkValue = HexConverter.fromHex("5346A7").toArray 100 | val resp = GenerateZpkResponse("00", zpkUnderZmk, zpkUnderLmk, checkValue) 101 | val encoding = HsmMessageEncoding.encode(resp) 102 | encoding should be(ByteString(" IB00X1C6FE37B531D49B2D6D5B30D146D6CD6U338120BA5C1B4911DD4CE73F960738DD5346A70000000000")) 103 | } 104 | 105 | test("GenerateMac decode TAK") { 106 | val req = HsmMessageEncoding.decode(ByteString(" MS0010U90A17A99E4BA32475E692D7AAE177BBD00101234567887654321")) 107 | assert(req.isInstanceOf[GenerateMacRequest]) 108 | val generateReq = req.asInstanceOf[GenerateMacRequest] 109 | generateReq.blockNumber should be (0) 110 | generateReq.iv should be (None) 111 | generateReq.keyLength should be (1) 112 | generateReq.keyType should be (0) 113 | generateReq.macKey should be (HexConverter.fromHex("90A17A99E4BA32475E692D7AAE177BBD").toArray) 114 | generateReq.message should be ("1234567887654321".toArray) 115 | } 116 | 117 | test("GenerateMac decode ZAK") { 118 | val req = HsmMessageEncoding.decode(ByteString(" MS0111U90A17A99E4BA32475E692D7AAE177BBD001031323334353637383837363534333231")) 119 | assert(req.isInstanceOf[GenerateMacRequest]) 120 | val generateReq = req.asInstanceOf[GenerateMacRequest] 121 | generateReq.blockNumber should be (0) 122 | generateReq.iv should be (None) 123 | generateReq.keyLength should be (1) 124 | generateReq.keyType should be (1) 125 | generateReq.macKey should be (HexConverter.fromHex("90A17A99E4BA32475E692D7AAE177BBD").toArray) 126 | generateReq.message should be ("1234567887654321".toArray) 127 | } 128 | 129 | test("GenerateRSAKeySet with custom firmware SA command") { 130 | val req = HsmMessageEncoding.decode(HexConverter.fromHex("2020202053413030353132303130303137010001")) 131 | assert(req.isInstanceOf[GenerateRSAKeySetRequest]) 132 | val generateReq = req.asInstanceOf[GenerateRSAKeySetRequest] 133 | generateReq.keyType should be (0) 134 | generateReq.keyLength should be (512) 135 | generateReq.publicKeyEncoding should be (1) 136 | generateReq.publicExponent should be (0x10001) 137 | } 138 | 139 | test("GenerateRSAKeySteResponse for custom SA command") { 140 | val publicKey = HexConverter.fromHex("30470240DCAC85790D770C4CAA562B4E50DFB42F4619B1AF8E3870714691D08C512DA0E365518D92BCFC5BAC28514C047DA4F61E6CB2B9DDA939C3594B7E60880D0B13770203010001").toArray 141 | val secretKey = HexConverter.fromHex("60779D7300F8C209B4C443C67367ADECBD80CAE5972843B9BE2990B573903DDE765CD87AA823056AC472674C5AF9A75127FC7E66340E3C2CC27F051406E6803D5556991FFE66A9C3B1288E7FC893C3B3123C955ED8DBD8E9530F1D5FB4CD3E1050BA913F0430C230F596306BEFDB096ABC8B12B2111A5CBF951F879E0AB063368BAA2BDD1BA69E80D285323BFD267B48720B81B33F0B5A23EB5552DE94F3319F953FB508A27CA894FC18EFE7D068E53D").toArray 142 | val resp = GenerateRSAKeySetResponse(publicKey, secretKey) 143 | val encoding = HsmMessageEncoding.encode(resp) 144 | encoding should be (HexConverter.fromHex("202020205342303030470240dcac85790d770c4caa562b4e50dfb42f4619b1af8e3870714691d08c512da0e365518d92bcfc5bac28514c047da4f61e6cb2b9dda939c3594b7e60880d0b137702030100013031373660779d7300f8c209b4c443c67367adecbd80cae5972843b9be2990b573903dde765cd87aa823056ac472674c5af9a75127fc7e66340e3c2cc27f051406e6803d5556991ffe66a9c3b1288e7fc893c3b3123c955ed8dbd8e9530f1d5fb4cd3e1050ba913f0430c230f596306befdb096abc8b12b2111a5cbf951f879e0ab063368baa2bdd1ba69e80d285323bfd267b48720b81b33f0b5a23eb5552de94f3319f953fb508a27ca894fc18efe7d068e53d")) 145 | } 146 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/ImportDesKeySuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.scalatest.FunSuite 25 | import scala.collection.immutable.Map 26 | import org.junit.runner.RunWith 27 | import org.scalatest.junit.JUnitRunner 28 | import org.leachbj.hsmsim.util.HexConverter 29 | 30 | @RunWith(classOf[JUnitRunner]) 31 | class ImportDesKeySuite extends FunSuite { 32 | test("import des key with valid parity should match test vector") { 33 | // decrypted key 022F04000000D58EA28B2F4DCD74375D665834B4970F6B7539BB2CC922EE8C866BCC1AA132D6A43B46AD961BC5EC6017B6D48BAEEE01D35257D589D3E7F805C63344ECE9EEB30000E53325C414207C4D9CD3E63826F6C45330129938828EADF214EF32001C0BE61E4C261D0A84F28497A419B778964D923D8F9423BF68E10B9D8CF3F3C22DBB247900008E5F170774DE88F824E8EEE5787864B4F24E267CC8861749B30447DD671621E46D7CD9C90EBD2E9D9565248DB2749EABE236E539068D455003D97783489BF477000098CCC3D80D6AFD891337EED019F9D8377561BB7B01B473F6B89F76AABD5D441432C4135C58A1ADBA6D667A506433B6D3B50D6D2A45EB5D13B34D4D2C1E7CC2FB000081B9CC50502840BE6D95BE52266DCD50A482C0467426DD3634BFC5E7C68DFFC8A02EF398DF500C42417DC8FFEC08391BC4DA76ED20E0D4AF0D8DE0FAD441D3367BCDA33A000000000000 34 | val rsa = HexConverter.fromHex("1837180A8E0A914C6222FFD98444C5447C3B8EE34CB7F3B6F1B487AFAC6566F60EE117EA97EA1BD7119467405EB969AFEF478F73059582BC9D6FBE232424EBA13D17B9E059FCEF0B1FE13FA53C24810B026EF779067EBA38533192DE9E3C3A5099B3C909129D54DF1A8976C24AC69E89AC0F50F527D01C1EEAC1A1056D89387AB9E30EF9087079D59FC315C70392B43F276306AD9D895349258E9B79A03DF07AC1D37AD4533339175A15FFB7D91F83CB755F14013EBA58B3A699959EF4B1944F4C441FC4EFF7B4DBBF3B49B7F8CC175641659254877D62C124AEB57A4B1E03D11C5081DAF0A24063E035E10FA2D3EC018A8B6061A6ABABE39BB963622AE7E2602E396C0D9F5E2041E118EE5609225BF466703A389150D41CC527A2D6A9A820914260895A01B5669E9076914F6990AEB659858602149C4BBC2CFEE2352F0D56CE3E6B071CEA7CCEE6B3C1B5B90DE9EEE36A4494BD80CB125C").toArray 35 | val des = HexConverter.fromHex("A2A7273D4B1F0F514FB716DF297909E92558DCB20AB44083761B1815425BD4A562EA06E913CE251324C04496B95EFDC2771F116ED602D198A5C5EDC725D7D115B4087F4DC0D299DAC6484892FB0493F6B1B8AC96FCC5F36D5FFB5054E1CE6ED8D8B6990786DE9F7AEBC0AA921EB9004C6F9BADA4715F7680433EF10B89516635").toArray 36 | val r = ImportDesKeyRequest(des, rsa, 'T') 37 | 38 | val resp = ImportDesKeyResponse.createResponse(r).asInstanceOf[ImportDesKeyResponse] 39 | val importDesKeyResponse = resp.asInstanceOf[ImportDesKeyResponse] 40 | assert(importDesKeyResponse.desKey === HexConverter.fromHex("CF7471F997A26A9A82B16A616434312FD101DF4F6C426E97").toArray) 41 | assert(importDesKeyResponse.keyCheckValue === HexConverter.fromHex("D58C6BB34AA6").toArray) 42 | } 43 | 44 | test("import des key with invalid parity should correct parity") { 45 | // decrypted key 022F04000000C2889EAB54871401255AF447609AF33B39DDA4F49650C991F44FE9DB6E6AEA52FFDB117FEA931F34E92EA2DB7E3D82821516472500A4A99BEE030A89E7C12C2B0000C1EBEEE3606B8255697916CD1989C3C8BB100308D46C2FD12D4D67D269361B68E69905DEA0865826A377D4AA4F2BCA7DA87EFC7DC4785CBB29A7014CD774E9C5000081B069C78DAF62AB6E3CA2DA40674CD22693C34DB98B310BF8354692499C9C37553CB655470CBF789B746C92542901AC0E0EDA18AB1871129EACB1B1452B72C700008147F497959D018E4650B9DE11068285D20AACB08D9D75361E339A8C46241245EF10AE946B043AC46CFA8DC6DF7286FE7054A853D8503DD21BC4AB888FA346830000AE99EF8DA8724FD45EE6D8631D2D34E90A61C3D4678A9C1363C5841BCD685B4A82AE052A28DDD6F19369CB7ABE5EF9B06C8D94850E749DF995C45871343595B9EA5FD819000000000000 46 | val rsa = HexConverter.fromHex("6780AD7C9D92081B578566F14361C60450A603A63FB8F1F67B46DC15A9C785F17B10BDD6A9A95552804F5C41D7F24D13A16F89716511E44CAA70A3E9D4B1026A2538E46BE466AA2BF5154D04D1AF924D89010DBF6390ED13A97C18FFF4C643984EE3650FD14DFF3DD0AECFF1D67906FDFFA7256792C82E512471785E4859CE48A06D84DCE20AD0B55517F9044470CF224E78E416CE894140C9D2DDD372164BE9068733081B051F559BAE2D8A782F42590AF825122DDB494B63065BE67A1E80817CBF47CEF94D7AE239A482449636D7E3E9A2C3D3975FEF747E3816895D9935DBBE224BD07A035D2D6869B9135D964756081D09D4C554E84D5216241B76DF6A2169528600CCB3CA2DA323C282CF70CB030A46568B6FB700783B9EF3BA63AA32A53233D454E0DDC914DFE2C4BE411F307E33F394735AD8842444547265C1F9FFF70ABC8EA33D0B6B6C923D1B868380481317C250514838285D").toArray 47 | val des = HexConverter.fromHex("0D6A3202E0D1E02AED8FC0440F89E021BEA9AE7441B2B0EF0682C65DFCBDD9544FC384CA1101252BBE40A6C9DDA293237F217C62E9B879CA32D92C9D7A81F9160934B1A66A0399CE56E403D61F00D3E7F9E549BD29509965AF54790769821570EE00A3EEF9B6A6BBE247EA535E35150CCF6DDC082800D86EA4DD9413419EB2DB").toArray 48 | val r = ImportDesKeyRequest(des, rsa, 'T') 49 | 50 | val resp = ImportDesKeyResponse.createResponse(r).asInstanceOf[ImportDesKeyResponse] 51 | val importDesKeyResponse = resp.asInstanceOf[ImportDesKeyResponse] 52 | assert(importDesKeyResponse.desKey === HexConverter.fromHex("971803BA969012152827CC97A98D18D50BE6559CDFF6F7E1").toArray) 53 | assert(importDesKeyResponse.keyCheckValue === HexConverter.fromHex("6EC2A9AE788F").toArray) 54 | } 55 | 56 | test("import 512bit key management key test vector") { 57 | // decrypted key 0130020000DF456ECF91719C350B17F8953265691494DF018FD441AF3FF279A20BF784313B00C11DFBEA6CA09F5879A5D1357B737E4BA0CAC43037AE682655EC28C374A2DED70094D8F48A60F668235CBAA5B8CC439B630DEA010A8D811F7FF6FBC15D4FAD76270080BEA7F19DC06A3AFBC3E0CE524CFEDD15DC82CACFC99AC439481B2CF86C948F006D6BF174293F21F914A306A0813CCDAF29D99A5C2B691E8FEEFC1AC620D9A688DB8AE837000000 58 | val rsa = HexConverter.fromHex("E14A3B918403718231C100F4882350A2289923C8FF3CE624152C5DE08002734AD28ACC2E01F72C745CB979C4A1FDD89B39FFA492B16C6B7C406439BA1DFDDA90C59B7C93239BC0B1709FE97E0871A1AE7712E052AC33FFD52C1B566125179276D378BEF382965A40C4C7AA20DFCDEE317FAD767A1AB3F64561AADE44663B009DF84FBD0311812A9EC08EB444B3401CD985BDB5367525D57565240BC4D5CD4EF9B7FCEDB08DB14B10DA5D0A6A82F8FF94").toArray 59 | val des = HexConverter.fromHex("5DE7AB4B9AB6066C9E855E25C9783B33BF2F5B21EB0E432189B98472C9CCDD8E299A41AAE7E123663EC2D1B556964B5F15E690723F974118FAE3EDD7A1BBD1E9").toArray 60 | // 7BF4D40FE6587A76 61 | val r = ImportDesKeyRequest(des, rsa, 'T') 62 | 63 | val resp = ImportDesKeyResponse.createResponse(r).asInstanceOf[ImportDesKeyResponse] 64 | val importDesKeyResponse = resp.asInstanceOf[ImportDesKeyResponse] 65 | assert(importDesKeyResponse.desKey === HexConverter.fromHex("F9856E85A3A471BB3D21D07610129AAEA8499F4793B75256").toArray) 66 | assert(importDesKeyResponse.keyCheckValue === HexConverter.fromHex("FF326A093235").toArray) 67 | } 68 | 69 | test("import 512bit public exponent 0x10001 signature only key test vector") { 70 | // decrypted key 0030020000FAD927E2EDE0DC43CF4FA5647AC537B83FC468B423D1B48C375A9D40AF2FD7C300E134B907BA48B37ADC6EAE42F7F321D070B25063B3356610F7C006E8D6480E3D009A12592253D341D6D0756DF39A4899E1AC20F9E27301B1B051AB715CB27D633B0099DAB0121474F8911629534A1324FA53F1BA106B9C91FD91D8004B78247483AD00DBB5A4E44CE3417E1F9DDF4987749B0CF0F5151CE7230AD2D7D972166CB5CD54FB4E373C000000 71 | val rsa = HexConverter.fromHex("60779D7300F8C209B4C443C67367ADECBD80CAE5972843B9BE2990B573903DDE765CD87AA823056AC472674C5AF9A75127FC7E66340E3C2CC27F051406E6803D5556991FFE66A9C3B1288E7FC893C3B3123C955ED8DBD8E9530F1D5FB4CD3E1050BA913F0430C230F596306BEFDB096ABC8B12B2111A5CBF951F879E0AB063368BAA2BDD1BA69E80D285323BFD267B48720B81B33F0B5A23EB5552DE94F3319F953FB508A27CA894FC18EFE7D068E53D").toArray 72 | val des = HexConverter.fromHex("3D22133C89295D71C14228C957BC669EAD027A7F910273DF04738078FE6F61E217CB93C63E07F7E538968C061F39B87A23F112617BA57A2E047C0FA263855735").toArray 73 | // 7BF4D40FE6587A76 74 | val r = ImportDesKeyRequest(des, rsa, 'T') 75 | 76 | val resp = ImportDesKeyResponse.createResponse(r).asInstanceOf[ImportDesKeyResponse] 77 | val importDesKeyResponse = resp.asInstanceOf[ImportDesKeyResponse] 78 | assert(importDesKeyResponse.desKey === HexConverter.fromHex("2FB4F1DCBCA6165A85D78B7A520DC70B6F251EA82FB48380").toArray) 79 | assert(importDesKeyResponse.keyCheckValue === HexConverter.fromHex("4E11B5A42945").toArray) 80 | 81 | // 0130020000DF456ECF91719C350B17F8953265691494DF018FD441AF3FF279A20BF784313B00C11DFBEA6CA09F5879A5D1357B737E4BA0CAC43037AE682655EC28C374A2DED70094D8F48A60F668235CBAA5B8CC439B630DEA010A8D811F7FF6FBC15D4FAD76270080BEA7F19DC06A3AFBC3E0CE524CFEDD15DC82CACFC99AC439481B2CF86C948F006D6BF174293F21F914A306A0813CCDAF29D99A5C2B691E8FEEFC1AC620D9A688DB8AE837000000 82 | // 0030020000FAD927E2EDE0DC43CF4FA5647AC537B83FC468B423D1B48C375A9D40AF2FD7C300E134B907BA48B37ADC6EAE42F7F321D070B25063B3356610F7C006E8D6480E3D009A12592253D341D6D0756DF39A4899E1AC20F9E27301B1B051AB715CB27D633B0099DAB0121474F8911629534A1324FA53F1BA106B9C91FD91D8004B78247483AD00DBB5A4E44CE3417E1F9DDF4987749B0CF0F5151CE7230AD2D7D972166CB5CD54FB4E373C000000 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/TranslatePinZpkToAnotherSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.scalatest.FunSuite 25 | import scala.collection.immutable.Map 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import org.junit.runner.RunWith 28 | import org.scalatest.junit.JUnitRunner 29 | 30 | @RunWith(classOf[JUnitRunner]) 31 | class TranslatePinZpkToAnotherSuite extends FunSuite { 32 | test("translate source format 05 to destination format 01") { 33 | val sourceZpk = HexConverter.fromHex("971803BA969012152827CC97A98D18D50BE6559CDFF6F7E1").toArray 34 | val destZpk = HexConverter.fromHex("247078713614BE11F819054C5FFB08FF").toArray; 35 | val maxPinLength = 6 36 | val sourcePinBlock = HexConverter.fromHex("F2B66A1070EADC47").toArray 37 | 38 | val sourcePinBlockFormat = "05" 39 | val destinationPinBlockFormat = "01" 40 | val accountNumber = "000001000376" 41 | val req = TranslatePinZpkToAnotherRequest(sourceZpk, destZpk, maxPinLength, sourcePinBlock, sourcePinBlockFormat, destinationPinBlockFormat, accountNumber) 42 | val res = TranslatePinZpkToAnotherResponse.createResponse(req) 43 | assert(res.errorCode === "00") 44 | assert(res.responseCode === "CD") 45 | assert(res.isInstanceOf[TranslatePinZpkToAnotherResponse]) 46 | val translateResponse = res.asInstanceOf[TranslatePinZpkToAnotherResponse] 47 | assert(translateResponse.pinBlock === HexConverter.fromHex("DBEEBF06C9ED0A48").toArray) 48 | } 49 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/TranslatePinZpkToLmkSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.junit.runner.RunWith 25 | import org.scalatest.FunSuite 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import org.scalatest.junit.JUnitRunner 28 | 29 | @RunWith(classOf[JUnitRunner]) 30 | class TranslatePinZpkToLmkSuite extends FunSuite { 31 | test("translate valid pinblock should return encrypted pin") { 32 | val zpk = HexConverter.fromHex("971803BA969012152827CC97A98D18D50BE6559CDFF6F7E1").toArray 33 | val sourcePinBlock = HexConverter.fromHex("F2B66A1070EADC47").toArray 34 | val accountNumber = "000001000376" 35 | val req = TranslatePinZpkToLmkRequest(zpk, sourcePinBlock, "05", accountNumber) 36 | val r = TranslatePinZpkToLmkResponse.createResponse(req) 37 | assert(r.errorCode === "00") 38 | assert(r.responseCode === "JF") 39 | assert(r.isInstanceOf[TranslatePinZpkToLmkResponse]) 40 | val translateResponse = r.asInstanceOf[TranslatePinZpkToLmkResponse] 41 | // assert(translateResponse.pin === HexConverter.fromHex("6629123327410")) 42 | 43 | // this is not the correct value - the LMK encrypted Pin block format is unknown at present 44 | assert(translateResponse.pin === HexConverter.fromHex("24698C68CF4FA4F9").toArray) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/TranslateZpkFromZmkToLmkSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.junit.runner.RunWith 25 | import org.scalatest.FunSuite 26 | import org.leachbj.hsmsim.util.HexConverter 27 | import org.scalatest.junit.JUnitRunner 28 | 29 | @RunWith(classOf[JUnitRunner]) 30 | class TranslateZpkFromZmkToLmkSuite extends FunSuite { 31 | test("translate valid ZPK should match test vector") { 32 | val zmkUnderLmk = HexConverter.fromHex("E13D662B185F5F3B08594F89F1FF903A").toArray 33 | val zpkUnderZmk = HexConverter.fromHex("7BC09407A015F72FC59C32147D2AAE57").toArray 34 | val req = TranslateZpkFromZmkToLmkRequest(/*'U'*/zmkUnderLmk, /*'X'*/zpkUnderZmk, false, 'U', 'U', '0') 35 | val r = TranslateZpkFromZmkToLmkResponse.createResponse(req) 36 | assert(r.errorCode === "01") 37 | assert(r.responseCode === "FB") 38 | assert(r.isInstanceOf[TranslateZpkFromZmkToLmkResponse]) 39 | val translateResponse = r.asInstanceOf[TranslateZpkFromZmkToLmkResponse] 40 | 41 | // verify the first 3 bytes of the checksum 42 | assert(translateResponse.checkValue === HexConverter.fromHex("DA24FFF7765A").toArray.take(3)) 43 | // key type == 'U' 44 | assert(translateResponse.zpk === HexConverter.fromHex("0B7C6F68A38FA3C2CB42C2991576A986").toArray) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/commands/VerifyInterchangePinIBMSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.commands 23 | 24 | import org.junit.runner.RunWith 25 | import org.scalatest.FunSuite 26 | import org.scalatest.junit.JUnitRunner 27 | import org.leachbj.hsmsim.util.HexConverter 28 | 29 | @RunWith(classOf[JUnitRunner]) 30 | class VerifyInterchangePinIBMSuite extends FunSuite { 31 | 32 | // trait TestValues { 33 | // val oddParity = HexConverter.fromHex("2A2A2A2A2A2A2A2A").toArray 34 | // val evenParity = HexConverter.fromHex("2B2B2B2B2B2B2B2B").toArray 35 | // val key = HexConverter.fromHex("EA040820ABB0CB9E8AF7ADD64F54C73D0289C8923D70FB8C").toArray 36 | // val encryptedKey = HexConverter.fromHex("971803BA969012152827CC97A98D18D50BE6559CDFF6F7E1").toArray 37 | // val clearPinBlock = HexConverter.fromHex("1514789d20617088").toArray 38 | // val encryptedPinBlock = HexConverter.fromHex("F2B66A1070EADC47").toArray 39 | // } 40 | 41 | test("valid pin data should return 00") { 42 | val zpk = HexConverter.fromHex("971803BA969012152827CC97A98D18D50BE6559CDFF6F7E1").toArray 43 | val pvk = HexConverter.fromHex("AF9958474101D950930D1FC86F99447E10B3BADFAA10458E").toArray 44 | val pinBlock = HexConverter.fromHex("F2B66A1070EADC47").toArray 45 | val pinBlockFormat = "01" 46 | val checkLength = 5 47 | val accountNumber = "000001000376" 48 | val decimalisation = "0123456789012345".getBytes("UTF-8") 49 | val pinValidation = "1234567890NF" 50 | val offset = "17702FFFFFFF" 51 | val req = VerifyInterchangePinIBMRequest(zpk, pvk, pinBlock, pinBlockFormat, checkLength, accountNumber, decimalisation, pinValidation, offset) 52 | val r = VerifyInterchangePinIBMResponse.createResponse(req) 53 | assert(r.errorCode === "00") 54 | assert(r.responseCode === "EB") 55 | assert(r.isInstanceOf[VerifyInterchangePinIBMResponse]) 56 | } 57 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/crypto/DESSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.crypto 23 | 24 | import org.junit.runner.RunWith 25 | import org.scalatest.FunSuite 26 | import org.scalatest.junit.JUnitRunner 27 | import org.leachbj.hsmsim.util.HexConverter 28 | import javax.crypto.spec.IvParameterSpec 29 | import javax.crypto.Cipher 30 | import javax.crypto.SecretKeyFactory 31 | import javax.crypto.spec.DESKeySpec 32 | import akka.util.ByteString 33 | import java.security.Security 34 | import org.bouncycastle.jce.provider.BouncyCastleProvider 35 | import javax.crypto.Mac 36 | 37 | @RunWith(classOf[JUnitRunner]) 38 | class DESSuite extends FunSuite { 39 | import org.leachbj.hsmsim.crypto.DES._ 40 | 41 | trait TestValues { 42 | val oddParity = HexConverter.fromHex("2A2A2A2A2A2A2A2A").toArray 43 | val evenParity = HexConverter.fromHex("2B2B2B2B2B2B2B2B").toArray 44 | val key = HexConverter.fromHex("EA040820ABB0CB9E8AF7ADD64F54C73D0289C8923D70FB8C").toArray 45 | val encryptedKey = HexConverter.fromHex("971803BA969012152827CC97A98D18D50BE6559CDFF6F7E1").toArray 46 | val clearPinBlock = HexConverter.fromHex("1514789d20617088").toArray 47 | val encryptedPinBlock = HexConverter.fromHex("F2B66A1070EADC47").toArray 48 | val keyDoubleLength = HexConverter.fromHex("F1799DF10D51EA89A849F22F3DDA7A80").toArray 49 | val encryptedDoubleLength = HexConverter.fromHex("247078713614BE11F819054C5FFB08FF").toArray 50 | } 51 | 52 | test("mac with nist test vector") { 53 | val data = "Hello World !!!!".getBytes 54 | val key = HexConverter.fromHex("7CA110454A1A6E570131D9619DC1376E").toArray 55 | assert(mac(key, data) === HexConverter.fromHex("F09B856213BAB83B").toArray) 56 | } 57 | 58 | test("mac with TAK test vector") { 59 | val key = HexConverter.fromHex("A24A64E9371F523DDA94E5E9CB894F13").toArray 60 | val data = "1234567887654321".getBytes 61 | assert(mac(key, data) === HexConverter.fromHex("934F05263310B6D3").toArray) 62 | } 63 | 64 | test("mac with ZAK test vector") { 65 | val key = HexConverter.fromHex("082FD59BD0F8B9981A2C160E19ABDA25").toArray 66 | val data = "1234567887654321".getBytes 67 | assert(mac(key, data) === HexConverter.fromHex("A1C911EBA50563C9").toArray) 68 | } 69 | 70 | test("isParityAdjusted should return true for oddParity") { 71 | new TestValues { 72 | assert(isParityAdjusted(oddParity)) 73 | } 74 | } 75 | 76 | test("isParityAdjusted should return flase for evenParity") { 77 | new TestValues { 78 | assert(isParityAdjusted(evenParity) == false) 79 | } 80 | } 81 | 82 | test("adjust parity for odd input return odd parity key") { 83 | new TestValues { 84 | assert(adjustParity(oddParity) === oddParity) 85 | } 86 | } 87 | 88 | test("adjust parity for even input should return odd parity key") { 89 | new TestValues { 90 | assert(adjustParity(evenParity) === oddParity) 91 | } 92 | } 93 | 94 | test("tripleDesEncryptVariant should support triple length keys") { 95 | new TestValues { 96 | assert(tripleDesEncryptVariant(LMK.lmkVariant("06-07", 0), key) === encryptedKey) 97 | } 98 | 99 | } 100 | 101 | test("tripleDesEncryptVariant should support double length keys") { 102 | new TestValues { 103 | assert(tripleDesEncryptVariant(LMK.lmkVariant("06-07", 0), keyDoubleLength) === encryptedDoubleLength) 104 | } 105 | } 106 | 107 | test("tripleDesDecryptVariant should support triple length keys") { 108 | new TestValues { 109 | assert(tripleDesDecryptVariant(LMK.lmkVariant("06-07", 0), encryptedKey) === key) 110 | } 111 | } 112 | 113 | test("tripleDesDecryptVariant should support double length keys") { 114 | new TestValues { 115 | assert(tripleDesDecryptVariant(LMK.lmkVariant("06-07", 0), encryptedDoubleLength) === keyDoubleLength) 116 | } 117 | } 118 | 119 | test("tripleDesEncrypt test vector") { 120 | new TestValues { 121 | assert(tripleDesEncrypt(key, clearPinBlock) === encryptedPinBlock) 122 | } 123 | } 124 | 125 | test("tripleDesDecrypt test vector") { 126 | new TestValues { 127 | assert(tripleDesDecrypt(key, encryptedPinBlock) === clearPinBlock) 128 | } 129 | } 130 | 131 | test("calculateCheckValue test vector") { 132 | new TestValues { 133 | assert(calculateCheckValue(key) === HexConverter.fromHex("6EC2A9AE788F").toArray) 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/crypto/IBMPinValidationSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.crypto 23 | 24 | import org.scalatest.FunSuite 25 | import org.junit.runner.RunWith 26 | import org.scalatest.junit.JUnitRunner 27 | import org.leachbj.hsmsim.util.HexConverter 28 | 29 | @RunWith(classOf[JUnitRunner]) 30 | class IBMPinValidationSuite extends FunSuite { 31 | import IBMPinValidation._ 32 | 33 | trait TestValues { 34 | val accountNumber = "000001000376" 35 | val decimalisation = "0123456789012345".getBytes("UTF-8") 36 | val pinValidation = "1234567890NF" 37 | val encryptedValue = HexConverter.fromHex("07087E789002BAD5").toArray 38 | val naturalPin = Vector(48, 55, 48, 56, 55) // 0 7 0 8 7 39 | val pin = "14789" 40 | val offset = "17702" 41 | val pinLen = 5 42 | } 43 | 44 | test("validationBlock should correctly insert account number") { 45 | new TestValues { 46 | assert(validationBlock(pinValidation, accountNumber) === "123456789000376F") 47 | } 48 | } 49 | 50 | test("digitReplacment") { 51 | new TestValues { 52 | assert(digitReplacement(encryptedValue, decimalisation, pinLen) === naturalPin) 53 | } 54 | } 55 | 56 | test("derivePinFromOffset") { 57 | new TestValues { 58 | assert(derivePinFromOffset(naturalPin, offset) === "14789") 59 | } 60 | } 61 | 62 | test("deriveOffsetFromPin") { 63 | new TestValues { 64 | assert(deriveOffsetFromPin(naturalPin, "14789") === offset) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/crypto/LMKSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.crypto 23 | 24 | import org.junit.runner.RunWith 25 | import org.scalatest.FunSuite 26 | import org.scalatest.junit.JUnitRunner 27 | import org.leachbj.hsmsim.util.HexConverter 28 | 29 | @RunWith(classOf[JUnitRunner]) 30 | class LMKSuite extends FunSuite { 31 | import org.leachbj.hsmsim.crypto.LMK.lmkVariant 32 | 33 | trait TestValues { 34 | val variant0 = HexConverter.fromHex("20202020202020203131313131313131").toArray 35 | val variant1 = HexConverter.fromHex("21202020202020203131313131313131").toArray 36 | val variant2 = HexConverter.fromHex("22202020202020203131313131313131").toArray 37 | val variant3 = HexConverter.fromHex("23202020202020203131313131313131").toArray 38 | val variant4 = HexConverter.fromHex("24202020202020203131313131313131").toArray 39 | val variant5 = HexConverter.fromHex("25202020202020203131313131313131").toArray 40 | val variant6 = HexConverter.fromHex("26202020202020203131313131313131").toArray 41 | val variant7 = HexConverter.fromHex("27202020202020203131313131313131").toArray 42 | val variant8 = HexConverter.fromHex("28202020202020203131313131313131").toArray 43 | } 44 | 45 | test("lmkVariant 0 test vector") { 46 | new TestValues { 47 | assert(lmkVariant("02-03", 0) === variant0) 48 | } 49 | } 50 | test("lmkVariant 1 test vector") { 51 | new TestValues { 52 | assert(lmkVariant("02-03", 1) === variant1) 53 | } 54 | } 55 | test("lmkVariant 2 test vector") { 56 | new TestValues { 57 | assert(lmkVariant("02-03", 2) === variant2) 58 | } 59 | } 60 | test("lmkVariant 3 test vector") { 61 | new TestValues { 62 | assert(lmkVariant("02-03", 3) === variant3) 63 | } 64 | } 65 | test("lmkVariant 4 test vector") { 66 | new TestValues { 67 | assert(lmkVariant("02-03", 4) === variant4) 68 | } 69 | } 70 | test("lmkVariant 5 test vector") { 71 | new TestValues { 72 | assert(lmkVariant("02-03", 5) === variant5) 73 | } 74 | } 75 | test("lmkVariant 6 test vector") { 76 | new TestValues { 77 | assert(lmkVariant("02-03", 6) === variant6) 78 | } 79 | } 80 | test("lmkVariant 7 test vector") { 81 | new TestValues { 82 | assert(lmkVariant("02-03", 7) === variant7) 83 | } 84 | } 85 | test("lmkVariant 8 test vector") { 86 | new TestValues { 87 | assert(lmkVariant("02-03", 8) === variant8) 88 | } 89 | } 90 | test("lmkVariant 9 is not allowed") { 91 | intercept[IllegalArgumentException] { 92 | lmkVariant("02-03", 9) 93 | } 94 | } 95 | test("lmkVariant for unknown pair throws exception") { 96 | intercept[RuntimeException] { 97 | lmkVariant("99-03", 1) 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /hsmsim-akka/src/test/scala/org/leachbj/hsmsim/util/HexConverterSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.util 23 | 24 | import org.scalatest.FunSuite 25 | import org.junit.runner.RunWith 26 | import org.scalatest.junit.JUnitRunner 27 | import akka.util.ByteStringBuilder 28 | 29 | @RunWith(classOf[JUnitRunner]) 30 | class HexConverterSuite extends FunSuite { 31 | import org.leachbj.hsmsim.util.HexConverter._ 32 | 33 | trait TestValues { 34 | val byteString = { 35 | val bs = new ByteStringBuilder 36 | bs ++= Array[Byte](0x01, 0x23, 0x45, 0x67, 0x89.toByte, 0xAB.toByte, 0xCD.toByte, 0xEF.toByte) 37 | bs.result 38 | } 39 | } 40 | 41 | test("toHex should match test vectors") { 42 | new TestValues { 43 | assert(toHex(byteString) === "0123456789ABCDEF") 44 | } 45 | } 46 | 47 | test("toHex should support optional separator") { 48 | new TestValues { 49 | assert(toHex(byteString, Option(" ")) == "01 23 45 67 89 AB CD EF") 50 | } 51 | } 52 | 53 | test("fromHex should match test vectors") { 54 | new TestValues { 55 | assert(fromHex("0123456789ABCDEF") === byteString) 56 | } 57 | } 58 | 59 | test("fromHex should be case insenstive") { 60 | new TestValues { 61 | assert(fromHex("0123456789abcdef") === byteString) 62 | } 63 | } 64 | 65 | test("fromHex should ignore non-hex characters") { 66 | new TestValues { 67 | assert(fromHex("01 2$34z56789/abcd?e@f") === byteString) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /hsmsim-war/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.leachbj 6 | hsmsim 7 | 0.0.1-SNAPSHOT 8 | 9 | hsmsim-war 10 | war 11 | hsmsim-war 12 | 13 | 14 | com.typesafe.akka 15 | akka-actor_2.10 16 | 17 | 18 | org.leachbj 19 | hsmsim-akka 20 | ${project.version} 21 | 22 | 23 | javax.servlet 24 | servlet-api 25 | 2.5 26 | 27 | 28 | log4j 29 | log4j 30 | 1.2.16 31 | provided 32 | 33 | 34 | 35 | hsmsim 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-compiler-plugin 40 | 41 | 1.8 42 | 1.8 43 | 44 | 45 | 46 | org.scala-tools 47 | maven-scala-plugin 48 | 49 | 50 | scala-compile-first 51 | 52 | add-source 53 | compile 54 | 55 | process-resources 56 | 57 | 58 | scala-test-compile 59 | 60 | testCompile 61 | 62 | process-test-resources 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /hsmsim-war/src/main/resources/akka.conf: -------------------------------------------------------------------------------- 1 | 2 | akka { 3 | 4 | # Loggers to register at boot time (akka.event.Logging$DefaultLogger logs 5 | # to STDOUT) 6 | loggers = ["akka.event.slf4j.Slf4jLogger"] 7 | 8 | # Log level used by the configured loggers (see "loggers") as soon 9 | # as they have been started; before that, see "stdout-loglevel" 10 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 11 | loglevel = "DEBUG" 12 | 13 | # Log level for the very basic logger activated during AkkaApplication startup 14 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 15 | stdout-loglevel = "DEBUG" 16 | 17 | # ensure that all Akka threads are daemon threads 18 | daemonic = on 19 | } -------------------------------------------------------------------------------- /hsmsim-war/src/main/scala/org/leachbj/hsmsim/servlet/ContextListener.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Bernard Leach 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package org.leachbj.hsmsim.servlet 23 | 24 | import _root_.akka.Main 25 | import javax.servlet.{ServletContextListener, ServletContextEvent} 26 | import akka.actor.ActorSystem 27 | import akka.actor.Props 28 | import org.leachbj.hsmsim.akka.HsmSimulator 29 | import org.apache.log4j.Logger 30 | 31 | /** 32 | * This class can be added to web.xml mappings as a listener to start and postStop Akka. 33 | * 34 | * ... 35 | * 36 | * com.my.Initializer 37 | * 38 | * ... 39 | * 40 | */ 41 | class ContextListener extends ServletContextListener { 42 | val log = Logger.getLogger(classOf[ContextListener]) 43 | 44 | lazy val system = ActorSystem("Main") 45 | lazy val sim = system.actorOf(Props[HsmSimulator]) 46 | def contextDestroyed(e: ServletContextEvent): Unit = system.stop(sim) 47 | def contextInitialized(e: ServletContextEvent): Unit = { 48 | log.debug("Actor " + sim + " started") 49 | system.registerOnTermination(log.debug("Akka exited")) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hsmsim-war/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | org.leachbj.hsmsim.servlet.ContextListener 7 | 8 | 9 | index.jsp 10 | 11 | 12 | -------------------------------------------------------------------------------- /hsmsim-war/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.leachbj 5 | hsmsim 6 | 0.0.1-SNAPSHOT 7 | pom 8 | hsmsim 9 | 10 | hsmsim-akka 11 | hsmsim-war 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | com.typesafe.akka 20 | akka-actor_2.10 21 | 2.2.1 22 | 23 | 24 | junit 25 | junit 26 | 4.10 27 | test 28 | 29 | 30 | org.bouncycastle 31 | bcprov-ext-jdk16 32 | 1.46 33 | 34 | 35 | org.scala-lang 36 | scala-library 37 | 2.10.2 38 | 39 | 40 | org.scalatest 41 | scalatest_2.10 42 | 1.9.2 43 | test 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | com.google.code.sortpom 52 | maven-sortpom-plugin 53 | 2.1.0 54 | 55 | -1 56 | custom_1 57 | groupId,artifactId 58 | groupId,artifactId 59 | true 60 | \n 61 | Stop 62 | false 63 | false 64 | 65 | 66 | 67 | 68 | sort 69 | 70 | verify 71 | 72 | 73 | 74 | 75 | maven-assembly-plugin 76 | 77 | 78 | 79 | org.leachbj.hsmsim.Sim 80 | 81 | 82 | 83 | jar-with-dependencies 84 | 85 | 86 | 87 | 88 | make-assembly 89 | 90 | single 91 | 92 | package 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-compiler-plugin 99 | 2.3 100 | 101 | 1.8 102 | 1.8 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-dependency-plugin 108 | 2.2 109 | 110 | 111 | org.apache.maven.plugins 112 | maven-jar-plugin 113 | 2.4 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-surefire-plugin 118 | 2.7 119 | 120 | true 121 | 122 | 123 | 124 | 126 | 127 | org.eclipse.m2e 128 | lifecycle-mapping 129 | 1.0.0 130 | 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-dependency-plugin 137 | [2.2,) 138 | 139 | copy-dependencies 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | org.scala-tools 152 | maven-scala-plugin 153 | 2.15.2 154 | 155 | 156 | scala-compile-first 157 | 158 | add-source 159 | compile 160 | 161 | process-resources 162 | 163 | 164 | scala-test-compile 165 | 166 | testCompile 167 | 168 | process-test-resources 169 | 170 | 171 | 172 | 173 | 174 | 175 | org.scalatest 176 | scalatest-maven-plugin 177 | 1.0-M2 178 | 179 | ${project.build.directory}/surefire-reports 180 | . 181 | 182 | 183 | 184 | test 185 | 186 | test 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | http://github.org/leachbj/hsmsim 195 | 196 | --------------------------------------------------------------------------------