KDPika Licensing Sign inStart free
API v1
Show all languages
Browse documentation

Choose a page in the navigation drawer.

KDPika Licensing · API v1 Advanced

Swift signature verification

Run the complete Swift Ed25519 verifier against KDPika's real signed JSON and positive, tampered, malformed and expired test cases.

On this page
Updated 2026-10-17 UTCDownload Markdown

Verify KDPika payload authenticity in Swift without losing the server's signed numeric tokens.

What you will learn

  • Build and run the complete Swift verifier.
  • Reject altered, ambiguous and untrusted envelopes.
  • Keep signature checking separate from permission to use a license.

Prerequisites

  • Read signed responses.
  • Install Swift 6+; macOS 13+; built-in CryptoKit.
  • Download the complete verification source archive and extract it.
  • Open a terminal in the extracted security directory; all paths below are relative to it.
  • Use Python 3.9+ only for the optional shared test runner, not as a dependency of this verifier.

Install and build

bashbash
mkdir -p build
swiftc swift/verify.swift -o build/swift-verify

Expected result: dependencies resolve and the build exits successfully. Version commands print the installed runtime. Use a maintained runtime compatible with the minimum; these are tested compatibility floors, not claims of ongoing vendor support.

This exact sample uses Apple's CryptoKit and was compiled on macOS. It is not a Windows/Linux SwiftCrypto build; do not claim cross-platform execution without porting and testing the crypto import.

Run the positive and negative cases

bashbash
./build/swift-verify vectors/positive.key.json vectors/positive.envelope.json
./build/swift-verify vectors/tampered.key.json vectors/tampered.envelope.json
./build/swift-verify vectors/expired.key.json vectors/expired.envelope.json
python3 selftest.py --command ./build/swift-verify

Expected output for the first three commands is signature_valid=true, signature_valid=false and signature_valid=true. The tampered command exits 1 intentionally; run commands separately rather than stopping a batch at that expected refusal. The shared runner prints PASS23 and exits 0 when every expected acceptance/refusal matches.

Expired remains cryptographically authentic but must not unlock access after its expiry. This program returns signature validity only. It does not replace a complete license client, network transport, offline policy or the official SDK.

Complete source

verify.swiftswift
// KDPika signature authenticity only. Swift 6+, macOS 13+, CryptoKit.
// Run compiled verifier with key.json envelope.json. Output signature_valid=true/false.
// Trust the key separately; verify expiry, installation and product before access.
import Foundation
import CryptoKit

