Standard library · Security and cryptography

tls

Secure TLS 1.3 and 1.2 connections and public key cryptography tools, with no OpenSSL needed.

import tls

The tls package is a complete implementation of TLS written in Salam and needs no outside library. A client connects with Dial and checks the server certificate against the system roots; a server reads its certificate and key with NewServerConfig and secures each connection with WrapServer.

The cryptography tools here are also useful for building protocols of your own: the two sides make a shared secret with ECDH, derive a key from it with HKDF, and encrypt and seal the message with AES-GCM. Keys and data are passed around as base64.

Types

Config

struct Config
The client configuration.
NameTypeDescription
server_namestr = ""The hostname for SNI and for certificate verification.
ca_filestr = ""The file of root certificates; empty means the system roots.
insecure_skip_verifybool = falseDo not verify the certificate; for testing only, since it leaves the connection open to eavesdropping.
alpnstr = ""The protocol for ALPN, such as h2.
aes_onlybool = falseOffer AES ciphers only.
tls-Client.salam
import tls

func main:
    mut cfg := tls.NewConfig("example.com")
    cfg.alpn = "http/1.1"
    mut conn := tls.Dial("example.com", 443, cfg)
    if not tls.ConnOk(conn):
        println "error:", tls.ConnError(conn)
        ret
    end
    println tls.Version(conn), tls.CipherName(conn), tls.PeerCertCount(conn)
    _ := tls.Write(conn, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
    mut body := ""
    until tls.ConnOk(conn):
        chunk := tls.Read(conn, 4096)
        if chunk.len() == 0:
            break
        end
        body += chunk
    end
    println body.len() > 0
    tls.Close(conn)
end

Conn

struct Conn
One TLS connection.
NameTypeDescription
hHandshakeThe handshake state.
hoststr = ""The host.
openbool = falseThe connection is open.
insecurebool = falseWithout certificate verification.
pendingBufData that has been read but not taken yet.
errstr = ""The error message.

The example for this item is shown with the ones above: tls-Client.salam

ServerConfig

struct ServerConfig
The server configuration: the certificate chain and the private key.
NameTypeDescription
chainRootsThe certificate chain.
keyPrivateKeyThe private key.
okbool = falseBoth were read.
tls-Server.salam
import tls
import net.tcp
import mem

func main:
    cfg := tls.NewServerConfig("cert.pem", "key.pem")
    if not cfg.ok:
        println "the certificate or key could not be read"
        ret
    end
    server := tcp.Bind(8443)
    until true:
        sock := tcp.Accept(server)
        mut conn := tls.WrapServer(sock.fd, cfg)
        if tls.ConnOk(conn):
            if tls.Readable(conn) or tls.Pending(conn) > 0:
                println tls.Read(conn, 1024)
            end
            buffer := mem.Allocate(256 as u64)
            n := tls.ReadBytes(conn, buffer, 256)
            _ := tls.WriteBytes(conn, buffer, n)
            _ := tls.Write(conn, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello")
            mem.Free(buffer)
            println tls.Fd(conn) >= 0
        end
        tls.Close(conn)
    end
end

Constants and variables

TLS10

const TLS10: i32 = 769
The version number of TLS 1.0; not supported.
tls-Certs.salam
import tls
import net.tcp
import encoding
import io

func main:
    roots := tls.LoadRoots("/etc/ssl/cert.pem")
    println tls.RootCount(roots)

    chain := tls.LoadCertChainFile("cert.pem")
    println tls.RootCount(chain)
    mut key := tls.LoadPrivateKeyFile("key.pem")
    mut key2 := tls.ParsePrivateKeyPEM("-----BEGIN ...")
    tls.FreePrivateKey(key2)

    cert := tls.HexDecodeBuf("3082...")
    println tls.CertIsValid(cert), tls.CertSubjectCN(cert)
    code := tls.VerifyCert(cert, roots, "example.com")
    println code, tls.VerifyError(code)

    digest := tls.HashDigest(false, "message")
    sig := tls.NewBuf(512)
    println tls.SignRSAPKCS1(key, false, digest, sig)
    println tls.SignRSAPSS(key, false, digest, sig)
    println tls.VerifyRSAPSSWithKey(key, false, digest, sig)
    r := tls.NewBuf(32)
    s := tls.NewBuf(32)
    if tls.SignECDSA(key, digest, r, s):
        der := tls.ECDSADerEncode(r, s, 32)
        println tls.VerifyECDSADERWithKey(key, digest, der)
    end

    pem := io.ReadFile("rsa-key.pem")
    mut good := false
    println tls.RsaKeyBits(pem)
    println tls.RsaSignPkcs1Sha256(pem, encoding.Base64Encode("..."), good)

    mut cfg := tls.NewConfig("internal.example")
    cfg.ca_file = "company-ca.pem"
    cfg.aes_only = true
    mut conn := tls.Dial("internal.example", 443, cfg)
    println tls.ConnOk(conn)
    tls.Close(conn)
    sock := tcp.Dial("example.com", 443)
    mut conn2 := tls.WrapClient(sock.fd, tls.NewConfig("example.com"))
    println tls.Version(conn2) == "TLS1.3"
    tls.Close(conn2)
    println tls.TLS10, tls.TLS11, tls.TLS12, tls.TLS13
    println tls.SelfTest()
    tls.FreeRoots(roots)
    tls.FreePrivateKey(key)
end

TLS11

const TLS11: i32 = 770
TLS 1.1; not supported.

The example for this item is shown with the ones above: tls-Certs.salam

TLS12

const TLS12: i32 = 771
TLS 1.2.

The example for this item is shown with the ones above: tls-Certs.salam

TLS13

const TLS13: i32 = 772
TLS 1.3.

The example for this item is shown with the ones above: tls-Certs.salam

Functions

SelfTest

func SelfTest(): int
Runs the internal tests of the package and reports them; returns the number of failures.

The example for this item is shown with the ones above: tls-Certs.salam

LoadCertChainFile

func LoadCertChainFile(path: str): Roots
Reads the certificate chain from a PEM file.

The example for this item is shown with the ones above: tls-Certs.salam

EcdhP256Generate

func EcdhP256Generate(priv &: str, pub_spki &: str): bool
An ECDH key pair on the P-256 curve: the private key as base64 and the SPKI public key as base64.
tls-Crypto.salam
import tls
import encoding

func main:
    mut priv1 := ""
    mut pub1 := ""
    mut priv2 := ""
    mut pub2 := ""
    println tls.EcdhP256Generate(priv1, pub1), tls.EcdhP256Generate(priv2, pub2)
    mut good := false
    secret1 := tls.EcdhP256Shared(priv1, pub2, good)
    secret2 := tls.EcdhP256Shared(priv2, pub1, good)
    println good, secret1 == secret2

    point1 := tls.EcdhP256PublicPoint(priv1)
    point2 := tls.EcdhP256PublicPoint(priv2)
    println tls.EcdhP256SharedPoint(priv1, point2, good) == tls.EcdhP256SharedPoint(priv2, point1, good)

    salt := tls.RandomSalt(16)
    key := tls.HkdfSha256(secret1, salt, "chat v1", 32, good)
    println good, key == tls.HkdfSha256(secret2, salt, "chat v1", 32, good)
    println tls.HkdfSha256Raw(secret1, salt, encoding.Base64Encode("chat v1"), 32, good) == key

    nonce := tls.GcmNonce(0)
    sealed := tls.AesGcmSeal(key, nonce, "hello, this message is encrypted", good)
    println good
    println tls.AesGcmOpen(key, nonce, sealed, good), good
    _ := tls.AesGcmOpen(key, tls.GcmNonce(1), sealed, good)
    println good
    binary := tls.AesGcmSealBytes(key, nonce, encoding.Base64Encode("abc"), good)
    println encoding.Base64Decode(tls.AesGcmOpenBytes(key, nonce, binary, good))

    digest := encoding.Base64Encode("0123456789abcdef0123456789abcdef")
    sig := tls.P256Sign(priv1, digest, good)
    println good, tls.P256Verify(point1, digest, sig), tls.P256Verify(point2, digest, sig)

    b := tls.HashDigest(false, "abc")
    println tls.BufLen(b), tls.HexEncodeBuf(b)
    mut hex := tls.HexDecodeBuf("cafe")
    tls.BufAppendStr(hex, "!")
    println tls.BufLen(hex), tls.HexEncodeBuf(hex)
    mut zeros := tls.NewZeroBuf(4)
    println tls.HexEncodeBuf(zeros)
    mut empty := tls.NewBuf(64)
    println tls.BufLen(empty)
    tls.WipeBuf(hex)
    tls.FreeBuf(hex)
    tls.FreeBuf(zeros)
    tls.FreeBuf(empty)
end
Outputtrue true true true true true true true true hello, this message is encrypted true false abc true true false 32 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3 cafe21 00000000 0

EcdhP256Shared

func EcdhP256Shared(priv_b64: str, peer_spki_b64: str, ok &: bool): str
The shared secret from your own private key and the public key of the other side.

The example for this item is shown with the ones above: tls-Crypto.salam

HkdfSha256

func HkdfSha256(ikm_b64: str, salt_b64: str, info: str, out_len: int, ok &: bool): str
Derives a key of any length from a secret with a salt and an info string; give every use its own info string.

The example for this item is shown with the ones above: tls-Crypto.salam

AesGcmSeal

func AesGcmSeal(key_b64: str, nonce_b64: str, plaintext: str, ok &: bool): str
Encrypts and seals the text with AES-GCM; the output is base64. Never use one nonce twice with the same key.

The example for this item is shown with the ones above: tls-Crypto.salam

AesGcmOpen

func AesGcmOpen(key_b64: str, nonce_b64: str, sealed_b64: str, ok &: bool): str
Opens sealed text; when it was tampered with, or the key or nonce is wrong, ok becomes false.

The example for this item is shown with the ones above: tls-Crypto.salam

GcmNonce

func GcmNonce(counter: i64): str
A 12 byte nonce from a counter; raise the counter by one for every message.

The example for this item is shown with the ones above: tls-Crypto.salam

RandomSalt

func RandomSalt(n: int): str
n random bytes as base64.

The example for this item is shown with the ones above: tls-Crypto.salam

EcdhP256PublicPoint

func EcdhP256PublicPoint(priv_b64: str): str
The raw public point of a private key, 65 bytes as base64.

The example for this item is shown with the ones above: tls-Crypto.salam

EcdhP256SharedPoint

func EcdhP256SharedPoint(priv_b64: str, peer_point_b64: str, ok &: bool): str
The shared secret from the raw public point of the other side.

The example for this item is shown with the ones above: tls-Crypto.salam

HkdfSha256Raw

func HkdfSha256Raw(ikm_b64: str, salt_b64: str, info_b64: str, out_len: int, ok &: bool): str
The same, with the info string as base64.

The example for this item is shown with the ones above: tls-Crypto.salam

AesGcmSealBytes

func AesGcmSealBytes(key_b64: str, nonce_b64: str, plaintext_b64: str, ok &: bool): str
Like seal, with base64 input for binary data.

The example for this item is shown with the ones above: tls-Crypto.salam

AesGcmOpenBytes

func AesGcmOpenBytes(key_b64: str, nonce_b64: str, sealed_b64: str, ok &: bool): str
Like open, with base64 output.

The example for this item is shown with the ones above: tls-Crypto.salam

P256Sign

func P256Sign(priv_b64: str, digest_b64: str, ok &: bool): str
A deterministic ECDSA signature under RFC 6979 over a digest; 64 bytes of r and s as base64.

The example for this item is shown with the ones above: tls-Crypto.salam

P256Verify

func P256Verify(pub_point_b64: str, digest_b64: str, sig_b64: str): bool
Verifies the signature against a public point.

The example for this item is shown with the ones above: tls-Crypto.salam

RsaSignPkcs1Sha256

func RsaSignPkcs1Sha256(pem: str, digest_b64: str, ok &: bool): str
Signs a digest with an RSA key given as PEM text.

The example for this item is shown with the ones above: tls-Certs.salam

RsaKeyBits

func RsaKeyBits(pem: str): int
The length of the RSA key in bits.

The example for this item is shown with the ones above: tls-Certs.salam

RsaEncryptOaepSha1

func RsaEncryptOaepSha1(pem: str, plain_b64: str, ok &: bool): str
Encrypts a small piece of data with an RSA public key, given as PEM, and OAEP padding with SHA-1; input and output are base64.
tls-RsaEncryptOaepSha1.salam
import tls
import encoding
import io

func main:
    key := io.ReadFile("public.pem")
    mut ok := false
    cipher := tls.RsaEncryptOaepSha1(key, encoding.Base64Encode("hello"), ok)
    if not ok:
        println "the RSA public key could not be read"
        ret
    end
    println cipher.len() > 0
end

FreePrivateKey

func FreePrivateKey(k &: PrivateKey)
Wipes the private key from memory and frees it.

The example for this item is shown with the ones above: tls-Certs.salam

ParsePrivateKeyPEM

func ParsePrivateKeyPEM(pem: str): PrivateKey
Reads an RSA or EC key from PEM text.

The example for this item is shown with the ones above: tls-Certs.salam

LoadPrivateKeyFile

func LoadPrivateKeyFile(path: str): PrivateKey
Reads the key from a PEM file.

The example for this item is shown with the ones above: tls-Certs.salam

NewConfig

func NewConfig(server_name: str): Config
A client configuration for one host.

The example for this item is shown with the ones above: tls-Client.salam

Dial

func Dial(host: str, port: int, cfg: Config): Conn
Connects to the host and does the TLS 1.3 or 1.2 handshake.

The example for this item is shown with the ones above: tls-Client.salam

WrapClient

func WrapClient(fd: i64, cfg: Config): Conn
Does the client handshake over an open TCP socket.

The example for this item is shown with the ones above: tls-Certs.salam

NewServerConfig

func NewServerConfig(cert_file: str, key_file: str): ServerConfig
Reads the server certificate and key from PEM files.

The example for this item is shown with the ones above: tls-Server.salam

WrapServer

func WrapServer(fd: i64, cfg: ServerConfig): Conn
Does the server handshake over a socket you accepted.

The example for this item is shown with the ones above: tls-Server.salam

ConnOk

func ConnOk(c: Conn): bool
The connection is healthy.

The example for this item is shown with the ones above: tls-Client.salam

ConnError

func ConnError(c: Conn): str
The error message of the connection.

The example for this item is shown with the ones above: tls-Client.salam

Write

func Write(c &: Conn, data: str): int
Sends data and returns the number of bytes.

The example for this item is shown with the ones above: tls-Client.salam

WriteBytes

func WriteBytes(c &: Conn, ptr: void*, n: int): int
Sends bytes from memory.

The example for this item is shown with the ones above: tls-Server.salam

Read

func Read(c &: Conn, max: int): str
Reads up to this many bytes; an empty string means the connection closed.

The example for this item is shown with the ones above: tls-Client.salam

ReadBytes

func ReadBytes(c &: Conn, buf: void*, max: int): int
Reads into the given memory.

The example for this item is shown with the ones above: tls-Server.salam

Pending

func Pending(c: Conn): int
The bytes that are decrypted and ready to read.

The example for this item is shown with the ones above: tls-Server.salam

Readable

func Readable(c: Conn): bool
There is something to read without waiting.

The example for this item is shown with the ones above: tls-Server.salam

Fd

func Fd(c: Conn): i64
The descriptor of the socket underneath.

The example for this item is shown with the ones above: tls-Server.salam

Detach

func Detach(c &: Conn)
Sends the close alert and frees the TLS state, but leaves the socket open.
tls-Detach.salam
import tls
import net.tcp

func main:
    sock := tcp.Dial("example.com", 443)
    if not tcp.ConnOk(sock):
        println "the connection was not made"
        ret
    end
    mut conn := tls.WrapClient(sock.fd, tls.NewConfig("example.com"))
    println tls.ConnOk(conn)
    tls.Detach(conn)
    tcp.Close(sock)
end

Close

func Close(c &: Conn)
Sends the close alert and closes the socket.

The example for this item is shown with the ones above: tls-Client.salam

Version

func Version(c: Conn): str
The agreed version, such as TLS1.3.

The example for this item is shown with the ones above: tls-Client.salam

CipherName

func CipherName(c: Conn): str
The name of the agreed cipher.

The example for this item is shown with the ones above: tls-Client.salam

PeerCertCount

func PeerCertCount(c: Conn): int
The number of certificates the other side sent.

The example for this item is shown with the ones above: tls-Client.salam

HexEncodeBuf

func HexEncodeBuf(b: Buf): str
Turns a buffer into hex.

The example for this item is shown with the ones above: tls-Crypto.salam

HexDecodeBuf

func HexDecodeBuf(s: str): Buf
Turns hex back into a buffer.

The example for this item is shown with the ones above: tls-Crypto.salam

NewBuf

func NewBuf(cap: int): Buf
An empty buffer with this capacity.

The example for this item is shown with the ones above: tls-Crypto.salam

NewZeroBuf

func NewZeroBuf(n: int): Buf
A buffer of n zero bytes.

The example for this item is shown with the ones above: tls-Crypto.salam

FreeBuf

func FreeBuf(b &: Buf)
Frees the buffer.

The example for this item is shown with the ones above: tls-Crypto.salam

WipeBuf

func WipeBuf(b &: Buf)
Zeroes the contents of the buffer; for secrets, before freeing.

The example for this item is shown with the ones above: tls-Crypto.salam

BufLen

func BufLen(b: Buf): int
The length of the buffer.

The example for this item is shown with the ones above: tls-Crypto.salam

BufAppendStr

func BufAppendStr(b &: Buf, s: str)
Adds a string to the buffer.

The example for this item is shown with the ones above: tls-Crypto.salam

LoadRoots

func LoadRoots(ca_file: str): Roots
Reads the root certificates from a PEM file.

The example for this item is shown with the ones above: tls-Certs.salam

RootCount

func RootCount(r: Roots): int
The number of certificates.

The example for this item is shown with the ones above: tls-Certs.salam

FreeRoots

func FreeRoots(r &: Roots)
Frees the certificates.

The example for this item is shown with the ones above: tls-Certs.salam

VerifyError

func VerifyError(code: int): str
The explanation of a certificate verification error code.

The example for this item is shown with the ones above: tls-Certs.salam

VerifyCert

func VerifyCert(der: Buf, roots: Roots, host: str): int
Verifies a DER certificate against the roots and the hostname; zero means valid.

The example for this item is shown with the ones above: tls-Certs.salam

CertIsValid

func CertIsValid(der: Buf): bool
The DER certificate parses correctly.

The example for this item is shown with the ones above: tls-Certs.salam

CertSubjectCN

func CertSubjectCN(der: Buf): str
The common name (CN) of the certificate.

The example for this item is shown with the ones above: tls-Certs.salam

HashDigest

func HashDigest(sha384: bool, msg: str): Buf
The SHA-256 digest, or SHA-384 when the first parameter is true, into a buffer.

The example for this item is shown with the ones above: tls-Crypto.salam

SignRSAPKCS1

func SignRSAPKCS1(k: PrivateKey, sha384: bool, digest: Buf, sigout: Buf): bool
An RSA signature with PKCS#1 v1.5.

The example for this item is shown with the ones above: tls-Certs.salam

SignRSAPSS

func SignRSAPSS(k: PrivateKey, sha384: bool, digest: Buf, sigout: Buf): bool
An RSA-PSS signature.

The example for this item is shown with the ones above: tls-Certs.salam

SignECDSA

func SignECDSA(k: PrivateKey, digest: Buf, rout: Buf, sout: Buf): bool
An ECDSA signature with r and s apart.

The example for this item is shown with the ones above: tls-Certs.salam

ECDSADerEncode

func ECDSADerEncode(rbytes: Buf, sbytes: Buf, width: int): Buf
Turns r and s into the DER signature format.

The example for this item is shown with the ones above: tls-Certs.salam

VerifyRSAPSSWithKey

func VerifyRSAPSSWithKey(k: PrivateKey, sha384: bool, digest: Buf, sig: Buf): bool
Verifies an RSA-PSS signature against a key.

The example for this item is shown with the ones above: tls-Certs.salam

VerifyECDSADERWithKey

func VerifyECDSADERWithKey(k: PrivateKey, digest: Buf, dersig: Buf): bool
Verifies an ECDSA signature in DER format.

The example for this item is shown with the ones above: tls-Certs.salam