├── .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 |
--------------------------------------------------------------------------------