enum Invalid: Error { case json }
indirect enum JSON {
    case object([(String, JSON)]), array([JSON]), string(String), number(String), bool(Bool), null
    func get(_ key: String) throws -> JSON {
        guard case .object(let pairs) = self,
              let pair = pairs.first(where: { Array($0.0.utf8) == Array(key.utf8) }) else { throw Invalid.json }
        return pair.1
    }
    func text() throws -> String { guard case .string(let s) = self else { throw Invalid.json }; return s }
}
func quote(_ text: String) -> String {
    var out = "\""
    for scalar in text.unicodeScalars {
        switch scalar.value {
        case 34: out += "\\\""
        case 92: out += "\\\\"
        case 8: out += "\\b"
        case 9: out += "\\t"
        case 10: out += "\\n"
        case 12: out += "\\f"
        case 13: out += "\\r"
        case 0..<32: out += String(format: "\\u%04x", scalar.value)
        case 0x2028: out += "\\u2028"
        case 0x2029: out += "\\u2029"
        default: out.unicodeScalars.append(scalar)
        }
    }
    return out + "\""
}
struct Parser {
    let raw: [UInt8]; var i = 0
    init(_ data: Data) throws {
        guard data.count <= 1048576, String(data: data, encoding: .utf8) != nil else { throw Invalid.json }
        raw = Array(data)
    }
    mutating func ws() { while i < raw.count && [9,10,13,32].contains(raw[i]) { i += 1 } }
    mutating func string() throws -> String {
        let start = i; guard i < raw.count && raw[i] == 34 else { throw Invalid.json }; i += 1
        while i < raw.count {
            let c = raw[i]; i += 1
            if c == 92 { guard i < raw.count else { throw Invalid.json }; i += 1 }
            else if c == 34 {
                let data = Data(raw[start..<i])
                guard let result = try JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) as? String else { throw Invalid.json }
                return result
            }
        }
        throw Invalid.json
    }
    mutating func value(_ depth: Int) throws -> JSON {
        guard depth <= 64 else { throw Invalid.json }; ws(); guard i < raw.count else { throw Invalid.json }
        let c = raw[i]
        if c == 34 { return .string(try string()) }
        if c == 123 || c == 91 {
            let object = c == 123; let end: UInt8 = object ? 125 : 93; i += 1; ws()
            var pairs: [(String, JSON)] = []; var values: [JSON] = []
            if i < raw.count && raw[i] == end { i += 1; return object ? .object(pairs) : .array(values) }
            while true {
                if object {
                    ws(); let key = try string(); ws()
                    guard i < raw.count && raw[i] == 58 && !pairs.contains(where: { Array($0.0.utf8) == Array(key.utf8) }) else { throw Invalid.json }
                    i += 1; pairs.append((key, try value(depth + 1)))
                } else { values.append(try value(depth + 1)) }
                ws(); guard i < raw.count else { throw Invalid.json }; let next = raw[i]; i += 1
                if next == end { return object ? .object(pairs) : .array(values) }
                guard next == 44 else { throw Invalid.json }
            }
        }
        for (token, result) in [("true", JSON.bool(true)), ("false", JSON.bool(false)), ("null", JSON.null)] {
            let bytes = Array(token.utf8)
            if raw.dropFirst(i).starts(with: bytes) { i += bytes.count; return result }
        }
        let rest = String(decoding: raw[i...], as: UTF8.self)
        guard let range = rest.range(of: #"^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?"#, options: .regularExpression) else { throw Invalid.json }
        let number = String(rest[range]); i += number.utf8.count; return .number(number)
    }
    mutating func parse() throws -> JSON { let result = try value(0); ws(); guard i == raw.count else { throw Invalid.json }; return result }
}
func canonical(_ value: JSON) -> String {
    switch value {
    case .object(let pairs): return "{" + pairs.sorted { Array($0.0.utf8).lexicographicallyPrecedes(Array($1.0.utf8)) }.map { quote($0.0) + ":" + canonical($0.1) }.joined(separator: ",") + "}"
    case .array(let items): return "[" + items.map(canonical).joined(separator: ",") + "]"
    case .string(let text): return quote(text)
    case .number(let raw): return raw
    case .bool(let value): return value ? "true" : "false"
    case .null: return "null"
    }
}
func decode(_ value: JSON, _ size: Int) throws -> Data {
    let text = try value.text()
    guard text.range(of: #"^[A-Za-z0-9_-]+$"#, options: .regularExpression) != nil else { throw Invalid.json }
    let padded = text.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + String(repeating: "=", count: (4 - text.count % 4) % 4)
    guard let bytes = Data(base64Encoded: padded), bytes.count == size,
          bytes.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") == text else { throw Invalid.json }
    return bytes
}
func verifyEnvelope(_ keyRaw: Data, _ envRaw: Data) -> Bool {
    do {
        var kp = try Parser(keyRaw); var ep = try Parser(envRaw); let key = try kp.parse(); let env = try ep.parse()
        let bytes = try decode(key.get("public_key"), 32)
        let id = "ed25519_" + SHA256.hash(data: bytes).map { String(format: "%02x", $0) }.joined().prefix(16)
        guard try key.get("algorithm").text() == "Ed25519", try key.get("encoding").text() == "base64url", try key.get("key_id").text() == id,
              try env.get("algorithm").text() == "Ed25519", try env.get("key_id").text() == id else { return false }
        let payload = try env.get("payload"); guard case .object = payload else { return false }
        let publicKey = try Curve25519.Signing.PublicKey(rawRepresentation: bytes)
        return try publicKey.isValidSignature(decode(env.get("signature"), 64), for: Data(canonical(payload).utf8))
    } catch { return false }
}
var valid = false
do { if CommandLine.arguments.count == 3 { valid = verifyEnvelope(try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[1])), try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[2]))) } }
catch { valid = false }
print("signature_valid=\(valid)"); if !valid { exit(1) }

Expected result

All 23 cases pass in the shared runner, including integral floats, non-BMP key order, Unicode separators, duplicate JSON names, invalid signatures and excessive depth. The original response's numeric tokens are retained, not recalculated from binary floating-point values.

Common mistakes

  • Taking the verification key from the same untrusted message being checked.
  • Treating a successful signature as proof of active access, correct installation or recent payment.
  • Parsing and reserializing numbers before passing the envelope into this sample.

Security notes

The key input must come from your trusted pin or deliberate fixed-origin HTTPS trust policy. Public vector keys are test anchors, never production configuration. This sample fails closed on malformed data; never fall back to unverified features.

After signature success, check product, environment, installation hash, active state, start/expiry, application-version policy and signed validation/grace deadlines. See offline mode. The current official SDKs have documented canonicalization limitations; this independent verifier does not silently repair their caches or alter their public APIs.

Troubleshooting

SymptomCauseAction
Positive case failsWrong trusted key, changed numeric tokens or missing dependencyRestore the exact archive/vector files and rerun the build
Tampered case returns falseExpected rejectionKeep the refusal; do not bypass verification
Expired case returns trueAuthenticity and expiry are separateReject access in the license policy layer
Cannot open a fileTerminal is outside the extracted security directoryChange to that directory and retry using quoted paths

Next steps

Run all test vectors and apply the signed-response policy checks.

Was this page helpful?

Choose an answer

Do not include personal data, license keys or credentials. Retained for up to 30 days. This is not a support inbox.

Search documentation

Arrow keys to move, Enter to open, Escape to close. Search stays in your browser.

Cookie settings